mirror of
https://github.com/servo/servo.git
synced 2025-08-03 20:50:07 +01:00
Have Animations struct handle rooting nodes
Instead of having `ScriptThread` handle rooting nodes, do this in `Animations`. This makes it easier to know when it is appropriate to root and unroot nodes instead of relying on a certain order of events. This also allows reducing quite a bit the amount of unsafe code.
This commit is contained in:
parent
7170a69695
commit
47642e0eee
5 changed files with 278 additions and 339 deletions
|
@ -17,26 +17,18 @@
|
|||
//! a page runs its course and the script thread returns to processing events in the main event
|
||||
//! loop.
|
||||
|
||||
use crate::animations::{
|
||||
AnimationsUpdate, TransitionOrAnimationEvent, TransitionOrAnimationEventType,
|
||||
};
|
||||
use crate::devtools;
|
||||
use crate::document_loader::DocumentLoader;
|
||||
use crate::dom::animationevent::AnimationEvent;
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::codegen::Bindings::AnimationEventBinding::AnimationEventInit;
|
||||
use crate::dom::bindings::codegen::Bindings::DocumentBinding::{
|
||||
DocumentMethods, DocumentReadyState,
|
||||
};
|
||||
use crate::dom::bindings::codegen::Bindings::EventBinding::EventInit;
|
||||
use crate::dom::bindings::codegen::Bindings::NavigatorBinding::NavigatorMethods;
|
||||
use crate::dom::bindings::codegen::Bindings::TransitionEventBinding::TransitionEventInit;
|
||||
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
|
||||
use crate::dom::bindings::conversions::{
|
||||
ConversionResult, FromJSValConvertible, StringificationBehavior,
|
||||
};
|
||||
use crate::dom::bindings::inheritance::Castable;
|
||||
use crate::dom::bindings::num::Finite;
|
||||
use crate::dom::bindings::refcounted::Trusted;
|
||||
use crate::dom::bindings::reflector::DomObject;
|
||||
use crate::dom::bindings::root::ThreadLocalStackRoots;
|
||||
|
@ -57,14 +49,11 @@ use crate::dom::htmlanchorelement::HTMLAnchorElement;
|
|||
use crate::dom::htmliframeelement::{HTMLIFrameElement, NavigationType};
|
||||
use crate::dom::identityhub::Identities;
|
||||
use crate::dom::mutationobserver::MutationObserver;
|
||||
use crate::dom::node::{
|
||||
from_untrusted_node_address, window_from_node, Node, NodeDamage, ShadowIncluding,
|
||||
};
|
||||
use crate::dom::node::{window_from_node, Node, ShadowIncluding};
|
||||
use crate::dom::performanceentry::PerformanceEntry;
|
||||
use crate::dom::performancepainttiming::PerformancePaintTiming;
|
||||
use crate::dom::serviceworker::TrustedServiceWorkerAddress;
|
||||
use crate::dom::servoparser::{ParserContext, ServoParser};
|
||||
use crate::dom::transitionevent::TransitionEvent;
|
||||
use crate::dom::uievent::UIEvent;
|
||||
use crate::dom::window::{ReflowReason, Window};
|
||||
use crate::dom::windowproxy::{CreatorBrowsingContextInfo, WindowProxy};
|
||||
|
@ -636,13 +625,6 @@ pub struct ScriptThread {
|
|||
/// resources during a turn of the event loop.
|
||||
docs_with_no_blocking_loads: DomRefCell<HashSet<Dom<Document>>>,
|
||||
|
||||
/// A list of nodes with in-progress CSS transitions, which roots them for the duration
|
||||
/// of the transition.
|
||||
animating_nodes: DomRefCell<HashMap<PipelineId, Vec<Dom<Node>>>>,
|
||||
|
||||
/// Animations events that are pending to be sent.
|
||||
animation_events: RefCell<Vec<TransitionOrAnimationEvent>>,
|
||||
|
||||
/// <https://html.spec.whatwg.org/multipage/#custom-element-reactions-stack>
|
||||
custom_element_reaction_stack: CustomElementReactionStack,
|
||||
|
||||
|
@ -831,40 +813,6 @@ impl ScriptThread {
|
|||
})
|
||||
}
|
||||
|
||||
/// Consume the list of pointer addresses corresponding to DOM nodes that are animating
|
||||
/// and root them in a per-pipeline list of nodes.
|
||||
///
|
||||
/// Unsafety: any pointer to invalid memory (ie. a GCed node) will trigger a crash.
|
||||
/// TODO: ensure caller uses rooted nodes instead of unsafe node addresses.
|
||||
pub(crate) unsafe fn process_animations_update(mut update: AnimationsUpdate) {
|
||||
if update.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
SCRIPT_THREAD_ROOT.with(|root| {
|
||||
let script_thread = &*root.get().unwrap();
|
||||
|
||||
if !update.events.is_empty() {
|
||||
script_thread
|
||||
.animation_events
|
||||
.borrow_mut()
|
||||
.append(&mut update.events);
|
||||
}
|
||||
|
||||
let js_runtime = script_thread.js_runtime.rt();
|
||||
let new_nodes = update
|
||||
.newly_animating_nodes
|
||||
.into_iter()
|
||||
.map(|n| Dom::from_ref(&*from_untrusted_node_address(js_runtime, n)));
|
||||
script_thread
|
||||
.animating_nodes
|
||||
.borrow_mut()
|
||||
.entry(update.pipeline_id)
|
||||
.or_insert_with(Vec::new)
|
||||
.extend(new_nodes);
|
||||
})
|
||||
}
|
||||
|
||||
pub fn set_mutation_observer_microtask_queued(value: bool) {
|
||||
SCRIPT_THREAD_ROOT.with(|root| {
|
||||
let script_thread = unsafe { &*root.get().unwrap() };
|
||||
|
@ -1363,9 +1311,6 @@ impl ScriptThread {
|
|||
|
||||
docs_with_no_blocking_loads: Default::default(),
|
||||
|
||||
animating_nodes: Default::default(),
|
||||
animation_events: Default::default(),
|
||||
|
||||
custom_element_reaction_stack: CustomElementReactionStack::new(),
|
||||
|
||||
webrender_document: state.webrender_document,
|
||||
|
@ -1644,19 +1589,13 @@ impl ScriptThread {
|
|||
}
|
||||
|
||||
// Perform step 11.10 from https://html.spec.whatwg.org/multipage/#event-loops.
|
||||
// TODO(mrobinson): This should also update the current animations to conform to
|
||||
// the HTML specification.
|
||||
fn update_animations_and_send_events(&self) {
|
||||
// We remove the events because handling these events might trigger
|
||||
// a reflow which might want to add more events to the queue.
|
||||
let events = self.animation_events.replace(Vec::new());
|
||||
for event in events.into_iter() {
|
||||
self.handle_transition_or_animation_event(&event);
|
||||
for (_, document) in self.documents.borrow().iter() {
|
||||
document.animations().send_pending_events();
|
||||
}
|
||||
|
||||
for (_, document) in self.documents.borrow().iter() {
|
||||
let update = document.update_animation_timeline();
|
||||
unsafe { ScriptThread::process_animations_update(update) };
|
||||
document.update_animation_timeline();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2859,11 +2798,11 @@ impl ScriptThread {
|
|||
.send((id, ScriptMsg::PipelineExited))
|
||||
.ok();
|
||||
|
||||
// Remove any rooted nodes for active animations and transitions.
|
||||
self.animating_nodes.borrow_mut().remove(&id);
|
||||
|
||||
// Now that layout is shut down, it's OK to remove the document.
|
||||
if let Some(document) = document {
|
||||
// Clear any active animations and unroot all of the associated DOM objects.
|
||||
document.animations().clear();
|
||||
|
||||
// We don't want to dispatch `mouseout` event pointing to non-existing element
|
||||
if let Some(target) = self.topmost_mouse_over_target.get() {
|
||||
if target.upcast::<Node>().owner_doc() == document {
|
||||
|
@ -2939,99 +2878,11 @@ impl ScriptThread {
|
|||
document.run_the_animation_frame_callbacks();
|
||||
}
|
||||
if tick_type.contains(AnimationTickType::CSS_ANIMATIONS_AND_TRANSITIONS) {
|
||||
match self.animating_nodes.borrow().get(&id) {
|
||||
Some(nodes) => {
|
||||
for node in nodes.iter() {
|
||||
node.dirty(NodeDamage::NodeStyleDamaged);
|
||||
}
|
||||
},
|
||||
None => return,
|
||||
}
|
||||
|
||||
document.animations().mark_animating_nodes_as_dirty();
|
||||
document.window().add_pending_reflow();
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles firing of transition-related events.
|
||||
///
|
||||
/// TODO(mrobinson): Add support for more events.
|
||||
fn handle_transition_or_animation_event(&self, event: &TransitionOrAnimationEvent) {
|
||||
// We limit the scope of the borrow here so that we aren't holding it when
|
||||
// sending events. Event handlers may trigger another layout, resulting in
|
||||
// a double mutable borrow of `animating_nodes`.
|
||||
let node = {
|
||||
let mut animating_nodes = self.animating_nodes.borrow_mut();
|
||||
let nodes = match animating_nodes.get_mut(&event.pipeline_id) {
|
||||
Some(nodes) => nodes,
|
||||
None => {
|
||||
return warn!(
|
||||
"Ignoring transition event for pipeline without animating nodes."
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
let node_index = nodes
|
||||
.iter()
|
||||
.position(|n| n.to_untrusted_node_address() == event.node);
|
||||
let node_index = match node_index {
|
||||
Some(node_index) => node_index,
|
||||
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 event for unknown node.");
|
||||
return;
|
||||
},
|
||||
};
|
||||
|
||||
// We need to root the node now, because if we remove it from the map
|
||||
// a garbage collection might clean it up while we are sending events.
|
||||
let node = DomRoot::from_ref(&*nodes[node_index]);
|
||||
if event.event_type.finalizes_transition_or_animation() {
|
||||
nodes.remove(node_index);
|
||||
}
|
||||
node
|
||||
};
|
||||
|
||||
let event_atom = match event.event_type {
|
||||
TransitionOrAnimationEventType::AnimationEnd => atom!("animationend"),
|
||||
TransitionOrAnimationEventType::AnimationIteration => atom!("animationiteration"),
|
||||
TransitionOrAnimationEventType::TransitionCancel => atom!("transitioncancel"),
|
||||
TransitionOrAnimationEventType::TransitionEnd => atom!("transitionend"),
|
||||
TransitionOrAnimationEventType::TransitionRun => atom!("transitionrun"),
|
||||
};
|
||||
let parent = EventInit {
|
||||
bubbles: true,
|
||||
cancelable: false,
|
||||
};
|
||||
|
||||
// TODO: Handle pseudo-elements properly
|
||||
let property_or_animation_name = DOMString::from(event.property_or_animation_name.clone());
|
||||
let elapsed_time = Finite::new(event.elapsed_time as f32).unwrap();
|
||||
let window = window_from_node(&*node);
|
||||
|
||||
if event.event_type.is_transition_event() {
|
||||
let event_init = TransitionEventInit {
|
||||
parent,
|
||||
propertyName: property_or_animation_name,
|
||||
elapsedTime: elapsed_time,
|
||||
pseudoElement: DOMString::new(),
|
||||
};
|
||||
TransitionEvent::new(&window, event_atom, &event_init)
|
||||
.upcast::<Event>()
|
||||
.fire(node.upcast());
|
||||
} else {
|
||||
let event_init = AnimationEventInit {
|
||||
parent,
|
||||
animationName: property_or_animation_name,
|
||||
elapsedTime: elapsed_time,
|
||||
pseudoElement: DOMString::new(),
|
||||
};
|
||||
AnimationEvent::new(&window, event_atom, &event_init)
|
||||
.upcast::<Event>()
|
||||
.fire(node.upcast());
|
||||
}
|
||||
}
|
||||
|
||||
/// Handles a Web font being loaded. Does nothing if the page no longer exists.
|
||||
fn handle_web_font_loaded(&self, pipeline_id: PipelineId) {
|
||||
let document = self.documents.borrow().find_document(pipeline_id);
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue