author | Josh Matthews <josh@joshmatthews.net> |
Mon, 15 May 2017 15:00:19 -0500 | |
changeset 358533 | ba0f4d2877263ffea0f71f5311ae8c780606b0fc |
parent 358532 | e12dcb4be8551c14073c891a4c9f790e0d790215 |
child 358534 | 7a2841383525164d5651218a06f3b5e74bd570af |
push id | 90352 |
push user | cbook@mozilla.com |
push date | Tue, 16 May 2017 13:09:14 +0000 |
treeherder | mozilla-inbound@8f89d291e303 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | nox |
milestone | 55.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/servo/components/layout/animation.rs +++ b/servo/components/layout/animation.rs @@ -4,31 +4,34 @@ //! CSS transitions and animations. use context::LayoutContext; use flow::{self, Flow}; use gfx::display_list::OpaqueNode; use ipc_channel::ipc::IpcSender; use msg::constellation_msg::PipelineId; +use opaque_node::OpaqueNodeMethods; use script_traits::{AnimationState, ConstellationControlMsg, LayoutMsg as ConstellationMsg}; +use script_traits::UntrustedNodeAddress; use std::collections::HashMap; use std::sync::mpsc::Receiver; use style::animation::{Animation, update_style_for_animation}; use style::font_metrics::ServoMetricsProvider; use style::selector_parser::RestyleDamage; use style::timer::Timer; /// Processes any new animations that were discovered after style recalculation. /// Also expire any old animations that have completed, inserting them into /// `expired_animations`. pub fn update_animation_state(constellation_chan: &IpcSender<ConstellationMsg>, script_chan: &IpcSender<ConstellationControlMsg>, running_animations: &mut HashMap<OpaqueNode, Vec<Animation>>, expired_animations: &mut HashMap<OpaqueNode, Vec<Animation>>, + mut newly_transitioning_nodes: Option<&mut Vec<UntrustedNodeAddress>>, new_animations_receiver: &Receiver<Animation>, pipeline_id: PipelineId, timer: &Timer) { let mut new_running_animations = vec![]; while let Ok(animation) = new_animations_receiver.try_recv() { let mut should_push = true; if let Animation::Keyframes(ref node, ref name, ref state) = animation { // If the animation was already present in the list for the @@ -66,33 +69,33 @@ pub fn update_animation_state(constellat // TODO: Do not expunge Keyframes animations, since we need that state if // the animation gets re-triggered. Probably worth splitting in two // different maps, or at least using a linked list? let mut keys_to_remove = vec![]; for (key, running_animations) in running_animations.iter_mut() { let mut animations_still_running = vec![]; for mut running_animation in running_animations.drain(..) { let still_running = !running_animation.is_expired() && match running_animation { - Animation::Transition(_, _, started_at, ref frame, _expired) => { + Animation::Transition(_, started_at, ref frame, _expired) => { now < started_at + frame.duration } Animation::Keyframes(_, _, ref mut state) => { // This animation is still running, or we need to keep // iterating. now < state.started_at + state.duration || state.tick() } }; if still_running { animations_still_running.push(running_animation); continue } - if let Animation::Transition(_, unsafe_node, _, ref frame, _) = running_animation { - script_chan.send(ConstellationControlMsg::TransitionEnd(unsafe_node, + if let Animation::Transition(node, _, ref frame, _) = running_animation { + script_chan.send(ConstellationControlMsg::TransitionEnd(node.to_untrusted_node_address(), frame.property_animation .property_name().into(), frame.duration)) .unwrap(); } expired_animations.entry(*key) .or_insert_with(Vec::new) @@ -107,16 +110,27 @@ pub fn update_animation_state(constellat } for key in keys_to_remove { running_animations.remove(&key).unwrap(); } // Add new running animations. for new_running_animation in new_running_animations { + if new_running_animation.is_transition() { + match newly_transitioning_nodes { + Some(ref mut nodes) => { + nodes.push(new_running_animation.node().to_untrusted_node_address()); + } + None => { + warn!("New transition encountered from compositor-initiated layout."); + } + } + } + running_animations.entry(*new_running_animation.node()) .or_insert_with(Vec::new) .push(new_running_animation) } let animation_state = if running_animations.is_empty() { AnimationState::NoAnimationsPresent } else {
--- a/servo/components/layout/context.rs +++ b/servo/components/layout/context.rs @@ -10,16 +10,17 @@ use gfx::font_cache_thread::FontCacheThr use gfx::font_context::FontContext; use heapsize::HeapSizeOf; use msg::constellation_msg::PipelineId; use net_traits::image_cache::{CanRequestImages, ImageCache, ImageState}; use net_traits::image_cache::{ImageOrMetadataAvailable, UsePlaceholder}; use opaque_node::OpaqueNodeMethods; use parking_lot::RwLock; use script_layout_interface::{PendingImage, PendingImageState}; +use script_traits::UntrustedNodeAddress; use servo_url::ServoUrl; use std::borrow::{Borrow, BorrowMut}; use std::cell::{RefCell, RefMut}; use std::collections::HashMap; use std::hash::BuildHasherDefault; use std::sync::{Arc, Mutex}; use std::thread; use style::context::{SharedStyleContext, ThreadLocalStyleContext}; @@ -91,17 +92,21 @@ pub struct LayoutContext<'a> { /// A cache of WebRender image info. pub webrender_image_cache: Arc<RwLock<HashMap<(ServoUrl, UsePlaceholder), WebRenderImageInfo, BuildHasherDefault<FnvHasher>>>>, /// A list of in-progress image loads to be shared with the script thread. /// A None value means that this layout was not initiated by the script thread. - pub pending_images: Option<Mutex<Vec<PendingImage>>> + pub pending_images: Option<Mutex<Vec<PendingImage>>>, + + /// A list of nodes that have just initiated a CSS transition. + /// A None value means that this layout was not initiated by the script thread. + pub newly_transitioning_nodes: Option<Mutex<Vec<UntrustedNodeAddress>>>, } impl<'a> Drop for LayoutContext<'a> { fn drop(&mut self) { if !thread::panicking() { if let Some(ref pending_images) = self.pending_images { assert!(pending_images.lock().unwrap().is_empty()); }
--- a/servo/components/layout/query.rs +++ b/servo/components/layout/query.rs @@ -12,28 +12,26 @@ use euclid::rect::Rect; use euclid::size::Size2D; use flow::{self, Flow}; use fragment::{Fragment, FragmentBorderBoxIterator, SpecificFragmentInfo}; use gfx::display_list::{DisplayItemMetadata, DisplayList, OpaqueNode, ScrollOffsetMap}; use inline::LAST_FRAGMENT_OF_ELEMENT; use ipc_channel::ipc::IpcSender; use msg::constellation_msg::PipelineId; use opaque_node::OpaqueNodeMethods; -use script_layout_interface::PendingImage; use script_layout_interface::rpc::{ContentBoxResponse, ContentBoxesResponse}; use script_layout_interface::rpc::{HitTestResponse, LayoutRPC}; use script_layout_interface::rpc::{MarginStyleResponse, NodeGeometryResponse}; use script_layout_interface::rpc::{NodeOverflowResponse, OffsetParentResponse}; use script_layout_interface::rpc::{NodeScrollRootIdResponse, ResolvedStyleResponse, TextIndexResponse}; use script_layout_interface::wrapper_traits::{LayoutNode, ThreadSafeLayoutElement, ThreadSafeLayoutNode}; use script_traits::LayoutMsg as ConstellationMsg; use script_traits::UntrustedNodeAddress; use sequential; use std::cmp::{min, max}; -use std::mem; use std::ops::Deref; use std::sync::{Arc, Mutex}; use style::computed_values; use style::context::{StyleContext, ThreadLocalStyleContext}; use style::dom::TElement; use style::logical_geometry::{WritingMode, BlockFlowDirection, InlineBaseDirection}; use style::properties::{style_structs, PropertyId, PropertyDeclarationId, LonghandId}; use style::properties::longhands::{display, position}; @@ -84,19 +82,16 @@ pub struct LayoutThreadData { pub margin_style_response: MarginStyleResponse, /// Scroll offsets of stacking contexts. This will only be populated if WebRender is in use. pub stacking_context_scroll_offsets: ScrollOffsetMap, /// Index in a text fragment. We need this do determine the insertion point. pub text_index_response: TextIndexResponse, - /// A list of images requests that need to be initiated. - pub pending_images: Vec<PendingImage>, - /// A queued response for the list of nodes at a given point. pub nodes_from_point_response: Vec<UntrustedNodeAddress>, } pub struct LayoutRPCImpl(pub Arc<Mutex<LayoutThreadData>>); // https://drafts.csswg.org/cssom-view/#overflow-directions fn overflow_direction(writing_mode: &WritingMode) -> OverflowDirection { @@ -193,22 +188,16 @@ impl LayoutRPC for LayoutRPCImpl { rw_data.margin_style_response.clone() } fn text_index(&self) -> TextIndexResponse { let &LayoutRPCImpl(ref rw_data) = self; let rw_data = rw_data.lock().unwrap(); rw_data.text_index_response.clone() } - - fn pending_images(&self) -> Vec<PendingImage> { - let &LayoutRPCImpl(ref rw_data) = self; - let mut rw_data = rw_data.lock().unwrap(); - mem::replace(&mut rw_data.pending_images, vec![]) - } } struct UnioningFragmentBorderBoxIterator { node_address: OpaqueNode, rect: Option<Rect<Au>>, } impl UnioningFragmentBorderBoxIterator {
--- a/servo/components/layout_thread/lib.rs +++ b/servo/components/layout_thread/lib.rs @@ -76,17 +76,18 @@ use layout::wrapper::drop_style_and_layo use layout_traits::LayoutThreadFactory; use msg::constellation_msg::{FrameId, PipelineId}; use net_traits::image_cache::{ImageCache, UsePlaceholder}; use parking_lot::RwLock; use profile_traits::mem::{self, Report, ReportKind, ReportsChan}; use profile_traits::time::{self, TimerMetadata, profile}; use profile_traits::time::{TimerMetadataFrameType, TimerMetadataReflowType}; use script::layout_wrapper::{ServoLayoutElement, ServoLayoutDocument, ServoLayoutNode}; -use script_layout_interface::message::{Msg, NewLayoutThreadInfo, Reflow, ReflowQueryType, ScriptReflow}; +use script_layout_interface::message::{Msg, NewLayoutThreadInfo, Reflow, ReflowQueryType}; +use script_layout_interface::message::{ScriptReflow, ReflowComplete}; use script_layout_interface::reporter::CSSErrorReporter; use script_layout_interface::rpc::{LayoutRPC, MarginStyleResponse, NodeOverflowResponse, OffsetParentResponse}; use script_layout_interface::rpc::TextIndexResponse; use script_layout_interface::wrapper_traits::LayoutNode; use script_traits::{ConstellationControlMsg, LayoutControlMsg, LayoutMsg as ConstellationMsg}; use script_traits::{StackingContextScrollState, UntrustedNodeAddress}; use selectors::Element; use servo_config::opts; @@ -287,16 +288,47 @@ impl LayoutThreadFactory for LayoutThrea } if let Some(content_process_shutdown_chan) = content_process_shutdown_chan { let _ = content_process_shutdown_chan.send(()); } }).expect("Thread spawning failed"); } } +struct ScriptReflowResult { + script_reflow: ScriptReflow, + result: RefCell<Option<ReflowComplete>>, +} + +impl Deref for ScriptReflowResult { + type Target = ScriptReflow; + fn deref(&self) -> &ScriptReflow { + &self.script_reflow + } +} + +impl ScriptReflowResult { + fn new(script_reflow: ScriptReflow) -> ScriptReflowResult { + ScriptReflowResult { + script_reflow: script_reflow, + result: RefCell::new(Some(Default::default())), + } + } +} + +impl Drop for ScriptReflowResult { + fn drop(&mut self) { + self.script_reflow.script_join_chan.send( + self.result + .borrow_mut() + .take() + .unwrap()).unwrap(); + } +} + /// The `LayoutThread` `rw_data` lock must remain locked until the first reflow, /// as RPC calls don't make sense until then. Use this in combination with /// `LayoutThread::lock_rw_data` and `LayoutThread::return_rw_data`. pub enum RWGuard<'a> { /// If the lock was previously held, from when the thread started. Held(MutexGuard<'a, LayoutThreadData>), /// If the lock was just used, and has been returned since there has been /// a reflow already. @@ -471,17 +503,16 @@ impl LayoutThread { scroll_root_id_response: None, scroll_area_response: Rect::zero(), overflow_response: NodeOverflowResponse(None), resolved_style_response: String::new(), offset_parent_response: OffsetParentResponse::empty(), margin_style_response: MarginStyleResponse::empty(), stacking_context_scroll_offsets: HashMap::new(), text_index_response: TextIndexResponse(None), - pending_images: vec![], nodes_from_point_response: vec![], })), error_reporter: CSSErrorReporter { pipelineid: id, script_chan: Arc::new(Mutex::new(script_chan)), }, webrender_image_cache: Arc::new(RwLock::new(HashMap::with_hasher(Default::default()))), @@ -508,17 +539,17 @@ impl LayoutThread { while self.handle_request(&mut rw_data) { // Loop indefinitely. } } // Create a layout context for use in building display lists, hit testing, &c. fn build_layout_context<'a>(&'a self, guards: StylesheetGuards<'a>, - request_images: bool, + script_initiated_layout: bool, snapshot_map: &'a SnapshotMap) -> LayoutContext<'a> { let thread_local_style_context_creation_data = ThreadLocalStyleContextCreationInfo::new(self.new_animations_sender.clone()); LayoutContext { id: self.id, style_context: SharedStyleContext { @@ -532,17 +563,18 @@ impl LayoutThread { timer: self.timer.clone(), quirks_mode: self.quirks_mode.unwrap(), traversal_flags: TraversalFlags::empty(), snapshot_map: snapshot_map, }, image_cache: self.image_cache.clone(), font_cache_thread: Mutex::new(self.font_cache_thread.clone()), webrender_image_cache: self.webrender_image_cache.clone(), - pending_images: if request_images { Some(Mutex::new(vec![])) } else { None }, + pending_images: if script_initiated_layout { Some(Mutex::new(vec![])) } else { None }, + newly_transitioning_nodes: if script_initiated_layout { Some(Mutex::new(vec![])) } else { None }, } } /// Receives and dispatches messages from the script and constellation threads fn handle_request<'a, 'b>(&mut self, possibly_locked_rw_data: &mut RwData<'a, 'b>) -> bool { enum Request { FromPipeline(LayoutControlMsg), FromScript(Msg), @@ -609,20 +641,21 @@ impl LayoutThread { self.handle_add_stylesheet(style_info, possibly_locked_rw_data) } Msg::SetQuirksMode(mode) => self.handle_set_quirks_mode(mode), Msg::GetRPC(response_chan) => { response_chan.send(box LayoutRPCImpl(self.rw_data.clone()) as Box<LayoutRPC + Send>).unwrap(); }, Msg::Reflow(data) => { + let mut data = ScriptReflowResult::new(data); profile(time::ProfilerCategory::LayoutPerform, self.profiler_metadata(), self.time_profiler_chan.clone(), - || self.handle_reflow(&data, possibly_locked_rw_data)); + || self.handle_reflow(&mut data, possibly_locked_rw_data)); }, Msg::TickAnimations => self.tick_all_animations(possibly_locked_rw_data), Msg::SetStackingContextScrollStates(new_scroll_states) => { self.set_stacking_context_scroll_states(new_scroll_states, possibly_locked_rw_data); } Msg::ReapStyleAndLayoutData(dead_data) => { unsafe { @@ -948,17 +981,17 @@ impl LayoutThread { builder.finalize(), true); self.webrender_api.generate_frame(None); }); } /// The high-level routine that performs layout threads. fn handle_reflow<'a, 'b>(&mut self, - data: &ScriptReflow, + data: &mut ScriptReflowResult, possibly_locked_rw_data: &mut RwData<'a, 'b>) { let document = unsafe { ServoLayoutNode::new(&data.document) }; let document = document.as_document().unwrap(); self.quirks_mode = Some(document.quirks_mode()); // FIXME(pcwalton): Combine `ReflowGoal` and `ReflowQueryType`. Then remove this assert. debug_assert!((data.reflow_info.goal == ReflowGoal::ForDisplay && data.query_type == ReflowQueryType::NoQuery) || @@ -1233,28 +1266,36 @@ impl LayoutThread { Some(&data.query_type), Some(&document), &mut rw_data, &mut layout_context); } self.respond_to_query_if_necessary(&data.query_type, &mut *rw_data, - &mut layout_context); + &mut layout_context, + data.result.borrow_mut().as_mut().unwrap()); } fn respond_to_query_if_necessary(&self, query_type: &ReflowQueryType, rw_data: &mut LayoutThreadData, - context: &mut LayoutContext) { + context: &mut LayoutContext, + reflow_result: &mut ReflowComplete) { let pending_images = match context.pending_images { Some(ref pending) => std_mem::replace(&mut *pending.lock().unwrap(), vec![]), None => vec![], }; - rw_data.pending_images = pending_images; + reflow_result.pending_images = pending_images; + + let newly_transitioning_nodes = match context.newly_transitioning_nodes { + Some(ref nodes) => std_mem::replace(&mut *nodes.lock().unwrap(), vec![]), + None => vec![], + }; + reflow_result.newly_transitioning_nodes = newly_transitioning_nodes; let mut root_flow = match self.root_flow.borrow().clone() { Some(root_flow) => root_flow, None => return, }; let root_flow = FlowRef::deref_mut(&mut root_flow); match *query_type { ReflowQueryType::ContentBoxQuery(node) => { @@ -1421,34 +1462,45 @@ impl LayoutThread { } self.perform_post_style_recalc_layout_passes(&mut root_flow, &reflow_info, None, None, &mut *rw_data, &mut layout_context); assert!(layout_context.pending_images.is_none()); + assert!(layout_context.newly_transitioning_nodes.is_none()); } } fn perform_post_style_recalc_layout_passes(&self, root_flow: &mut FlowRef, data: &Reflow, query_type: Option<&ReflowQueryType>, document: Option<&ServoLayoutDocument>, rw_data: &mut LayoutThreadData, context: &mut LayoutContext) { - // Kick off animations if any were triggered, expire completed ones. - animation::update_animation_state(&self.constellation_chan, - &self.script_chan, - &mut *self.running_animations.write(), - &mut *self.expired_animations.write(), - &self.new_animations_receiver, - self.id, - &self.timer); + { + let mut newly_transitioning_nodes = context + .newly_transitioning_nodes + .as_ref() + .map(|nodes| nodes.lock().unwrap()); + let newly_transitioning_nodes = newly_transitioning_nodes + .as_mut() + .map(|nodes| &mut **nodes); + // Kick off animations if any were triggered, expire completed ones. + animation::update_animation_state(&self.constellation_chan, + &self.script_chan, + &mut *self.running_animations.write(), + &mut *self.expired_animations.write(), + newly_transitioning_nodes, + &self.new_animations_receiver, + self.id, + &self.timer); + } profile(time::ProfilerCategory::LayoutRestyleDamagePropagation, self.profiler_metadata(), self.time_profiler_chan.clone(), || { // Call `compute_layout_damage` even in non-incremental mode, because it sets flags // that are needed in both incremental and non-incremental traversals. let damage = FlowRef::deref_mut(root_flow).compute_layout_damage();
--- a/servo/components/script/dom/document.rs +++ b/servo/components/script/dom/document.rs @@ -821,32 +821,35 @@ impl Document { pub fn dirty_all_nodes(&self) { let root = self.upcast::<Node>(); for node in root.traverse_preorder() { node.dirty(NodeDamage::OtherNodeDamage) } } + #[allow(unsafe_code)] pub fn handle_mouse_event(&self, js_runtime: *mut JSRuntime, button: MouseButton, client_point: Point2D<f32>, mouse_event_type: MouseEventType) { let mouse_event_type_string = match mouse_event_type { MouseEventType::Click => "click".to_owned(), MouseEventType::MouseUp => "mouseup".to_owned(), MouseEventType::MouseDown => "mousedown".to_owned(), }; debug!("{}: at {:?}", mouse_event_type_string, client_point); let node = match self.window.hit_test_query(client_point, false) { Some(node_address) => { debug!("node address is {:?}", node_address); - node::from_untrusted_node_address(js_runtime, node_address) + unsafe { + node::from_untrusted_node_address(js_runtime, node_address) + } }, None => return, }; let el = match node.downcast::<Element>() { Some(el) => Root::from_ref(el), None => { let parent = node.GetParentNode(); @@ -983,23 +986,26 @@ impl Document { return; } } // Update last_click_info with the time and position of the click. *self.last_click_info.borrow_mut() = Some((now, click_pos)); } + #[allow(unsafe_code)] pub fn handle_touchpad_pressure_event(&self, js_runtime: *mut JSRuntime, client_point: Point2D<f32>, pressure: f32, phase_now: TouchpadPressurePhase) { let node = match self.window.hit_test_query(client_point, false) { - Some(node_address) => node::from_untrusted_node_address(js_runtime, node_address), + Some(node_address) => unsafe { + node::from_untrusted_node_address(js_runtime, node_address) + }, None => return }; let el = match node.downcast::<Element>() { Some(el) => Root::from_ref(el), None => { let parent = node.GetParentNode(); match parent.and_then(Root::downcast::<Element>) { @@ -1084,32 +1090,33 @@ impl Document { false, false, 0i16, None); let event = mouse_event.upcast::<Event>(); event.fire(target); } + #[allow(unsafe_code)] pub fn handle_mouse_move_event(&self, js_runtime: *mut JSRuntime, client_point: Option<Point2D<f32>>, prev_mouse_over_target: &MutNullableJS<Element>) { let client_point = match client_point { None => { // If there's no point, there's no target under the mouse // FIXME: dispatch mouseout here. We have no point. prev_mouse_over_target.set(None); return; } Some(client_point) => client_point, }; let maybe_new_target = self.window.hit_test_query(client_point, true).and_then(|address| { - let node = node::from_untrusted_node_address(js_runtime, address); + let node = unsafe { node::from_untrusted_node_address(js_runtime, address) }; node.inclusive_ancestors() .filter_map(Root::downcast::<Element>) .next() }); // Send mousemove event to topmost target, and forward it if it's an iframe if let Some(ref new_target) = maybe_new_target { // If the target is an iframe, forward the event to the child document. @@ -1181,33 +1188,36 @@ impl Document { // Store the current mouse over target for next frame. prev_mouse_over_target.set(maybe_new_target.r()); self.window.reflow(ReflowGoal::ForDisplay, ReflowQueryType::NoQuery, ReflowReason::MouseEvent); } + #[allow(unsafe_code)] pub fn handle_touch_event(&self, js_runtime: *mut JSRuntime, event_type: TouchEventType, touch_id: TouchId, point: Point2D<f32>) -> TouchEventResult { let TouchId(identifier) = touch_id; let event_name = match event_type { TouchEventType::Down => "touchstart", TouchEventType::Move => "touchmove", TouchEventType::Up => "touchend", TouchEventType::Cancel => "touchcancel", }; let node = match self.window.hit_test_query(point, false) { - Some(node_address) => node::from_untrusted_node_address(js_runtime, node_address), + Some(node_address) => unsafe { + node::from_untrusted_node_address(js_runtime, node_address) + }, None => return TouchEventResult::Processed(false), }; let el = match node.downcast::<Element>() { Some(el) => Root::from_ref(el), None => { let parent = node.GetParentNode(); match parent.and_then(Root::downcast::<Element>) { Some(parent) => parent, @@ -3475,17 +3485,19 @@ impl DocumentMethods for Document { if x < 0.0 || y < 0.0 || x > viewport.width || y > viewport.height { return None; } match self.window.hit_test_query(*point, false) { Some(untrusted_node_address) => { let js_runtime = unsafe { JS_GetRuntime(window.get_cx()) }; - let node = node::from_untrusted_node_address(js_runtime, untrusted_node_address); + let node = unsafe { + node::from_untrusted_node_address(js_runtime, untrusted_node_address) + }; let parent_node = node.GetParentNode().unwrap(); let element_ref = node.downcast::<Element>().unwrap_or_else(|| { parent_node.downcast::<Element>().unwrap() }); Some(Root::from_ref(element_ref)) }, None => self.GetDocumentElement() @@ -3510,17 +3522,19 @@ impl DocumentMethods for Document { return vec!(); } let js_runtime = unsafe { JS_GetRuntime(window.get_cx()) }; // Step 1 and Step 3 let mut elements: Vec<Root<Element>> = self.nodes_from_point(point).iter() .flat_map(|&untrusted_node_address| { - let node = node::from_untrusted_node_address(js_runtime, untrusted_node_address); + let node = unsafe { + node::from_untrusted_node_address(js_runtime, untrusted_node_address) + }; Root::downcast::<Element>(node) }).collect(); // Step 4 if let Some(root_element) = self.GetDocumentElement() { if elements.last() != Some(&root_element) { elements.push(root_element); }
--- a/servo/components/script/dom/node.rs +++ b/servo/components/script/dom/node.rs @@ -922,30 +922,28 @@ fn first_node_not_in<I>(mut nodes: I, no } }) }) } /// If the given untrusted node address represents a valid DOM node in the given runtime, /// returns it. #[allow(unsafe_code)] -pub fn from_untrusted_node_address(_runtime: *mut JSRuntime, candidate: UntrustedNodeAddress) +pub unsafe fn from_untrusted_node_address(_runtime: *mut JSRuntime, candidate: UntrustedNodeAddress) -> Root<Node> { - unsafe { - // https://github.com/servo/servo/issues/6383 - let candidate: uintptr_t = mem::transmute(candidate.0); + // https://github.com/servo/servo/issues/6383 + let candidate: uintptr_t = mem::transmute(candidate.0); // let object: *mut JSObject = jsfriendapi::bindgen::JS_GetAddressableObject(runtime, // candidate); - let object: *mut JSObject = mem::transmute(candidate); - if object.is_null() { - panic!("Attempted to create a `JS<Node>` from an invalid pointer!") - } - let boxed_node = conversions::private_from_object(object) as *const Node; - Root::from_ref(&*boxed_node) + let object: *mut JSObject = mem::transmute(candidate); + if object.is_null() { + panic!("Attempted to create a `JS<Node>` from an invalid pointer!") } + let boxed_node = conversions::private_from_object(object) as *const Node; + Root::from_ref(&*boxed_node) } #[allow(unsafe_code)] pub trait LayoutNodeHelpers { unsafe fn type_id_for_layout(&self) -> NodeTypeId; unsafe fn parent_node_ref(&self) -> Option<LayoutJS<Node>>; unsafe fn first_child_ref(&self) -> Option<LayoutJS<Node>>;
--- a/servo/components/script/dom/window.rs +++ b/servo/components/script/dom/window.rs @@ -71,17 +71,17 @@ use profile_traits::time::ProfilerChan a use script_layout_interface::{TrustedNodeAddress, PendingImageState}; use script_layout_interface::message::{Msg, Reflow, ReflowQueryType, ScriptReflow}; use script_layout_interface::reporter::CSSErrorReporter; use script_layout_interface::rpc::{ContentBoxResponse, ContentBoxesResponse, LayoutRPC}; use script_layout_interface::rpc::{MarginStyleResponse, NodeScrollRootIdResponse}; use script_layout_interface::rpc::{ResolvedStyleResponse, TextIndexResponse}; use script_runtime::{CommonScriptMsg, ScriptChan, ScriptPort, ScriptThreadEventCategory}; use script_thread::{MainThreadScriptChan, MainThreadScriptMsg, Runnable, RunnableWrapper}; -use script_thread::{SendableMainThreadScriptChan, ImageCacheMsg}; +use script_thread::{SendableMainThreadScriptChan, ImageCacheMsg, ScriptThread}; use script_traits::{ConstellationControlMsg, LoadData, MozBrowserEvent, UntrustedNodeAddress}; use script_traits::{DocumentState, TimerEvent, TimerEventId}; use script_traits::{ScriptMsg as ConstellationMsg, TimerSchedulerMsg, WindowSizeData, WindowSizeType}; use script_traits::webdriver_msg::{WebDriverJSError, WebDriverJSResult}; use servo_atoms::Atom; use servo_config::opts; use servo_config::prefs::PREFS; use servo_geometry::{f32_rect_to_au_rect, max_rect}; @@ -1145,16 +1145,17 @@ impl Window { /// below). If there is no window size yet, the page is presumed invisible /// and no reflow is performed. If reflow is suppressed, no reflow will be /// performed for ForDisplay goals. /// /// TODO(pcwalton): Only wait for style recalc, since we have /// off-main-thread layout. /// /// Returns true if layout actually happened, false otherwise. + #[allow(unsafe_code)] pub fn force_reflow(&self, goal: ReflowGoal, query_type: ReflowQueryType, reason: ReflowReason) -> bool { // Check if we need to unsuppress reflow. Note that this needs to be // *before* any early bailouts, or reflow might never be unsuppresed! match reason { ReflowReason::FirstLoad | @@ -1208,45 +1209,44 @@ impl Window { query_type: query_type, dom_count: self.Document().dom_count(), }; self.layout_chan.send(Msg::Reflow(reflow)).unwrap(); debug!("script: layout forked"); - match join_port.try_recv() { + let complete = match join_port.try_recv() { Err(Empty) => { info!("script: waiting on layout"); - join_port.recv().unwrap(); + join_port.recv().unwrap() } - Ok(_) => {} + Ok(reflow_complete) => reflow_complete, Err(Disconnected) => { panic!("Layout thread failed while script was waiting for a result."); } - } + }; debug!("script: layout joined"); // Pending reflows require display, so only reset the pending reflow count if this reflow // was to be displayed. if goal == ReflowGoal::ForDisplay { self.pending_reflow_count.set(0); } if let Some(marker) = marker { self.emit_timeline_marker(marker.end()); } - let pending_images = self.layout_rpc.pending_images(); - for image in pending_images { + for image in complete.pending_images { let id = image.id; let js_runtime = self.js_runtime.borrow(); let js_runtime = js_runtime.as_ref().unwrap(); - let node = from_untrusted_node_address(js_runtime.rt(), image.node); + let node = unsafe { from_untrusted_node_address(js_runtime.rt(), image.node) }; if let PendingImageState::Unrequested(ref url) = image.state { fetch_image_for_layout(url.clone(), &*node, id, self.image_cache.clone()); } let mut images = self.pending_layout_images.borrow_mut(); let nodes = images.entry(id).or_insert(vec![]); if nodes.iter().find(|n| &***n as *const _ == &*node as *const _).is_none() { @@ -1256,16 +1256,20 @@ impl Window { ROUTER.add_route(responder_listener.to_opaque(), box move |message| { let _ = image_cache_chan.send((pipeline, message.to().unwrap())); }); self.image_cache.add_listener(id, ImageResponder::new(responder, id)); nodes.push(JS::from_ref(&*node)); } } + unsafe { + ScriptThread::note_newly_transitioning_nodes(complete.newly_transitioning_nodes); + } + true } /// Reflows the page if it's possible to do so and the page is dirty. This /// method will wait for the layout thread to complete (but see the `TODO` /// below). If there is no window size yet, the page is presumed invisible /// and no reflow is performed. /// @@ -1450,28 +1454,29 @@ impl Window { ReflowQueryType::ResolvedStyleQuery(element, pseudo, property), ReflowReason::Query) { return DOMString::new(); } let ResolvedStyleResponse(resolved) = self.layout_rpc.resolved_style(); DOMString::from(resolved) } + #[allow(unsafe_code)] pub fn offset_parent_query(&self, node: TrustedNodeAddress) -> (Option<Root<Element>>, Rect<Au>) { if !self.reflow(ReflowGoal::ForScriptQuery, ReflowQueryType::OffsetParentQuery(node), ReflowReason::Query) { return (None, Rect::zero()); } let response = self.layout_rpc.offset_parent(); let js_runtime = self.js_runtime.borrow(); let js_runtime = js_runtime.as_ref().unwrap(); let element = response.node_address.and_then(|parent_node_address| { - let node = from_untrusted_node_address(js_runtime.rt(), parent_node_address); + let node = unsafe { from_untrusted_node_address(js_runtime.rt(), parent_node_address) }; Root::downcast(node) }); (element, response.rect) } pub fn margin_style_query(&self, node: TrustedNodeAddress) -> MarginStyleResponse { if !self.reflow(ReflowGoal::ForScriptQuery, ReflowQueryType::MarginStyleQuery(node),
--- a/servo/components/script/script_thread.rs +++ b/servo/components/script/script_thread.rs @@ -42,17 +42,17 @@ use dom::bindings::trace::JSTraceable; use dom::bindings::utils::WRAP_CALLBACKS; use dom::document::{Document, DocumentSource, FocusType, HasBrowsingContext, IsHTMLDocument, TouchEventResult}; use dom::element::Element; use dom::event::{Event, EventBubbles, EventCancelable}; use dom::globalscope::GlobalScope; use dom::htmlanchorelement::HTMLAnchorElement; use dom::htmliframeelement::{HTMLIFrameElement, NavigationType}; use dom::mutationobserver::MutationObserver; -use dom::node::{Node, NodeDamage, window_from_node}; +use dom::node::{Node, NodeDamage, window_from_node, from_untrusted_node_address}; use dom::serviceworker::TrustedServiceWorkerAddress; use dom::serviceworkerregistration::ServiceWorkerRegistration; use dom::servoparser::{ParserContext, ServoParser}; use dom::transitionevent::TransitionEvent; use dom::uievent::UIEvent; use dom::window::{ReflowReason, Window}; use dom::windowproxy::WindowProxy; use dom::worker::TrustedWorkerAddress; @@ -64,17 +64,16 @@ use hyper::mime::{Mime, SubLevel, TopLev use hyper_serde::Serde; use ipc_channel::ipc::{self, IpcSender}; use ipc_channel::router::ROUTER; use js::glue::GetWindowProxyClass; use js::jsapi::{JSAutoCompartment, JSContext, JS_SetWrapObjectCallbacks}; use js::jsapi::{JSTracer, SetWindowProxyClass}; use js::jsval::UndefinedValue; use js::rust::Runtime; -use layout_wrapper::ServoLayoutNode; use mem::heap_size_of_self_and_children; use microtask::{MicrotaskQueue, Microtask}; use msg::constellation_msg::{FrameId, FrameType, PipelineId, PipelineNamespace}; use net_traits::{CoreResourceMsg, FetchMetadata, FetchResponseListener}; use net_traits::{IpcSend, Metadata, ReferrerPolicy, ResourceThreads}; use net_traits::image_cache::{ImageCache, PendingImageResponse}; use net_traits::request::{CredentialsMode, Destination, RequestInit}; use net_traits::storage_thread::StorageType; @@ -104,17 +103,16 @@ use std::option::Option; use std::ptr; use std::rc::Rc; use std::result::Result; use std::sync::{Arc, Mutex}; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::mpsc::{Receiver, Select, Sender, channel}; use std::thread; use style::context::ReflowGoal; -use style::dom::{TNode, UnsafeNode}; use style::thread_state; use task_source::dom_manipulation::{DOMManipulationTask, DOMManipulationTaskSource}; use task_source::file_reading::FileReadingTaskSource; use task_source::history_traversal::HistoryTraversalTaskSource; use task_source::networking::NetworkingTaskSource; use task_source::user_interaction::{UserInteractionTask, UserInteractionTaskSource}; use time::Tm; use url::Position; @@ -485,16 +483,20 @@ pub struct ScriptThread { mutation_observers: DOMRefCell<Vec<JS<MutationObserver>>>, /// A handle to the webvr thread, if available webvr_thread: Option<IpcSender<WebVRMsg>>, /// A list of pipelines containing documents that finished loading all their blocking /// resources during a turn of the event loop. docs_with_no_blocking_loads: DOMRefCell<HashSet<JS<Document>>>, + + /// A list of nodes with in-progress CSS transitions, which roots them for the duration + /// of the transition. + transitioning_nodes: DOMRefCell<Vec<JS<Node>>>, } /// In the event of thread panic, all data on the stack runs its destructor. However, there /// are no reachable, owning pointers to the DOM memory, so it never gets freed by default /// when the script thread fails. The ScriptMemoryFailsafe uses the destructor bomb pattern /// to forcibly tear down the JS compartments for pages associated with the failing ScriptThread. struct ScriptMemoryFailsafe<'a> { owner: Option<&'a ScriptThread>, @@ -569,16 +571,27 @@ impl ScriptThreadFactory for ScriptThrea failsafe.neuter(); }).expect("Thread spawning failed"); (sender, receiver) } } impl ScriptThread { + pub unsafe fn note_newly_transitioning_nodes(nodes: Vec<UntrustedNodeAddress>) { + SCRIPT_THREAD_ROOT.with(|root| { + let script_thread = &*root.get().unwrap(); + let js_runtime = script_thread.js_runtime.rt(); + let new_nodes = nodes + .into_iter() + .map(|n| JS::from_ref(&*from_untrusted_node_address(js_runtime, n))); + script_thread.transitioning_nodes.borrow_mut().extend(new_nodes); + }) + } + pub fn add_mutation_observer(observer: &MutationObserver) { SCRIPT_THREAD_ROOT.with(|root| { let script_thread = unsafe { &*root.get().unwrap() }; script_thread.mutation_observers .borrow_mut() .push(JS::from_ref(observer)); }) } @@ -737,16 +750,18 @@ impl ScriptThread { mutation_observers: Default::default(), layout_to_constellation_chan: state.layout_to_constellation_chan, webvr_thread: state.webvr_thread, docs_with_no_blocking_loads: Default::default(), + + transitioning_nodes: Default::default(), } } pub fn get_cx(&self) -> *mut JSContext { self.js_runtime.cx() } /// Starts the script thread. After calling this method, the script thread will loop receiving @@ -1597,21 +1612,39 @@ impl ScriptThread { let document = match { self.documents.borrow().find_document(id) } { Some(document) => document, None => return warn!("Message sent to closed pipeline {}.", id), }; document.run_the_animation_frame_callbacks(); } /// Handles firing of transition events. - #[allow(unsafe_code)] - fn handle_transition_event(&self, unsafe_node: UnsafeNode, name: String, duration: f64) { - let node = unsafe { ServoLayoutNode::from_unsafe(&unsafe_node) }; - let node = unsafe { node.get_jsmanaged().get_for_script() }; - let window = window_from_node(node); + fn handle_transition_event(&self, unsafe_node: UntrustedNodeAddress, name: String, duration: f64) { + let js_runtime = self.js_runtime.rt(); + let node = unsafe { + from_untrusted_node_address(js_runtime, unsafe_node) + }; + + let idx = self.transitioning_nodes + .borrow() + .iter() + .position(|n| &**n as *const _ == &*node as *const _); + match idx { + Some(idx) => { + self.transitioning_nodes.borrow_mut().remove(idx); + } + None => { + // If no index is found, we can't know whether this node is safe to use. + // It's better not to fire a DOM event than crash. + warn!("Ignoring transition end notification for unknown node."); + return; + } + } + + let window = window_from_node(&*node); // Not quite the right thing - see #13865. node.dirty(NodeDamage::NodeStyleDamaged); if let Some(el) = node.downcast::<Element>() { if &*window.GetComputedStyle(el, None).Display() == "none" { return; }
--- a/servo/components/script_layout_interface/message.rs +++ b/servo/components/script_layout_interface/message.rs @@ -1,23 +1,23 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use {OpaqueStyleAndLayoutData, TrustedNodeAddress}; +use {OpaqueStyleAndLayoutData, TrustedNodeAddress, PendingImage}; use app_units::Au; use euclid::point::Point2D; use euclid::rect::Rect; use gfx_traits::Epoch; use ipc_channel::ipc::{IpcReceiver, IpcSender}; use msg::constellation_msg::PipelineId; use net_traits::image_cache::ImageCache; use profile_traits::mem::ReportsChan; use rpc::LayoutRPC; -use script_traits::{ConstellationControlMsg, LayoutControlMsg}; +use script_traits::{ConstellationControlMsg, LayoutControlMsg, UntrustedNodeAddress}; use script_traits::{LayoutMsg as ConstellationMsg, StackingContextScrollState, WindowSizeData}; use servo_url::ServoUrl; use std::sync::Arc; use std::sync::mpsc::{Receiver, Sender}; use style::context::{QuirksMode, ReflowGoal}; use style::properties::PropertyId; use style::selector_parser::PseudoElement; use style::stylesheets::Stylesheet; @@ -104,42 +104,45 @@ pub enum ReflowQueryType { /// Information needed for a reflow. pub struct Reflow { /// The goal of reflow: either to render to the screen or to flush layout info for script. pub goal: ReflowGoal, /// A clipping rectangle for the page, an enlarged rectangle containing the viewport. pub page_clip_rect: Rect<Au>, } +/// Information derived from a layout pass that needs to be returned to the script thread. +#[derive(Default)] +pub struct ReflowComplete { + /// The list of images that were encountered that are in progress. + pub pending_images: Vec<PendingImage>, + /// The list of nodes that initiated a CSS transition. + pub newly_transitioning_nodes: Vec<UntrustedNodeAddress>, +} + /// Information needed for a script-initiated reflow. pub struct ScriptReflow { /// General reflow data. pub reflow_info: Reflow, /// The document node. pub document: TrustedNodeAddress, /// The document's list of stylesheets. pub document_stylesheets: Vec<::style::stylearc::Arc<Stylesheet>>, /// Whether the document's stylesheets have changed since the last script reflow. pub stylesheets_changed: bool, /// The current window size. pub window_size: WindowSizeData, /// The channel that we send a notification to. - pub script_join_chan: Sender<()>, + pub script_join_chan: Sender<ReflowComplete>, /// The type of query if any to perform during this reflow. pub query_type: ReflowQueryType, /// The number of objects in the dom #10110 pub dom_count: u32, } -impl Drop for ScriptReflow { - fn drop(&mut self) { - self.script_join_chan.send(()).unwrap(); - } -} - pub struct NewLayoutThreadInfo { pub id: PipelineId, pub url: ServoUrl, pub is_parent: bool, pub layout_pair: (Sender<Msg>, Receiver<Msg>), pub pipeline_port: IpcReceiver<LayoutControlMsg>, pub constellation_chan: IpcSender<ConstellationMsg>, pub script_chan: IpcSender<ConstellationControlMsg>,
--- a/servo/components/script_layout_interface/rpc.rs +++ b/servo/components/script_layout_interface/rpc.rs @@ -1,13 +1,12 @@ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -use PendingImage; use app_units::Au; use euclid::point::Point2D; use euclid::rect::Rect; use script_traits::UntrustedNodeAddress; use style::properties::longhands::{margin_top, margin_right, margin_bottom, margin_left, overflow_x}; use webrender_traits::ClipId; /// Synchronous messages that script can send to layout. @@ -33,18 +32,16 @@ pub trait LayoutRPC { fn node_scroll_root_id(&self) -> NodeScrollRootIdResponse; /// Requests the node containing the point of interest fn hit_test(&self) -> HitTestResponse; /// Query layout for the resolved value of a given CSS property fn resolved_style(&self) -> ResolvedStyleResponse; fn offset_parent(&self) -> OffsetParentResponse; /// Query layout for the resolve values of the margin properties for an element. fn margin_style(&self) -> MarginStyleResponse; - /// Requests the list of not-yet-loaded images that were encountered in the last reflow. - fn pending_images(&self) -> Vec<PendingImage>; /// Requests the list of nodes from the given point. fn nodes_from_point_response(&self) -> Vec<UntrustedNodeAddress>; fn text_index(&self) -> TextIndexResponse; } pub struct ContentBoxResponse(pub Option<Rect<Au>>);
--- a/servo/components/script_traits/lib.rs +++ b/servo/components/script_traits/lib.rs @@ -64,17 +64,17 @@ use profile_traits::mem; use profile_traits::time as profile_time; use serde::{Deserialize, Deserializer, Serialize, Serializer}; use servo_url::ImmutableOrigin; use servo_url::ServoUrl; use std::collections::HashMap; use std::fmt; use std::sync::Arc; use std::sync::mpsc::{Receiver, Sender}; -use style_traits::{CSSPixel, UnsafeNode}; +use style_traits::CSSPixel; use webdriver_msg::{LoadStatus, WebDriverScriptCommand}; use webrender_traits::ClipId; use webvr_traits::{WebVREvent, WebVRMsg}; pub use script_msg::{LayoutMsg, ScriptMsg, EventResult, LogEntry}; pub use script_msg::{ServiceWorkerMsg, ScopeThings, SWManagerMsg, SWManagerSenders, DOMMessage}; /// The address of a node. Layout sends these back. They must be validated via @@ -269,17 +269,17 @@ pub enum ConstellationControlMsg { /// Set an iframe to be focused. Used when an element in an iframe gains focus. /// PipelineId is for the parent, FrameId is for the actual frame. FocusIFrame(PipelineId, FrameId), /// Passes a webdriver command to the script thread for execution WebDriverScriptCommand(PipelineId, WebDriverScriptCommand), /// Notifies script thread that all animations are done TickAllAnimations(PipelineId), /// Notifies the script thread of a transition end - TransitionEnd(UnsafeNode, String, f64), + TransitionEnd(UntrustedNodeAddress, String, f64), /// Notifies the script thread that a new Web font has been loaded, and thus the page should be /// reflowed. WebFontLoaded(PipelineId), /// Cause a `load` event to be dispatched at the appropriate frame element. DispatchFrameLoadEvent { /// The frame that has been marked as loaded. target: FrameId, /// The pipeline that contains a frame loading the target pipeline.
--- a/servo/components/style/animation.rs +++ b/servo/components/style/animation.rs @@ -3,17 +3,17 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ //! CSS transitions and animations. #![deny(missing_docs)] use Atom; use bezier::Bezier; use context::SharedStyleContext; -use dom::{OpaqueNode, UnsafeNode}; +use dom::OpaqueNode; use euclid::point::Point2D; use font_metrics::FontMetricsProvider; use keyframes::{KeyframesStep, KeyframesStepValue}; use properties::{self, CascadeFlags, ComputedValues, Importance}; use properties::animated_properties::{AnimatedProperty, TransitionProperty}; use properties::longhands::animation_direction::computed_value::single_value::T as AnimationDirection; use properties::longhands::animation_iteration_count::single_value::computed_value::T as AnimationIterationCount; use properties::longhands::animation_play_state::computed_value::single_value::T as AnimationPlayState; @@ -183,59 +183,68 @@ impl KeyframesAnimationState { /// State relating to an animation. #[derive(Clone, Debug)] pub enum Animation { /// A transition is just a single frame triggered at a time, with a reflow. /// /// the f64 field is the start time as returned by `time::precise_time_s()`. /// /// The `bool` field is werther this animation should no longer run. - Transition(OpaqueNode, UnsafeNode, f64, AnimationFrame, bool), + Transition(OpaqueNode, f64, AnimationFrame, bool), /// A keyframes animation is identified by a name, and can have a /// node-dependent state (i.e. iteration count, etc.). Keyframes(OpaqueNode, Atom, KeyframesAnimationState), } impl Animation { /// Mark this animation as expired. #[inline] pub fn mark_as_expired(&mut self) { debug_assert!(!self.is_expired()); match *self { - Animation::Transition(_, _, _, _, ref mut expired) => *expired = true, + Animation::Transition(_, _, _, ref mut expired) => *expired = true, Animation::Keyframes(_, _, ref mut state) => state.expired = true, } } /// Whether this animation is expired. #[inline] pub fn is_expired(&self) -> bool { match *self { - Animation::Transition(_, _, _, _, expired) => expired, + Animation::Transition(_, _, _, expired) => expired, Animation::Keyframes(_, _, ref state) => state.expired, } } /// The opaque node that owns the animation. #[inline] pub fn node(&self) -> &OpaqueNode { match *self { - Animation::Transition(ref node, _, _, _, _) => node, + Animation::Transition(ref node, _, _, _) => node, Animation::Keyframes(ref node, _, _) => node, } } /// Whether this animation is paused. A transition can never be paused. #[inline] pub fn is_paused(&self) -> bool { match *self { Animation::Transition(..) => false, Animation::Keyframes(_, _, ref state) => state.is_paused(), } } + + /// Whether this animation is a transition. + #[inline] + pub fn is_transition(&self) -> bool { + match *self { + Animation::Transition(..) => true, + Animation::Keyframes(..) => false, + } + } } /// A single animation frame of a single property. #[derive(Debug, Clone)] pub struct AnimationFrame { /// A description of the property animation that is occurring. pub property_animation: PropertyAnimation, @@ -397,17 +406,16 @@ impl PropertyAnimation { /// the given style difference. This is called from the layout worker threads. /// Returns true if any animations were kicked off and false otherwise. // // TODO(emilio): Take rid of this mutex splitting SharedLayoutContex into a // cloneable part and a non-cloneable part.. #[cfg(feature = "servo")] pub fn start_transitions_if_applicable(new_animations_sender: &Sender<Animation>, opaque_node: OpaqueNode, - unsafe_node: UnsafeNode, old_style: &ComputedValues, new_style: &mut Arc<ComputedValues>, timer: &Timer, possibly_expired_animations: &[PropertyAnimation]) -> bool { let mut had_animations = false; for i in 0..new_style.get_box().transition_property_count() { // Create any property animations, if applicable. @@ -431,17 +439,17 @@ pub fn start_transitions_if_applicable(n property_animation.update(Arc::get_mut(new_style).unwrap(), 0.0); // Kick off the animation. let box_style = new_style.get_box(); let now = timer.seconds(); let start_time = now + (box_style.transition_delay_mod(i).seconds() as f64); new_animations_sender - .send(Animation::Transition(opaque_node, unsafe_node, start_time, AnimationFrame { + .send(Animation::Transition(opaque_node, start_time, AnimationFrame { duration: box_style.transition_duration_mod(i).seconds() as f64, property_animation: property_animation, }, /* is_expired = */ false)).unwrap(); had_animations = true; } } @@ -584,17 +592,17 @@ pub fn update_style_for_animation_frame( pub fn update_style_for_animation(context: &SharedStyleContext, animation: &Animation, style: &mut Arc<ComputedValues>, font_metrics_provider: &FontMetricsProvider) { debug!("update_style_for_animation: entering"); debug_assert!(!animation.is_expired()); match *animation { - Animation::Transition(_, _, start_time, ref frame, _) => { + Animation::Transition(_, start_time, ref frame, _) => { debug!("update_style_for_animation: transition found"); let now = context.timer.seconds(); let mut new_style = (*style).clone(); let updated_style = update_style_for_animation_frame(&mut new_style, now, start_time, frame); if updated_style { *style = new_style @@ -762,17 +770,17 @@ pub fn complete_expired_transitions(node let had_animations_to_expire; { let all_expired_animations = context.expired_animations.read(); let animations_to_expire = all_expired_animations.get(&node); had_animations_to_expire = animations_to_expire.is_some(); if let Some(ref animations) = animations_to_expire { for animation in *animations { // TODO: support animation-fill-mode - if let Animation::Transition(_, _, _, ref frame, _) = *animation { + if let Animation::Transition(_, _, ref frame, _) = *animation { frame.property_animation.update(Arc::make_mut(style), 1.0); } } } } if had_animations_to_expire { context.expired_animations.write().remove(&node);
--- a/servo/components/style/matching.rs +++ b/servo/components/style/matching.rs @@ -740,17 +740,16 @@ trait PrivateMatchMethods: TElement { this_opaque, &new_values); // Trigger transitions if necessary. This will reset `new_values` back // to its old value if it did trigger a transition. if let Some(ref values) = *old_values { animation::start_transitions_if_applicable( new_animations_sender, this_opaque, - self.as_node().to_unsafe(), &**values, new_values, &shared_context.timer, &possibly_expired_animations); } } /// Computes and applies non-redundant damage. @@ -838,17 +837,17 @@ trait PrivateMatchMethods: TElement { // // See #12171 and the associated PR for an example where this // happened while debugging other release panic. if !running_animation.is_expired() { animation::update_style_for_animation(context, running_animation, style, font_metrics); - if let Animation::Transition(_, _, _, ref frame, _) = *running_animation { + if let Animation::Transition(_, _, ref frame, _) = *running_animation { possibly_expired_animations.push(frame.property_animation.clone()) } } } } } fn share_style_with_candidate_if_possible(&self,