aboutsummaryrefslogtreecommitdiffstats
path: root/components/script
diff options
context:
space:
mode:
Diffstat (limited to 'components/script')
-rw-r--r--components/script/dom/activation.rs18
-rw-r--r--components/script/dom/document.rs63
-rw-r--r--components/script/dom/element.rs14
-rw-r--r--components/script/dom/htmlbodyelement.rs11
-rw-r--r--components/script/dom/servoparser/mod.rs4
-rw-r--r--components/script/dom/window.rs221
-rw-r--r--components/script/script_thread.rs20
7 files changed, 165 insertions, 186 deletions
diff --git a/components/script/dom/activation.rs b/components/script/dom/activation.rs
index 8f325fb4e89..2d78f505214 100644
--- a/components/script/dom/activation.rs
+++ b/components/script/dom/activation.rs
@@ -2,14 +2,10 @@
* 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 script_layout_interface::ReflowGoal;
-
use crate::dom::element::Element;
use crate::dom::event::Event;
use crate::dom::eventtarget::EventTarget;
use crate::dom::htmlinputelement::InputActivationState;
-use crate::dom::node::window_from_node;
-use crate::dom::window::ReflowReason;
use crate::script_runtime::CanGc;
/// Trait for elements with defined activation behavior
@@ -35,23 +31,9 @@ pub trait Activatable {
// https://html.spec.whatwg.org/multipage/#concept-selector-active
fn enter_formal_activation_state(&self) {
self.as_element().set_active_state(true);
-
- let win = window_from_node(self.as_element());
- win.reflow(
- ReflowGoal::Full,
- ReflowReason::ElementStateChanged,
- CanGc::note(),
- );
}
fn exit_formal_activation_state(&self) {
self.as_element().set_active_state(false);
-
- let win = window_from_node(self.as_element());
- win.reflow(
- ReflowGoal::Full,
- ReflowReason::ElementStateChanged,
- CanGc::note(),
- );
}
}
diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs
index 550832a47bb..84441a3d239 100644
--- a/components/script/dom/document.rs
+++ b/components/script/dom/document.rs
@@ -47,7 +47,7 @@ use num_traits::ToPrimitive;
use percent_encoding::percent_decode;
use profile_traits::ipc as profile_ipc;
use profile_traits::time::{TimerMetadata, TimerMetadataFrameType, TimerMetadataReflowType};
-use script_layout_interface::{PendingRestyle, ReflowGoal, TrustedNodeAddress};
+use script_layout_interface::{PendingRestyle, TrustedNodeAddress};
use script_traits::{
AnimationState, AnimationTickType, CompositorEvent, DocumentActivity, MouseButton,
MouseEventType, ScriptMsg, TouchEventType, TouchId, UntrustedNodeAddress, WheelDelta,
@@ -180,7 +180,7 @@ use crate::dom::webglrenderingcontext::WebGLRenderingContext;
#[cfg(feature = "webgpu")]
use crate::dom::webgpu::gpucanvascontext::GPUCanvasContext;
use crate::dom::wheelevent::WheelEvent;
-use crate::dom::window::{ReflowReason, Window};
+use crate::dom::window::Window;
use crate::dom::windowproxy::WindowProxy;
use crate::dom::xpathevaluator::XPathEvaluator;
use crate::fetch::FetchCanceller;
@@ -335,8 +335,6 @@ pub struct Document {
loader: DomRefCell<DocumentLoader>,
/// The current active HTML parser, to allow resuming after interruptions.
current_parser: MutNullableDom<ServoParser>,
- /// When we should kick off a reflow. This happens during parsing.
- reflow_timeout: Cell<Option<Instant>>,
/// The cached first `base` element with an `href` attribute.
base_element: MutNullableDom<HTMLBaseElement>,
/// This field is set to the document itself for inert documents.
@@ -705,7 +703,7 @@ impl Document {
self.activity.get() != DocumentActivity::Inactive
}
- pub fn set_activity(&self, activity: DocumentActivity, can_gc: CanGc) {
+ pub fn set_activity(&self, activity: DocumentActivity) {
// This function should only be called on documents with a browsing context
assert!(self.has_browsing_context);
if activity == self.activity.get() {
@@ -727,11 +725,6 @@ impl Document {
self.title_changed();
self.dirty_all_nodes();
- self.window().reflow(
- ReflowGoal::Full,
- ReflowReason::CachedPageNeededReflow,
- can_gc,
- );
self.window().resume();
media.resume(&client_context_id);
@@ -919,36 +912,6 @@ impl Document {
node.dirty(NodeDamage::OtherNodeDamage);
}
- /// Reflows and disarms the timer if the reflow timer has expired.
- pub fn reflow_if_reflow_timer_expired(&self, can_gc: CanGc) {
- let Some(reflow_timeout) = self.reflow_timeout.get() else {
- return;
- };
-
- if Instant::now() < reflow_timeout {
- return;
- }
-
- self.reflow_timeout.set(None);
- self.window
- .reflow(ReflowGoal::Full, ReflowReason::RefreshTick, can_gc);
- }
-
- /// Schedules a reflow to be kicked off at the given [`Duration`] in the future. This reflow
- /// happens even if the event loop is busy. This is used to display initial page content during
- /// parsing.
- pub fn set_reflow_timeout(&self, duration: Duration) {
- let new_reflow_time = Instant::now() + duration;
-
- // Do not schedule a timeout that is longer than the one that is currently queued.
- if matches!(self.reflow_timeout.get(), Some(existing_timeout) if existing_timeout < new_reflow_time)
- {
- return;
- }
-
- self.reflow_timeout.set(Some(new_reflow_time))
- }
-
/// Remove any existing association between the provided id and any elements in this document.
pub fn unregister_element_id(&self, to_unregister: &Element, id: Atom) {
self.document_or_shadow_root
@@ -1037,7 +1000,7 @@ impl Document {
let target = self.find_fragment_node(fragment);
// Step 1
- self.set_target_element(target.as_deref(), can_gc);
+ self.set_target_element(target.as_deref());
let point = target
.as_ref()
@@ -2257,16 +2220,10 @@ impl Document {
self.process_deferred_scripts();
},
LoadType::PageSource(_) => {
+ // We finished loading the page, so if the `Window` is still waiting for
+ // the first layout, allow it.
if self.has_browsing_context && self.is_fully_active() {
- // Note: if the document is not fully active, layout will have exited already.
- // The underlying problem might actually be that layout exits while it should be kept alive.
- // See https://github.com/servo/servo/issues/22507
-
- // Disarm the reflow timer and trigger the initial reflow.
- self.reflow_timeout.set(None);
- self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
- self.window
- .reflow(ReflowGoal::Full, ReflowReason::FirstLoad, can_gc);
+ self.window().allow_layout_if_necessary(can_gc);
}
// Deferred scripts have to wait for page to finish loading,
@@ -3385,7 +3342,6 @@ impl Document {
running_animation_callbacks: Cell::new(false),
loader: DomRefCell::new(doc_loader),
current_parser: Default::default(),
- reflow_timeout: Cell::new(None),
base_element: Default::default(),
appropriate_template_contents_owner_document: Default::default(),
pending_restyles: DomRefCell::new(HashMap::new()),
@@ -3831,7 +3787,7 @@ impl Document {
self.policy_container.borrow().get_referrer_policy()
}
- pub fn set_target_element(&self, node: Option<&Element>, can_gc: CanGc) {
+ pub fn set_target_element(&self, node: Option<&Element>) {
if let Some(ref element) = self.target_element.get() {
element.set_target_state(false);
}
@@ -3841,9 +3797,6 @@ impl Document {
if let Some(ref element) = self.target_element.get() {
element.set_target_state(true);
}
-
- self.window
- .reflow(ReflowGoal::Full, ReflowReason::ElementStateChanged, can_gc);
}
pub fn incr_ignore_destructive_writes_counter(&self) {
diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs
index f28070a7bf7..44838256cf5 100644
--- a/components/script/dom/element.rs
+++ b/components/script/dom/element.rs
@@ -26,7 +26,6 @@ use js::jsval::JSVal;
use js::rust::HandleObject;
use net_traits::request::CorsSettings;
use net_traits::ReferrerPolicy;
-use script_layout_interface::ReflowGoal;
use selectors::attr::{AttrSelectorOperation, CaseSensitivity, NamespaceConstraint};
use selectors::bloom::{BloomFilter, BLOOM_HASH_MASK};
use selectors::matching::{ElementSelectorFlags, MatchingContext};
@@ -149,7 +148,6 @@ use crate::dom::text::Text;
use crate::dom::validation::Validatable;
use crate::dom::validitystate::ValidationFlags;
use crate::dom::virtualmethods::{vtable_for, VirtualMethods};
-use crate::dom::window::ReflowReason;
use crate::script_runtime::CanGc;
use crate::script_thread::ScriptThread;
use crate::stylesheet_loader::StylesheetOwner;
@@ -4429,11 +4427,6 @@ impl TaskOnce for ElementPerformFullscreenEnter {
// Step 7.5
element.set_fullscreen_state(true);
document.set_fullscreen_element(Some(&element));
- document.window().reflow(
- ReflowGoal::Full,
- ReflowReason::ElementStateChanged,
- CanGc::note(),
- );
// Step 7.6
document
@@ -4467,13 +4460,6 @@ impl TaskOnce for ElementPerformFullscreenExit {
// TODO Step 9.1-5
// Step 9.6
element.set_fullscreen_state(false);
-
- document.window().reflow(
- ReflowGoal::Full,
- ReflowReason::ElementStateChanged,
- CanGc::note(),
- );
-
document.set_fullscreen_element(None);
// Step 9.8
diff --git a/components/script/dom/htmlbodyelement.rs b/components/script/dom/htmlbodyelement.rs
index 5bb650973c7..0b749b779a5 100644
--- a/components/script/dom/htmlbodyelement.rs
+++ b/components/script/dom/htmlbodyelement.rs
@@ -2,8 +2,6 @@
* 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 std::time::Duration;
-
use dom_struct::dom_struct;
use embedder_traits::EmbedderMsg;
use html5ever::{local_name, namespace_url, ns, LocalName, Prefix};
@@ -26,9 +24,6 @@ use crate::dom::node::{document_from_node, window_from_node, BindContext, Node};
use crate::dom::virtualmethods::VirtualMethods;
use crate::script_runtime::CanGc;
-/// How long we should wait before performing the initial reflow after `<body>` is parsed.
-const INITIAL_REFLOW_DELAY: Duration = Duration::from_millis(200);
-
#[dom_struct]
pub struct HTMLBodyElement {
htmlelement: HTMLElement,
@@ -158,11 +153,9 @@ impl VirtualMethods for HTMLBodyElement {
}
let window = window_from_node(self);
- let document = window.Document();
- document.set_reflow_timeout(INITIAL_REFLOW_DELAY);
+ window.prevent_layout_until_load_event();
if window.is_top_level() {
- let msg = EmbedderMsg::HeadParsed;
- window.send_to_embedder(msg);
+ window.send_to_embedder(EmbedderMsg::HeadParsed);
}
}
diff --git a/components/script/dom/servoparser/mod.rs b/components/script/dom/servoparser/mod.rs
index aa181a3e074..2a2b19ad061 100644
--- a/components/script/dom/servoparser/mod.rs
+++ b/components/script/dom/servoparser/mod.rs
@@ -618,7 +618,9 @@ impl ServoParser {
assert!(!self.suspended.get());
assert!(!self.aborted.get());
- self.document.reflow_if_reflow_timer_expired(can_gc);
+ self.document
+ .window()
+ .reflow_if_reflow_timer_expired(can_gc);
let script = match feed(&self.tokenizer) {
TokenizerResult::Done => return,
TokenizerResult::Script(script) => script,
diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs
index 0c2f8ef57ed..3b7bc966534 100644
--- a/components/script/dom/window.rs
+++ b/components/script/dom/window.rs
@@ -13,7 +13,7 @@ use std::ptr::NonNull;
use std::rc::Rc;
use std::sync::atomic::Ordering;
use std::sync::{Arc, Mutex};
-use std::time::Duration;
+use std::time::{Duration, Instant};
use app_units::Au;
use backtrace::Backtrace;
@@ -72,6 +72,7 @@ use style::media_queries;
use style::parser::ParserContext as CssParserContext;
use style::properties::style_structs::Font;
use style::properties::PropertyId;
+use style::queries::values::PrefersColorScheme;
use style::selector_parser::PseudoElement;
use style::str::HTML_SPACE_CHARACTERS;
use style::stylesheets::{CssRuleType, Origin, UrlExtraData};
@@ -167,20 +168,36 @@ enum WindowState {
Zombie, // Pipeline is closed, but the window hasn't been GCed yet.
}
-/// Extra information concerning the reason for reflowing.
-#[derive(Debug, MallocSizeOf)]
-pub enum ReflowReason {
- CachedPageNeededReflow,
- ElementStateChanged,
- FirstLoad,
- Query,
- RefreshTick,
- RequestAnimationFrame,
- ScrollFromScript,
- UpdateTheRendering,
- Viewport,
- WorkletLoaded,
- ThemeChange,
+/// How long we should wait before performing the initial reflow after `<body>` is parsed,
+/// assuming that `<body>` take this long to parse.
+const INITIAL_REFLOW_DELAY: Duration = Duration::from_millis(200);
+
+/// During loading and parsing, layouts are suppressed to avoid flashing incomplete page
+/// contents.
+///
+/// Exceptions:
+/// - Parsing the body takes so long, that layouts are no longer suppressed in order
+/// to show the user that the page is loading.
+/// - Script triggers a layout query or scroll event in which case, we want to layout
+/// but not display the contents.
+///
+/// For more information see: <https://github.com/servo/servo/pull/6028>.
+#[derive(Clone, Copy, MallocSizeOf)]
+enum LayoutBlocker {
+ /// The first load event hasn't been fired and we have not started to parse the `<body>` yet.
+ WaitingForParse,
+ /// The body is being parsed the `<body>` starting at the `Instant` specified.
+ Parsing(Instant),
+ /// The body finished parsing and the `load` event has been fired or parsing took so
+ /// long, that we are going to do layout anyway. Note that subsequent changes to the body
+ /// can trigger parsing again, but the `Window` stays in this state.
+ FiredLoadEventOrParsingTimerExpired,
+}
+
+impl LayoutBlocker {
+ fn layout_blocked(&self) -> bool {
+ !matches!(self, Self::FiredLoadEventOrParsingTimerExpired)
+ }
}
#[dom_struct]
@@ -226,7 +243,7 @@ pub struct Window {
/// Platform theme.
#[no_trace]
- theme: Cell<Theme>,
+ theme: Cell<PrefersColorScheme>,
/// Parent id associated with this page, if any.
#[no_trace]
@@ -255,10 +272,11 @@ pub struct Window {
#[no_trace]
page_clip_rect: Cell<UntypedRect<Au>>,
- /// Flag to suppress reflows. The first reflow will come either with
- /// RefreshTick or with FirstLoad. Until those first reflows, we want to
- /// suppress others like MissingExplicitReflow.
- suppress_reflow: Cell<bool>,
+ /// See the documentation for [`LayoutBlocker`]. Essentially, this flag prevents
+ /// layouts from happening before the first load event, apart from a few exceptional
+ /// cases.
+ #[no_trace]
+ layout_blocker: Cell<LayoutBlocker>,
/// A channel for communicating results of async scripts back to the webdriver server
#[ignore_malloc_size_of = "channels are hard"]
@@ -1769,7 +1787,6 @@ impl Window {
scroll_id,
scroll_offset: Vector2D::new(-x, -y),
}),
- ReflowReason::ScrollFromScript,
can_gc,
);
}
@@ -1809,41 +1826,30 @@ impl Window {
ScriptThread::handle_tick_all_animations_for_testing(pipeline_id);
}
- pub(crate) fn reflows_suppressed(&self) -> bool {
- self.suppress_reflow.get()
- }
-
/// Reflows the page unconditionally if possible and not suppressed. This method will wait for
/// the layout to complete. 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.
///
/// Returns true if layout actually happened, false otherwise.
+ ///
+ /// NOTE: This method should almost never be called directly! Layout and rendering updates should
+ /// happen as part of the HTML event loop via *update the rendering*.
#[allow(unsafe_code)]
- pub(crate) fn force_reflow(
+ 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!
- match reason {
- ReflowReason::FirstLoad | ReflowReason::RefreshTick => self.suppress_reflow.set(false),
- _ => (),
- }
- let for_display = reflow_goal.needs_display();
+ // If layouts are blocked, we block all layouts that are for display only. Other
+ // layouts (for queries and scrolling) are not blocked, as they do not display
+ // anything and script excpects the layout to be up-to-date after they run.
+ let layout_blocked = self.layout_blocker.get().layout_blocked();
let pipeline_id = self.upcast::<GlobalScope>().pipeline_id();
-
- // If this was just a normal reflow (not triggered by script which forces reflow),
- // and the document is still suppressing reflows, exit early.
- if reflow_goal == ReflowGoal::Full && self.suppress_reflow.get() {
- debug!(
- "Suppressing reflow pipeline {} for reason {:?} before FirstLoad or RefreshTick",
- pipeline_id, reason
- );
+ if reflow_goal == ReflowGoal::UpdateTheRendering && layout_blocked {
+ debug!("Suppressing pre-load-event reflow pipeline {pipeline_id}");
return false;
}
@@ -1860,8 +1866,7 @@ impl Window {
debug!("Not invalidating cached layout values for paint-only reflow.");
}
- debug!("script: performing reflow for reason {:?}", reason);
-
+ debug!("script: performing reflow for goal {reflow_goal:?}");
let marker = if self.need_emit_timeline_marker(TimelineMarkerType::Reflow) {
Some(TimelineMarker::start("Reflow".to_owned()))
} else {
@@ -1873,7 +1878,7 @@ impl Window {
// On debug mode, print the reflow event information.
if self.relayout_event {
- debug_reflow_events(pipeline_id, &reflow_goal, &reason);
+ debug_reflow_events(pipeline_id, &reflow_goal);
}
let document = self.Document();
@@ -1882,6 +1887,7 @@ impl Window {
// If this reflow is for display, ensure webgl canvases are composited with
// up-to-date contents.
+ let for_display = reflow_goal.needs_display();
if for_display {
#[cfg(feature = "webgpu")]
document.flush_dirty_webgpu_canvases();
@@ -1896,11 +1902,6 @@ impl Window {
.or_else(|| document.GetDocumentElement())
.map(|root| root.upcast::<Node>().to_trusted_node_address());
- let theme = match self.theme.get() {
- Theme::Light => style::queries::values::PrefersColorScheme::Light,
- Theme::Dark => style::queries::values::PrefersColorScheme::Dark,
- };
-
// Send new document and relevant styles to layout.
let reflow = ScriptReflow {
reflow_info: Reflow {
@@ -1917,7 +1918,7 @@ impl Window {
pending_restyles,
animation_timeline_value: document.current_animation_timeline_value(),
animations: document.animations().sets.clone(),
- theme,
+ theme: self.theme.get(),
};
self.layout.borrow_mut().reflow(reflow);
@@ -1977,26 +1978,25 @@ impl Window {
true
}
- /// Reflows the page if it's possible to do so and the page is dirty.
+ /// Reflows the page if it's possible to do so and the page is dirty. Returns true if layout
+ /// actually happened, false otherwise.
///
- /// Returns true if layout actually happened, false otherwise.
- /// This return value is useful for script queries, that wait for a lock
- /// that layout might hold if the first layout hasn't happened yet (which
- /// may happen in the only case a query reflow may bail out, that is, if the
- /// viewport size is not present). See #11223 for an example of that.
- pub fn reflow(&self, reflow_goal: ReflowGoal, reason: ReflowReason, can_gc: CanGc) -> bool {
+ /// NOTE: This method should almost never be called directly! Layout and rendering updates
+ /// should happen as part of the HTML event loop via *update the rendering*. Currerntly, the
+ /// only exceptions are script queries and scroll requests.
+ pub(crate) fn reflow(&self, reflow_goal: ReflowGoal, can_gc: CanGc) -> bool {
// Fetch the pending web fonts before layout, in case a font loads during
// the layout.
let pending_web_fonts = self.layout.borrow().waiting_for_web_fonts_to_load();
self.Document().ensure_safe_to_run_script_or_layout();
- let for_display = reflow_goal == ReflowGoal::Full;
let mut issued_reflow = false;
let condition = self.Document().needs_reflow();
- if !for_display || condition.is_some() {
+ let updating_the_rendering = reflow_goal == ReflowGoal::UpdateTheRendering;
+ if !updating_the_rendering || condition.is_some() {
debug!("Reflowing document ({:?})", self.pipeline_id());
- issued_reflow = self.force_reflow(reflow_goal, reason, condition);
+ issued_reflow = self.force_reflow(reflow_goal, condition);
// We shouldn't need a reflow immediately after a
// reflow, except if we're waiting for a deferred paint.
@@ -2004,16 +2004,16 @@ impl Window {
assert!(
{
condition.is_none() ||
- (!for_display &&
+ (!updating_the_rendering &&
condition == Some(ReflowTriggerCondition::PaintPostponed)) ||
- self.suppress_reflow.get()
+ self.layout_blocker.get().layout_blocked()
},
"condition was {:?}",
condition
);
} else {
debug!(
- "Document ({:?}) doesn't need reflow - skipping it (reason {reason:?})",
+ "Document ({:?}) doesn't need reflow - skipping it (goal {reflow_goal:?})",
self.pipeline_id()
);
}
@@ -2044,7 +2044,7 @@ impl Window {
// When all these conditions are met, notify the constellation
// that this pipeline is ready to write the image (from the script thread
// perspective at least).
- if self.prepare_for_screenshot && for_display {
+ if self.prepare_for_screenshot && updating_the_rendering {
// Checks if the html element has reftest-wait attribute present.
// See http://testthewebforward.org/docs/reftests.html
// and https://web-platform-tests.org/writing-tests/crashtest.html
@@ -2076,6 +2076,66 @@ impl Window {
issued_reflow
}
+ /// If parsing has taken a long time and reflows are still waiting for the `load` event,
+ /// start allowing them. See <https://github.com/servo/servo/pull/6028>.
+ pub(crate) fn reflow_if_reflow_timer_expired(&self, can_gc: CanGc) {
+ // Only trigger a long parsing time reflow if we are in the first parse of `<body>`
+ // and it started more than `INITIAL_REFLOW_DELAY` ago.
+ if !matches!(
+ self.layout_blocker.get(),
+ LayoutBlocker::Parsing(instant) if instant + INITIAL_REFLOW_DELAY < Instant::now()
+ ) {
+ return;
+ }
+ self.allow_layout_if_necessary(can_gc);
+ }
+
+ /// Block layout for this `Window` until parsing is done. If parsing takes a long time,
+ /// we want to layout anyway, so schedule a moment in the future for when layouts are
+ /// allowed even though parsing isn't finished and we havne't sent a load event.
+ pub(crate) fn prevent_layout_until_load_event(&self) {
+ // If we have already started parsing or have already fired a load event, then
+ // don't delay the first layout any longer.
+ if !matches!(self.layout_blocker.get(), LayoutBlocker::WaitingForParse) {
+ return;
+ }
+
+ self.layout_blocker
+ .set(LayoutBlocker::Parsing(Instant::now()));
+ }
+
+ /// Inform the [`Window`] that layout is allowed either because `load` has happened
+ /// or because parsing the `<body>` took so long that we cannot wait any longer.
+ pub(crate) fn allow_layout_if_necessary(&self, can_gc: CanGc) {
+ if matches!(
+ self.layout_blocker.get(),
+ LayoutBlocker::FiredLoadEventOrParsingTimerExpired
+ ) {
+ return;
+ }
+
+ self.layout_blocker
+ .set(LayoutBlocker::FiredLoadEventOrParsingTimerExpired);
+ self.Document().set_needs_paint(true);
+
+ // We do this immediately instead of scheduling a future task, because this can
+ // happen if parsing is taking a very long time, which means that the
+ // `ScriptThread` is busy doing the parsing and not doing layouts.
+ //
+ // TOOD(mrobinson): It's expected that this is necessary when in the process of
+ // parsing, as we need to interrupt it to update contents, but why is this
+ // necessary when parsing finishes? Not doing the synchronous update in that case
+ // causes iframe tests to become flaky. It seems there's an issue with the timing of
+ // iframe size updates.
+ //
+ // See <https://github.com/servo/servo/issues/14719>
+ self.reflow(ReflowGoal::UpdateTheRendering, can_gc);
+ }
+
+ pub(crate) fn layout_blocked(&self) -> bool {
+ self.layout_blocker.get().layout_blocked()
+ }
+
/// If writing a screenshot, synchronously update the layout epoch that it set
/// in the constellation.
pub(crate) fn update_constellation_epoch(&self) {
@@ -2095,11 +2155,7 @@ impl Window {
}
pub fn layout_reflow(&self, query_msg: QueryMsg, can_gc: CanGc) -> bool {
- self.reflow(
- ReflowGoal::LayoutQuery(query_msg),
- ReflowReason::Query,
- can_gc,
- )
+ self.reflow(ReflowGoal::LayoutQuery(query_msg), can_gc)
}
pub fn resolved_font_style_query(
@@ -2368,8 +2424,18 @@ impl Window {
self.window_size.get()
}
- pub fn set_theme(&self, theme: Theme) {
- self.theme.set(theme);
+ /// Handle a theme change request, triggering a reflow is any actual change occured.
+ pub(crate) fn handle_theme_change(&self, new_theme: Theme) {
+ let new_theme = match new_theme {
+ Theme::Light => PrefersColorScheme::Light,
+ Theme::Dark => PrefersColorScheme::Dark,
+ };
+
+ if self.theme.get() == new_theme {
+ return;
+ }
+ self.theme.set(new_theme);
+ self.Document().set_needs_paint(true);
}
pub fn get_url(&self) -> ServoUrl {
@@ -2717,7 +2783,7 @@ impl Window {
unhandled_resize_event: Default::default(),
window_size: Cell::new(window_size),
current_viewport: Cell::new(initial_viewport.to_untyped()),
- suppress_reflow: Cell::new(true),
+ layout_blocker: Cell::new(LayoutBlocker::WaitingForParse),
current_state: Cell::new(WindowState::Alive),
devtools_marker_sender: Default::default(),
devtools_markers: Default::default(),
@@ -2747,7 +2813,7 @@ impl Window {
throttled: Cell::new(false),
layout_marker: DomRefCell::new(Rc::new(Cell::new(true))),
current_event: DomRefCell::new(None),
- theme: Cell::new(Theme::Light),
+ theme: Cell::new(PrefersColorScheme::Light),
});
unsafe { WindowBinding::Wrap(JSContext::from_ptr(runtime.cx()), win) }
@@ -2825,10 +2891,9 @@ fn should_move_clip_rect(clip_rect: UntypedRect<Au>, new_viewport: UntypedRect<f
(clip_rect.max_y() - new_viewport.max_y()).abs() <= viewport_scroll_margin.height
}
-fn debug_reflow_events(id: PipelineId, reflow_goal: &ReflowGoal, reason: &ReflowReason) {
+fn debug_reflow_events(id: PipelineId, reflow_goal: &ReflowGoal) {
let goal_string = match *reflow_goal {
- ReflowGoal::Full => "\tFull",
- ReflowGoal::TickAnimations => "\tTickAnimations",
+ ReflowGoal::UpdateTheRendering => "\tFull",
ReflowGoal::UpdateScrollNode(_) => "\tUpdateScrollNode",
ReflowGoal::LayoutQuery(ref query_msg) => match *query_msg {
QueryMsg::ContentBox => "\tContentBoxQuery",
@@ -2846,7 +2911,7 @@ fn debug_reflow_events(id: PipelineId, reflow_goal: &ReflowGoal, reason: &Reflow
},
};
- println!("**** pipeline={}\t{}\t{:?}", id, goal_string, reason);
+ println!("**** pipeline={id}\t{goal_string}");
}
impl Window {
diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs
index 095d3eefbf6..4730b48600d 100644
--- a/components/script/script_thread.rs
+++ b/components/script/script_thread.rs
@@ -137,7 +137,7 @@ use crate::dom::serviceworker::TrustedServiceWorkerAddress;
use crate::dom::servoparser::{ParserContext, ServoParser};
#[cfg(feature = "webgpu")]
use crate::dom::webgpu::identityhub::IdentityHub;
-use crate::dom::window::{ReflowReason, Window};
+use crate::dom::window::Window;
use crate::dom::windowproxy::{CreatorBrowsingContextInfo, WindowProxy};
use crate::dom::worker::TrustedWorkerAddress;
use crate::dom::worklet::WorkletThreadPool;
@@ -1625,7 +1625,7 @@ impl ScriptThread {
// > doc and its node navigable to reflect the current state.
let window = document.window();
if document.is_fully_active() {
- window.reflow(ReflowGoal::Full, ReflowReason::UpdateTheRendering, can_gc);
+ window.reflow(ReflowGoal::UpdateTheRendering, can_gc);
}
// TODO: Process top layer removals according to
@@ -1660,7 +1660,7 @@ impl ScriptThread {
}
let Some((_, document)) = self.documents.borrow().iter().find(|(_, document)| {
- !document.window().reflows_suppressed() && document.needs_reflow().is_some()
+ !document.window().layout_blocked() && document.needs_reflow().is_some()
}) else {
return;
};
@@ -2254,7 +2254,7 @@ impl ScriptThread {
self.handle_resize_inactive_msg(id, new_size)
},
ConstellationControlMsg::ThemeChange(_, theme) => {
- self.handle_theme_change(theme);
+ self.handle_theme_change_msg(theme);
},
ConstellationControlMsg::GetTitle(pipeline_id) => {
self.handle_get_title_msg(pipeline_id)
@@ -2846,12 +2846,10 @@ impl ScriptThread {
})
}
- fn handle_theme_change(&self, theme: Theme) {
- let docs = self.documents.borrow();
- for (_, document) in docs.iter() {
- let window = document.window();
- window.set_theme(theme);
- window.force_reflow(ReflowGoal::Full, ReflowReason::ThemeChange, None);
+ /// Handle changes to the theme, triggering reflow if the theme actually changed.
+ fn handle_theme_change_msg(&self, theme: Theme) {
+ for (_, document) in self.documents.borrow().iter() {
+ document.window().handle_theme_change(theme);
}
}
@@ -2972,7 +2970,7 @@ impl ScriptThread {
);
let document = self.documents.borrow().find_document(id);
if let Some(document) = document {
- document.set_activity(activity, CanGc::note());
+ document.set_activity(activity);
return;
}
let mut loads = self.incomplete_loads.borrow_mut();