aboutsummaryrefslogtreecommitdiffstats
path: root/components/script
diff options
context:
space:
mode:
Diffstat (limited to 'components/script')
-rw-r--r--components/script/animation_timeline.rs49
-rw-r--r--components/script/dom/document.rs21
-rw-r--r--components/script/dom/window.rs53
-rw-r--r--components/script/lib.rs1
-rw-r--r--components/script/script_thread.rs96
5 files changed, 122 insertions, 98 deletions
diff --git a/components/script/animation_timeline.rs b/components/script/animation_timeline.rs
new file mode 100644
index 00000000000..e0ad520db61
--- /dev/null
+++ b/components/script/animation_timeline.rs
@@ -0,0 +1,49 @@
+/* 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 https://mozilla.org/MPL/2.0/. */
+
+#![deny(missing_docs)]
+
+//! A timeline module, used to specify an `AnimationTimeline` which determines
+//! the time used for synchronizing animations in the script thread.
+
+use time;
+
+/// A `AnimationTimeline` which is used to synchronize animations during the script
+/// event loop.
+#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf)]
+pub struct AnimationTimeline {
+ current_value: f64,
+}
+
+impl AnimationTimeline {
+ /// Creates a new "normal" timeline, i.e., a "Current" mode timer.
+ #[inline]
+ pub fn new() -> Self {
+ Self {
+ current_value: time::precise_time_s(),
+ }
+ }
+
+ /// Creates a new "test mode" timeline, with initial time 0.
+ #[inline]
+ pub fn new_for_testing() -> Self {
+ Self { current_value: 0. }
+ }
+
+ /// Returns the current value of the timeline in seconds.
+ pub fn current_value(&self) -> f64 {
+ self.current_value
+ }
+
+ /// Updates the value of the `AnimationTimeline` to the current clock time.
+ pub fn update(&mut self) {
+ self.current_value = time::precise_time_s();
+ }
+
+ /// Increments the current value of the timeline by a specific number of seconds.
+ /// This is used for testing.
+ pub fn advance_specific(&mut self, by: f64) {
+ self.current_value += by;
+ }
+}
diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs
index eacd6a11745..5e40f433da5 100644
--- a/components/script/dom/document.rs
+++ b/components/script/dom/document.rs
@@ -2,6 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+use crate::animation_timeline::AnimationTimeline;
use crate::document_loader::{DocumentLoader, LoadType};
use crate::dom::attr::Attr;
use crate::dom::beforeunloadevent::BeforeUnloadEvent;
@@ -380,6 +381,9 @@ pub struct Document {
csp_list: DomRefCell<Option<CspList>>,
/// https://w3c.github.io/slection-api/#dfn-selection
selection: MutNullableDom<Selection>,
+ /// A timeline for animations which is used for synchronizing animations.
+ /// https://drafts.csswg.org/web-animations/#timeline
+ animation_timeline: DomRefCell<AnimationTimeline>,
}
#[derive(JSTraceable, MallocSizeOf)]
@@ -2904,6 +2908,11 @@ impl Document {
dirty_webgl_contexts: DomRefCell::new(HashMap::new()),
csp_list: DomRefCell::new(None),
selection: MutNullableDom::new(None),
+ animation_timeline: if pref!(layout.animations.test.enabled) {
+ DomRefCell::new(AnimationTimeline::new_for_testing())
+ } else {
+ DomRefCell::new(AnimationTimeline::new())
+ },
}
}
@@ -3605,6 +3614,18 @@ impl Document {
})
.collect()
}
+
+ pub fn advance_animation_timeline_for_testing(&self, delta: f64) {
+ self.animation_timeline.borrow_mut().advance_specific(delta);
+ }
+
+ pub fn update_animation_timeline(&self) {
+ self.animation_timeline.borrow_mut().update();
+ }
+
+ pub fn current_animation_timeline_value(&self) -> f64 {
+ self.animation_timeline.borrow().current_value()
+ }
}
impl Element {
diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs
index 481586a71ae..414ef0f2e45 100644
--- a/components/script/dom/window.rs
+++ b/components/script/dom/window.rs
@@ -173,8 +173,7 @@ pub enum ReflowReason {
IFrameLoadEvent,
MissingExplicitReflow,
ElementStateChanged,
- TickAnimations,
- AdvanceClock(i32),
+ PendingReflow,
}
#[dom_struct]
@@ -1550,12 +1549,9 @@ impl Window {
/// layout animation clock.
pub fn advance_animation_clock(&self, delta: i32) {
let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
- ScriptThread::restyle_animating_nodes_for_advancing_clock(&pipeline_id);
- self.force_reflow(
- ReflowGoal::TickAnimations,
- ReflowReason::AdvanceClock(delta),
- None,
- );
+ self.Document()
+ .advance_animation_timeline_for_testing(delta as f64 / 1000.);
+ ScriptThread::handle_tick_all_animations_for_testing(pipeline_id);
}
/// Reflows the page unconditionally if possible and not suppressed. This
@@ -1632,11 +1628,6 @@ impl Window {
document.flush_dirty_canvases();
}
- let advance_clock_delta = match reason {
- ReflowReason::AdvanceClock(delta) => Some(delta),
- _ => None,
- };
-
// Send new document and relevant styles to layout.
let needs_display = reflow_goal.needs_display();
let reflow = ScriptReflow {
@@ -1651,7 +1642,7 @@ impl Window {
script_join_chan: join_chan,
dom_count: document.dom_count(),
pending_restyles: document.drain_pending_restyles(),
- advance_clock_delta,
+ animation_timeline_value: document.current_animation_timeline_value(),
};
self.layout_chan
@@ -2453,8 +2444,7 @@ fn should_move_clip_rect(clip_rect: UntypedRect<Au>, new_viewport: UntypedRect<f
}
fn debug_reflow_events(id: PipelineId, reflow_goal: &ReflowGoal, reason: &ReflowReason) {
- let mut debug_msg = format!("**** pipeline={}", id);
- debug_msg.push_str(match *reflow_goal {
+ let goal_string = match *reflow_goal {
ReflowGoal::Full => "\tFull",
ReflowGoal::TickAnimations => "\tTickAnimations",
ReflowGoal::LayoutQuery(ref query_msg, _) => match query_msg {
@@ -2471,34 +2461,9 @@ fn debug_reflow_events(id: PipelineId, reflow_goal: &ReflowGoal, reason: &Reflow
&QueryMsg::ElementInnerTextQuery(_) => "\tElementInnerTextQuery",
&QueryMsg::InnerWindowDimensionsQuery(_) => "\tInnerWindowDimensionsQuery",
},
- });
-
- debug_msg.push_str(match *reason {
- ReflowReason::CachedPageNeededReflow => "\tCachedPageNeededReflow",
- ReflowReason::RefreshTick => "\tRefreshTick",
- ReflowReason::FirstLoad => "\tFirstLoad",
- ReflowReason::KeyEvent => "\tKeyEvent",
- ReflowReason::MouseEvent => "\tMouseEvent",
- ReflowReason::Query => "\tQuery",
- ReflowReason::Timer => "\tTimer",
- ReflowReason::Viewport => "\tViewport",
- ReflowReason::WindowResize => "\tWindowResize",
- ReflowReason::DOMContentLoaded => "\tDOMContentLoaded",
- ReflowReason::DocumentLoaded => "\tDocumentLoaded",
- ReflowReason::StylesheetLoaded => "\tStylesheetLoaded",
- ReflowReason::ImageLoaded => "\tImageLoaded",
- ReflowReason::RequestAnimationFrame => "\tRequestAnimationFrame",
- ReflowReason::WebFontLoaded => "\tWebFontLoaded",
- ReflowReason::WorkletLoaded => "\tWorkletLoaded",
- ReflowReason::FramedContentChanged => "\tFramedContentChanged",
- ReflowReason::IFrameLoadEvent => "\tIFrameLoadEvent",
- ReflowReason::MissingExplicitReflow => "\tMissingExplicitReflow",
- ReflowReason::ElementStateChanged => "\tElementStateChanged",
- ReflowReason::TickAnimations => "\tTickAnimations",
- ReflowReason::AdvanceClock(..) => "\tAdvanceClock",
- });
-
- println!("{}", debug_msg);
+ };
+
+ println!("**** pipeline={}\t{}\t{:?}", id, goal_string, reason);
}
impl Window {
diff --git a/components/script/lib.rs b/components/script/lib.rs
index 4eeca229e01..cd73045456a 100644
--- a/components/script/lib.rs
+++ b/components/script/lib.rs
@@ -47,6 +47,7 @@ extern crate servo_atoms;
#[macro_use]
extern crate style;
+mod animation_timeline;
#[warn(deprecated)]
#[macro_use]
mod task;
diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs
index 5a99d800799..008274ded47 100644
--- a/components/script/script_thread.rs
+++ b/components/script/script_thread.rs
@@ -141,11 +141,12 @@ use script_traits::{
LayoutMsg, LoadData, LoadOrigin, MediaSessionActionType, MouseButton, MouseEventType,
NewLayoutInfo, Painter, ProgressiveWebMetricType, ScriptMsg, ScriptThreadFactory,
ScriptToConstellationChan, StructuredSerializedData, TimerSchedulerMsg, TouchEventType,
- TouchId, TransitionOrAnimationEventType, UntrustedNodeAddress, UpdatePipelineIdReason,
- WebrenderIpcSender, WheelDelta, WindowSizeData, WindowSizeType,
+ TouchId, TransitionOrAnimationEvent, TransitionOrAnimationEventType, UntrustedNodeAddress,
+ UpdatePipelineIdReason, WebrenderIpcSender, WheelDelta, WindowSizeData, WindowSizeType,
};
use servo_atoms::Atom;
use servo_config::opts;
+use servo_config::pref;
use servo_url::{ImmutableOrigin, MutableOrigin, ServoUrl};
use std::borrow::Cow;
use std::cell::Cell;
@@ -1498,7 +1499,6 @@ impl ScriptThread {
})
},
FromConstellation(ConstellationControlMsg::TickAllAnimations(pipeline_id, _)) => {
- // step 7.8
if !animation_ticks.contains(&pipeline_id) {
animation_ticks.insert(pipeline_id);
sequential.push(event);
@@ -1544,6 +1544,9 @@ impl ScriptThread {
}
}
+ // Step 11.10 from https://html.spec.whatwg.org/multipage/#event-loops.
+ self.update_animations_and_send_events();
+
// Process the gathered events.
debug!("Processing events.");
for msg in sequential {
@@ -1603,7 +1606,7 @@ impl ScriptThread {
let pending_reflows = window.get_pending_reflow_count();
if pending_reflows > 0 {
- window.reflow(ReflowGoal::Full, ReflowReason::ImageLoaded);
+ window.reflow(ReflowGoal::Full, ReflowReason::PendingReflow);
} else {
// Reflow currently happens when explicitly invoked by code that
// knows the document could have been modified. This should really
@@ -1616,6 +1619,19 @@ impl ScriptThread {
true
}
+ // Perform step 11.10 from https://html.spec.whatwg.org/multipage/#event-loops.
+ // TODO(mrobinson): This should also update the current animations and send events
+ // to conform to the HTML specification. This might mean having events for rooting
+ // DOM nodes and ultimately all animations living in script.
+ fn update_animations_and_send_events(&self) {
+ for (_, document) in self.documents.borrow().iter() {
+ // Only update the time if it isn't being managed by a test.
+ if !pref!(layout.animations.test.enabled) {
+ document.update_animation_timeline();
+ }
+ }
+ }
+
fn categorize_msg(&self, msg: &MixedMessage) -> ScriptThreadEventCategory {
match *msg {
MixedMessage::FromConstellation(ref inner_msg) => match *inner_msg {
@@ -1905,20 +1921,8 @@ impl ScriptThread {
ConstellationControlMsg::TickAllAnimations(pipeline_id, tick_type) => {
self.handle_tick_all_animations(pipeline_id, tick_type)
},
- ConstellationControlMsg::TransitionOrAnimationEvent {
- pipeline_id,
- event_type,
- node,
- property_or_animation_name,
- elapsed_time,
- } => {
- self.handle_transition_or_animation_event(
- pipeline_id,
- event_type,
- node,
- property_or_animation_name,
- elapsed_time,
- );
+ ConstellationControlMsg::TransitionOrAnimationEvent(ref event) => {
+ self.handle_transition_or_animation_event(event);
},
ConstellationControlMsg::WebFontLoaded(pipeline_id) => {
self.handle_web_font_loaded(pipeline_id)
@@ -2914,22 +2918,12 @@ impl ScriptThread {
debug!("Exited script thread.");
}
- fn restyle_animating_nodes(&self, id: &PipelineId) -> bool {
- match self.animating_nodes.borrow().get(id) {
- Some(nodes) => {
- for node in nodes.iter() {
- node.dirty(NodeDamage::NodeStyleDamaged);
- }
- true
- },
- None => false,
- }
- }
-
- pub fn restyle_animating_nodes_for_advancing_clock(id: &PipelineId) {
+ /// Handles animation tick requested during testing.
+ pub fn handle_tick_all_animations_for_testing(id: PipelineId) {
SCRIPT_THREAD_ROOT.with(|root| {
let script_thread = unsafe { &*root.get().unwrap() };
- script_thread.restyle_animating_nodes(id);
+ script_thread
+ .handle_tick_all_animations(id, AnimationTickType::CSS_ANIMATIONS_AND_TRANSITIONS);
});
}
@@ -2943,38 +2937,32 @@ impl ScriptThread {
document.run_the_animation_frame_callbacks();
}
if tick_type.contains(AnimationTickType::CSS_ANIMATIONS_AND_TRANSITIONS) {
- if !self.restyle_animating_nodes(&id) {
- return;
+ match self.animating_nodes.borrow().get(&id) {
+ Some(nodes) => {
+ for node in nodes.iter() {
+ node.dirty(NodeDamage::NodeStyleDamaged);
+ }
+ },
+ None => return,
}
- document.window().force_reflow(
- ReflowGoal::TickAnimations,
- ReflowReason::TickAnimations,
- None,
- );
+ 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,
- pipeline_id: PipelineId,
- event_type: TransitionOrAnimationEventType,
- unsafe_node: UntrustedNodeAddress,
- property_or_animation_name: String,
- elapsed_time: f64,
- ) {
+ fn handle_transition_or_animation_event(&self, event: &TransitionOrAnimationEvent) {
let js_runtime = self.js_runtime.rt();
- let node = unsafe { from_untrusted_node_address(js_runtime, unsafe_node) };
+ let node = unsafe { from_untrusted_node_address(js_runtime, event.node) };
// We limit the scope of the borrow here, so that we don't maintain this borrow
// and then incidentally trigger another layout. That might result in a double
// mutable borrow of `animating_nodes`.
{
let mut animating_nodes = self.animating_nodes.borrow_mut();
- let nodes = match animating_nodes.get_mut(&pipeline_id) {
+ let nodes = match animating_nodes.get_mut(&event.pipeline_id) {
Some(nodes) => nodes,
None => {
return warn!(
@@ -2996,12 +2984,12 @@ impl ScriptThread {
},
};
- if event_type.finalizes_transition_or_animation() {
+ if event.event_type.finalizes_transition_or_animation() {
nodes.remove(node_index);
}
}
- let event_atom = match event_type {
+ let event_atom = match event.event_type {
TransitionOrAnimationEventType::AnimationEnd => atom!("animationend"),
TransitionOrAnimationEventType::TransitionCancel => atom!("transitioncancel"),
TransitionOrAnimationEventType::TransitionEnd => atom!("transitionend"),
@@ -3013,11 +3001,11 @@ impl ScriptThread {
};
// TODO: Handle pseudo-elements properly
- let property_or_animation_name = DOMString::from(property_or_animation_name);
- let elapsed_time = Finite::new(elapsed_time as f32).unwrap();
+ 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_type.is_transition_event() {
+ if event.event_type.is_transition_event() {
let event_init = TransitionEventInit {
parent,
propertyName: property_or_animation_name,