Cache the result of retrieving an element's client rectangle from layout.

This commit is contained in:
Josh Matthews 2020-03-26 16:07:29 -04:00
parent 148c24c29c
commit 6ab7a50b31
5 changed files with 125 additions and 22 deletions

View file

@ -23,14 +23,14 @@ use crate::dom::bindings::reflector::DomObject;
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::bindings::structuredclone;
use crate::dom::bindings::trace::RootedTraceableBox;
use crate::dom::bindings::trace::{JSTraceable, RootedTraceableBox};
use crate::dom::bindings::utils::{GlobalStaticData, WindowProxyHandler};
use crate::dom::bindings::weakref::DOMTracker;
use crate::dom::bluetooth::BluetoothExtraPermissionData;
use crate::dom::crypto::Crypto;
use crate::dom::cssstyledeclaration::{CSSModificationAccess, CSSStyleDeclaration, CSSStyleOwner};
use crate::dom::customelementregistry::CustomElementRegistry;
use crate::dom::document::{AnimationFrameCallback, Document};
use crate::dom::document::{AnimationFrameCallback, Document, ReflowTriggerCondition};
use crate::dom::element::Element;
use crate::dom::event::{Event, EventStatus};
use crate::dom::eventtarget::EventTarget;
@ -55,6 +55,7 @@ use crate::dom::worklet::Worklet;
use crate::dom::workletglobalscope::WorkletGlobalScopeType;
use crate::fetch;
use crate::layout_image::fetch_image_for_layout;
use crate::malloc_size_of::MallocSizeOf;
use crate::microtask::MicrotaskQueue;
use crate::realms::InRealm;
use crate::script_runtime::{
@ -334,6 +335,12 @@ pub struct Window {
event_loop_waker: Option<Box<dyn EventLoopWaker>>,
visible: Cell<bool>,
/// A shared marker for the validity of any cached layout values. A value of true
/// indicates that any such values remain valid; any new layout that invalidates
/// those values will cause the marker to be set to false.
#[ignore_malloc_size_of = "Rc is hard"]
layout_marker: DomRefCell<Rc<Cell<bool>>>,
}
impl Window {
@ -1561,7 +1568,12 @@ impl Window {
///
/// Returns true if layout actually happened, false otherwise.
#[allow(unsafe_code)]
pub fn force_reflow(&self, reflow_goal: ReflowGoal, reason: ReflowReason) -> bool {
pub fn force_reflow(
&self,
reflow_goal: ReflowGoal,
reason: ReflowReason,
condition: Option<ReflowTriggerCondition>,
) -> bool {
self.Document().ensure_safe_to_run_script_or_layout();
// Check if we need to unsuppress reflow. Note that this needs to be
// *before* any early bailouts, or reflow might never be unsuppresed!
@ -1580,6 +1592,19 @@ impl Window {
return false;
}
if condition != Some(ReflowTriggerCondition::PaintPostponed) {
debug!(
"Invalidating layout cache due to reflow condition {:?}",
condition
);
// Invalidate any existing cached layout values.
self.layout_marker.borrow().set(false);
// Create a new layout caching token.
*self.layout_marker.borrow_mut() = Rc::new(Cell::new(true));
} else {
debug!("Not invalidating cached layout values for paint-only reflow.");
}
debug!("script: performing reflow for reason {:?}", reason);
let marker = if self.need_emit_timeline_marker(TimelineMarkerType::Reflow) {
@ -1714,16 +1739,18 @@ impl Window {
let for_display = reflow_goal == ReflowGoal::Full;
let mut issued_reflow = false;
if !for_display || self.Document().needs_reflow() {
issued_reflow = self.force_reflow(reflow_goal, reason);
let condition = self.Document().needs_reflow();
if !for_display || condition.is_some() {
issued_reflow = self.force_reflow(reflow_goal, reason, condition);
// We shouldn't need a reflow immediately after a
// reflow, except if we're waiting for a deferred paint.
assert!(
!self.Document().needs_reflow() ||
(!for_display && self.Document().needs_paint()) ||
assert!({
let condition = self.Document().needs_reflow();
condition.is_none() ||
(!for_display && condition == Some(ReflowTriggerCondition::PaintPostponed)) ||
self.suppress_reflow.get()
);
});
} else {
debug!(
"Document doesn't need reflow - skipping it (reason {:?})",
@ -2348,6 +2375,7 @@ impl Window {
player_context,
event_loop_waker,
visible: Cell::new(true),
layout_marker: DomRefCell::new(Rc::new(Cell::new(true))),
});
unsafe { WindowBinding::Wrap(JSContext::from_ptr(runtime.cx()), win) }
@ -2356,6 +2384,42 @@ impl Window {
pub fn pipeline_id(&self) -> PipelineId {
self.upcast::<GlobalScope>().pipeline_id()
}
/// Create a new cached instance of the given value.
pub fn cache_layout_value<T>(&self, value: T) -> LayoutValue<T>
where
T: Copy + JSTraceable + MallocSizeOf,
{
LayoutValue::new(self.layout_marker.borrow().clone(), value)
}
}
/// An instance of a value associated with a particular snapshot of layout. This stored
/// value can only be read as long as the associated layout marker that is considered
/// valid. It will automatically become unavailable when the next layout operation is
/// performed.
#[derive(JSTraceable, MallocSizeOf)]
pub struct LayoutValue<T: JSTraceable + MallocSizeOf> {
#[ignore_malloc_size_of = "Rc is hard"]
is_valid: Rc<Cell<bool>>,
value: T,
}
impl<T: Copy + JSTraceable + MallocSizeOf> LayoutValue<T> {
fn new(marker: Rc<Cell<bool>>, value: T) -> Self {
LayoutValue {
is_valid: marker,
value,
}
}
/// Retrieve the stored value if it is still valid.
pub fn get(&self) -> Result<T, ()> {
if self.is_valid.get() {
return Ok(self.value);
}
Err(())
}
}
fn should_move_clip_rect(clip_rect: UntypedRect<Au>, new_viewport: UntypedRect<f32>) -> bool {