mirror of
https://github.com/servo/servo.git
synced 2025-08-05 21:50:18 +01:00
Auto merge of #15816 - servo:raf-timer, r=jdm,emilio
Improve performance of layout queries and requestAnimationFrame Part of #14442. <!-- Reviewable:start --> --- This change is [<img src="https://reviewable.io/review_button.svg" height="34" align="absmiddle" alt="Reviewable"/>](https://reviewable.io/reviews/servo/servo/15816) <!-- Reviewable:end -->
This commit is contained in:
commit
72fd27bbcc
4 changed files with 95 additions and 29 deletions
|
@ -84,11 +84,7 @@ pub enum ConstructionResult {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConstructionResult {
|
impl ConstructionResult {
|
||||||
pub fn swap_out(&mut self) -> ConstructionResult {
|
pub fn get(&mut self) -> ConstructionResult {
|
||||||
if opts::get().nonincremental_layout {
|
|
||||||
return mem::replace(self, ConstructionResult::None)
|
|
||||||
}
|
|
||||||
|
|
||||||
// FIXME(pcwalton): Stop doing this with inline fragments. Cloning fragments is very
|
// FIXME(pcwalton): Stop doing this with inline fragments. Cloning fragments is very
|
||||||
// inefficient!
|
// inefficient!
|
||||||
(*self).clone()
|
(*self).clone()
|
||||||
|
@ -485,7 +481,7 @@ impl<'a, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode>
|
||||||
inline_fragment_accumulator: &mut InlineFragmentsAccumulator,
|
inline_fragment_accumulator: &mut InlineFragmentsAccumulator,
|
||||||
abs_descendants: &mut AbsoluteDescendants,
|
abs_descendants: &mut AbsoluteDescendants,
|
||||||
legalizer: &mut Legalizer) {
|
legalizer: &mut Legalizer) {
|
||||||
match kid.swap_out_construction_result() {
|
match kid.get_construction_result() {
|
||||||
ConstructionResult::None => {}
|
ConstructionResult::None => {}
|
||||||
ConstructionResult::Flow(kid_flow, kid_abs_descendants) => {
|
ConstructionResult::Flow(kid_flow, kid_abs_descendants) => {
|
||||||
// If kid_flow is TableCaptionFlow, kid_flow should be added under
|
// If kid_flow is TableCaptionFlow, kid_flow should be added under
|
||||||
|
@ -784,7 +780,7 @@ impl<'a, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode>
|
||||||
if kid.get_pseudo_element_type() != PseudoElementType::Normal {
|
if kid.get_pseudo_element_type() != PseudoElementType::Normal {
|
||||||
self.process(&kid);
|
self.process(&kid);
|
||||||
}
|
}
|
||||||
match kid.swap_out_construction_result() {
|
match kid.get_construction_result() {
|
||||||
ConstructionResult::None => {}
|
ConstructionResult::None => {}
|
||||||
ConstructionResult::Flow(flow, kid_abs_descendants) => {
|
ConstructionResult::Flow(flow, kid_abs_descendants) => {
|
||||||
if !flow::base(&*flow).flags.contains(IS_ABSOLUTELY_POSITIONED) {
|
if !flow::base(&*flow).flags.contains(IS_ABSOLUTELY_POSITIONED) {
|
||||||
|
@ -1035,7 +1031,7 @@ impl<'a, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode>
|
||||||
side: caption_side::T) {
|
side: caption_side::T) {
|
||||||
// Only flows that are table captions are matched here.
|
// Only flows that are table captions are matched here.
|
||||||
for kid in node.children() {
|
for kid in node.children() {
|
||||||
match kid.swap_out_construction_result() {
|
match kid.get_construction_result() {
|
||||||
ConstructionResult::Flow(kid_flow, _) => {
|
ConstructionResult::Flow(kid_flow, _) => {
|
||||||
if kid_flow.is_table_caption() &&
|
if kid_flow.is_table_caption() &&
|
||||||
kid_flow.as_block()
|
kid_flow.as_block()
|
||||||
|
@ -1304,7 +1300,7 @@ impl<'a, ConcreteThreadSafeLayoutNode: ThreadSafeLayoutNode>
|
||||||
// CSS 2.1 § 17.2.1. Treat all non-column child fragments of `table-column-group`
|
// CSS 2.1 § 17.2.1. Treat all non-column child fragments of `table-column-group`
|
||||||
// as `display: none`.
|
// as `display: none`.
|
||||||
if let ConstructionResult::ConstructionItem(ConstructionItem::TableColumnFragment(fragment)) =
|
if let ConstructionResult::ConstructionItem(ConstructionItem::TableColumnFragment(fragment)) =
|
||||||
kid.swap_out_construction_result() {
|
kid.get_construction_result() {
|
||||||
col_fragments.push(fragment)
|
col_fragments.push(fragment)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1641,9 +1637,8 @@ trait NodeUtils {
|
||||||
/// Sets the construction result of a flow.
|
/// Sets the construction result of a flow.
|
||||||
fn set_flow_construction_result(self, result: ConstructionResult);
|
fn set_flow_construction_result(self, result: ConstructionResult);
|
||||||
|
|
||||||
/// Replaces the flow construction result in a node with `ConstructionResult::None` and returns
|
/// Returns the construction result for this node.
|
||||||
/// the old value.
|
fn get_construction_result(self) -> ConstructionResult;
|
||||||
fn swap_out_construction_result(self) -> ConstructionResult;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<ConcreteThreadSafeLayoutNode> NodeUtils for ConcreteThreadSafeLayoutNode
|
impl<ConcreteThreadSafeLayoutNode> NodeUtils for ConcreteThreadSafeLayoutNode
|
||||||
|
@ -1686,9 +1681,9 @@ impl<ConcreteThreadSafeLayoutNode> NodeUtils for ConcreteThreadSafeLayoutNode
|
||||||
}
|
}
|
||||||
|
|
||||||
#[inline(always)]
|
#[inline(always)]
|
||||||
fn swap_out_construction_result(self) -> ConstructionResult {
|
fn get_construction_result(self) -> ConstructionResult {
|
||||||
let mut layout_data = self.mutate_layout_data().unwrap();
|
let mut layout_data = self.mutate_layout_data().unwrap();
|
||||||
self.construction_result_mut(&mut *layout_data).swap_out()
|
self.construction_result_mut(&mut *layout_data).get()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -774,7 +774,7 @@ impl LayoutThread {
|
||||||
Some(x) => x,
|
Some(x) => x,
|
||||||
None => return None,
|
None => return None,
|
||||||
};
|
};
|
||||||
let result = data.flow_construction_result.swap_out();
|
let result = data.flow_construction_result.get();
|
||||||
|
|
||||||
let mut flow = match result {
|
let mut flow = match result {
|
||||||
ConstructionResult::Flow(mut flow, abs_descendants) => {
|
ConstructionResult::Flow(mut flow, abs_descendants) => {
|
||||||
|
|
|
@ -111,7 +111,7 @@ use script_runtime::{CommonScriptMsg, ScriptThreadEventCategory};
|
||||||
use script_thread::{MainThreadScriptMsg, Runnable};
|
use script_thread::{MainThreadScriptMsg, Runnable};
|
||||||
use script_traits::{AnimationState, CompositorEvent, DocumentActivity};
|
use script_traits::{AnimationState, CompositorEvent, DocumentActivity};
|
||||||
use script_traits::{MouseButton, MouseEventType, MozBrowserEvent};
|
use script_traits::{MouseButton, MouseEventType, MozBrowserEvent};
|
||||||
use script_traits::{ScriptMsg as ConstellationMsg, TouchpadPressurePhase};
|
use script_traits::{MsDuration, ScriptMsg as ConstellationMsg, TouchpadPressurePhase};
|
||||||
use script_traits::{TouchEventType, TouchId};
|
use script_traits::{TouchEventType, TouchId};
|
||||||
use script_traits::UntrustedNodeAddress;
|
use script_traits::UntrustedNodeAddress;
|
||||||
use servo_atoms::Atom;
|
use servo_atoms::Atom;
|
||||||
|
@ -136,9 +136,19 @@ use style::str::{HTML_SPACE_CHARACTERS, split_html_space_chars, str_join};
|
||||||
use style::stylesheets::Stylesheet;
|
use style::stylesheets::Stylesheet;
|
||||||
use task_source::TaskSource;
|
use task_source::TaskSource;
|
||||||
use time;
|
use time;
|
||||||
|
use timers::OneshotTimerCallback;
|
||||||
use url::Host;
|
use url::Host;
|
||||||
use url::percent_encoding::percent_decode;
|
use url::percent_encoding::percent_decode;
|
||||||
|
|
||||||
|
/// The number of times we are allowed to see spurious `requestAnimationFrame()` calls before
|
||||||
|
/// falling back to fake ones.
|
||||||
|
///
|
||||||
|
/// A spurious `requestAnimationFrame()` call is defined as one that does not change the DOM.
|
||||||
|
const SPURIOUS_ANIMATION_FRAME_THRESHOLD: u8 = 5;
|
||||||
|
|
||||||
|
/// The amount of time between fake `requestAnimationFrame()`s.
|
||||||
|
const FAKE_REQUEST_ANIMATION_FRAME_DELAY: u64 = 16;
|
||||||
|
|
||||||
pub enum TouchEventResult {
|
pub enum TouchEventResult {
|
||||||
Processed(bool),
|
Processed(bool),
|
||||||
Forwarded,
|
Forwarded,
|
||||||
|
@ -290,6 +300,11 @@ pub struct Document {
|
||||||
last_click_info: DOMRefCell<Option<(Instant, Point2D<f32>)>>,
|
last_click_info: DOMRefCell<Option<(Instant, Point2D<f32>)>>,
|
||||||
/// https://html.spec.whatwg.org/multipage/#ignore-destructive-writes-counter
|
/// https://html.spec.whatwg.org/multipage/#ignore-destructive-writes-counter
|
||||||
ignore_destructive_writes_counter: Cell<u32>,
|
ignore_destructive_writes_counter: Cell<u32>,
|
||||||
|
/// The number of spurious `requestAnimationFrame()` requests we've received.
|
||||||
|
///
|
||||||
|
/// A rAF request is considered spurious if nothing was actually reflowed.
|
||||||
|
spurious_animation_frames: Cell<u8>,
|
||||||
|
|
||||||
/// Track the total number of elements in this DOM's tree.
|
/// Track the total number of elements in this DOM's tree.
|
||||||
/// This is sent to the layout thread every time a reflow is done;
|
/// This is sent to the layout thread every time a reflow is done;
|
||||||
/// layout uses this to determine if the gains from parallel layout will be worth the overhead.
|
/// layout uses this to determine if the gains from parallel layout will be worth the overhead.
|
||||||
|
@ -1498,11 +1513,20 @@ impl Document {
|
||||||
//
|
//
|
||||||
// TODO: Should tick animation only when document is visible
|
// TODO: Should tick animation only when document is visible
|
||||||
if !self.running_animation_callbacks.get() {
|
if !self.running_animation_callbacks.get() {
|
||||||
let global_scope = self.window.upcast::<GlobalScope>();
|
if !self.is_faking_animation_frames() {
|
||||||
let event = ConstellationMsg::ChangeRunningAnimationsState(
|
let global_scope = self.window.upcast::<GlobalScope>();
|
||||||
global_scope.pipeline_id(),
|
let event = ConstellationMsg::ChangeRunningAnimationsState(
|
||||||
AnimationState::AnimationCallbacksPresent);
|
global_scope.pipeline_id(),
|
||||||
global_scope.constellation_chan().send(event).unwrap();
|
AnimationState::AnimationCallbacksPresent);
|
||||||
|
global_scope.constellation_chan().send(event).unwrap();
|
||||||
|
} else {
|
||||||
|
let callback = FakeRequestAnimationFrameCallback {
|
||||||
|
document: Trusted::new(self),
|
||||||
|
};
|
||||||
|
self.global()
|
||||||
|
.schedule_callback(OneshotTimerCallback::FakeRequestAnimationFrame(callback),
|
||||||
|
MsDuration::new(FAKE_REQUEST_ANIMATION_FRAME_DELAY));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ident
|
ident
|
||||||
|
@ -1524,6 +1548,7 @@ impl Document {
|
||||||
&mut *self.animation_frame_list.borrow_mut());
|
&mut *self.animation_frame_list.borrow_mut());
|
||||||
|
|
||||||
self.running_animation_callbacks.set(true);
|
self.running_animation_callbacks.set(true);
|
||||||
|
let was_faking_animation_frames = self.is_faking_animation_frames();
|
||||||
let timing = self.window.Performance().Now();
|
let timing = self.window.Performance().Now();
|
||||||
|
|
||||||
for (_, callback) in animation_frame_list.drain(..) {
|
for (_, callback) in animation_frame_list.drain(..) {
|
||||||
|
@ -1532,24 +1557,40 @@ impl Document {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.running_animation_callbacks.set(false);
|
||||||
|
|
||||||
|
let spurious = !self.window.reflow(ReflowGoal::ForDisplay,
|
||||||
|
ReflowQueryType::NoQuery,
|
||||||
|
ReflowReason::RequestAnimationFrame);
|
||||||
|
|
||||||
// Only send the animation change state message after running any callbacks.
|
// Only send the animation change state message after running any callbacks.
|
||||||
// This means that if the animation callback adds a new callback for
|
// This means that if the animation callback adds a new callback for
|
||||||
// the next frame (which is the common case), we won't send a NoAnimationCallbacksPresent
|
// the next frame (which is the common case), we won't send a NoAnimationCallbacksPresent
|
||||||
// message quickly followed by an AnimationCallbacksPresent message.
|
// message quickly followed by an AnimationCallbacksPresent message.
|
||||||
if self.animation_frame_list.borrow().is_empty() {
|
//
|
||||||
|
// If this frame was spurious and we've seen too many spurious frames in a row, tell the
|
||||||
|
// constellation to stop giving us video refresh callbacks, to save energy. (A spurious
|
||||||
|
// animation frame is one in which the callback did not mutate the DOM—that is, an
|
||||||
|
// animation frame that wasn't actually used for animation.)
|
||||||
|
if self.animation_frame_list.borrow().is_empty() ||
|
||||||
|
(!was_faking_animation_frames && self.is_faking_animation_frames()) {
|
||||||
mem::swap(&mut *self.animation_frame_list.borrow_mut(),
|
mem::swap(&mut *self.animation_frame_list.borrow_mut(),
|
||||||
&mut *animation_frame_list);
|
&mut *animation_frame_list);
|
||||||
let global_scope = self.window.upcast::<GlobalScope>();
|
let global_scope = self.window.upcast::<GlobalScope>();
|
||||||
let event = ConstellationMsg::ChangeRunningAnimationsState(global_scope.pipeline_id(),
|
let event = ConstellationMsg::ChangeRunningAnimationsState(
|
||||||
AnimationState::NoAnimationCallbacksPresent);
|
global_scope.pipeline_id(),
|
||||||
|
AnimationState::NoAnimationCallbacksPresent);
|
||||||
global_scope.constellation_chan().send(event).unwrap();
|
global_scope.constellation_chan().send(event).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
self.running_animation_callbacks.set(false);
|
// Update the counter of spurious animation frames.
|
||||||
|
if spurious {
|
||||||
self.window.reflow(ReflowGoal::ForDisplay,
|
if self.spurious_animation_frames.get() < SPURIOUS_ANIMATION_FRAME_THRESHOLD {
|
||||||
ReflowQueryType::NoQuery,
|
self.spurious_animation_frames.set(self.spurious_animation_frames.get() + 1)
|
||||||
ReflowReason::RequestAnimationFrame);
|
}
|
||||||
|
} else {
|
||||||
|
self.spurious_animation_frames.set(0)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn fetch_async(&self, load: LoadType,
|
pub fn fetch_async(&self, load: LoadType,
|
||||||
|
@ -2048,6 +2089,7 @@ impl Document {
|
||||||
target_element: MutNullableJS::new(None),
|
target_element: MutNullableJS::new(None),
|
||||||
last_click_info: DOMRefCell::new(None),
|
last_click_info: DOMRefCell::new(None),
|
||||||
ignore_destructive_writes_counter: Default::default(),
|
ignore_destructive_writes_counter: Default::default(),
|
||||||
|
spurious_animation_frames: Cell::new(0),
|
||||||
dom_count: Cell::new(1),
|
dom_count: Cell::new(1),
|
||||||
fullscreen_element: MutNullableJS::new(None),
|
fullscreen_element: MutNullableJS::new(None),
|
||||||
}
|
}
|
||||||
|
@ -2254,6 +2296,12 @@ impl Document {
|
||||||
self.ignore_destructive_writes_counter.get() - 1);
|
self.ignore_destructive_writes_counter.get() - 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether we've seen so many spurious animation frames (i.e. animation frames that didn't
|
||||||
|
/// mutate the DOM) that we've decided to fall back to fake ones.
|
||||||
|
fn is_faking_animation_frames(&self) -> bool {
|
||||||
|
self.spurious_animation_frames.get() >= SPURIOUS_ANIMATION_FRAME_THRESHOLD
|
||||||
|
}
|
||||||
|
|
||||||
// https://fullscreen.spec.whatwg.org/#dom-element-requestfullscreen
|
// https://fullscreen.spec.whatwg.org/#dom-element-requestfullscreen
|
||||||
#[allow(unrooted_must_root)]
|
#[allow(unrooted_must_root)]
|
||||||
pub fn enter_fullscreen(&self, pending: &Element) -> Rc<Promise> {
|
pub fn enter_fullscreen(&self, pending: &Element) -> Rc<Promise> {
|
||||||
|
@ -3668,6 +3716,26 @@ pub enum FocusEventType {
|
||||||
Blur, // Element lost focus. Doesn't bubble.
|
Blur, // Element lost focus. Doesn't bubble.
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A fake `requestAnimationFrame()` callback—"fake" because it is not triggered by the video
|
||||||
|
/// refresh but rather a simple timer.
|
||||||
|
///
|
||||||
|
/// If the page is observed to be using `requestAnimationFrame()` for non-animation purposes (i.e.
|
||||||
|
/// without mutating the DOM), then we fall back to simple timeouts to save energy over video
|
||||||
|
/// refresh.
|
||||||
|
#[derive(JSTraceable, HeapSizeOf)]
|
||||||
|
pub struct FakeRequestAnimationFrameCallback {
|
||||||
|
/// The document.
|
||||||
|
#[ignore_heap_size_of = "non-owning"]
|
||||||
|
document: Trusted<Document>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FakeRequestAnimationFrameCallback {
|
||||||
|
pub fn invoke(self) {
|
||||||
|
let document = self.document.root();
|
||||||
|
document.run_the_animation_frame_callbacks();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(HeapSizeOf, JSTraceable)]
|
#[derive(HeapSizeOf, JSTraceable)]
|
||||||
pub enum AnimationFrameCallback {
|
pub enum AnimationFrameCallback {
|
||||||
DevtoolsFramerateTick { actor_name: String },
|
DevtoolsFramerateTick { actor_name: String },
|
||||||
|
|
|
@ -7,6 +7,7 @@ use dom::bindings::cell::DOMRefCell;
|
||||||
use dom::bindings::codegen::Bindings::FunctionBinding::Function;
|
use dom::bindings::codegen::Bindings::FunctionBinding::Function;
|
||||||
use dom::bindings::reflector::DomObject;
|
use dom::bindings::reflector::DomObject;
|
||||||
use dom::bindings::str::DOMString;
|
use dom::bindings::str::DOMString;
|
||||||
|
use dom::document::FakeRequestAnimationFrameCallback;
|
||||||
use dom::eventsource::EventSourceTimeoutCallback;
|
use dom::eventsource::EventSourceTimeoutCallback;
|
||||||
use dom::globalscope::GlobalScope;
|
use dom::globalscope::GlobalScope;
|
||||||
use dom::testbinding::TestBindingCallback;
|
use dom::testbinding::TestBindingCallback;
|
||||||
|
@ -69,6 +70,7 @@ pub enum OneshotTimerCallback {
|
||||||
EventSourceTimeout(EventSourceTimeoutCallback),
|
EventSourceTimeout(EventSourceTimeoutCallback),
|
||||||
JsTimer(JsTimerTask),
|
JsTimer(JsTimerTask),
|
||||||
TestBindingCallback(TestBindingCallback),
|
TestBindingCallback(TestBindingCallback),
|
||||||
|
FakeRequestAnimationFrame(FakeRequestAnimationFrameCallback),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl OneshotTimerCallback {
|
impl OneshotTimerCallback {
|
||||||
|
@ -78,6 +80,7 @@ impl OneshotTimerCallback {
|
||||||
OneshotTimerCallback::EventSourceTimeout(callback) => callback.invoke(),
|
OneshotTimerCallback::EventSourceTimeout(callback) => callback.invoke(),
|
||||||
OneshotTimerCallback::JsTimer(task) => task.invoke(this, js_timers),
|
OneshotTimerCallback::JsTimer(task) => task.invoke(this, js_timers),
|
||||||
OneshotTimerCallback::TestBindingCallback(callback) => callback.invoke(),
|
OneshotTimerCallback::TestBindingCallback(callback) => callback.invoke(),
|
||||||
|
OneshotTimerCallback::FakeRequestAnimationFrame(callback) => callback.invoke(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue