layout: Add a ReflowPhases bitflags (#37696)

This is used to capture information about what layout phases are
necessary for a given `ReflowGoal`. It's moved closer to where these
decisions are made and it should be easier to understand what the values
mean. They had gotten a bit out of sync with how queries and layout were
implemented.

Testing: This shouldn't change observable behavior and thus should be
covered
by existing WPT tests.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson 2025-06-26 11:31:16 +02:00 committed by GitHub
parent 03dbf9b6f7
commit 3cda9f2fb2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 65 additions and 60 deletions

View file

@ -14,6 +14,7 @@ use std::sync::{Arc, LazyLock};
use app_units::Au; use app_units::Au;
use base::Epoch; use base::Epoch;
use base::id::{PipelineId, WebViewId}; use base::id::{PipelineId, WebViewId};
use bitflags::bitflags;
use compositing_traits::CrossProcessCompositorApi; use compositing_traits::CrossProcessCompositorApi;
use compositing_traits::display_list::ScrollType; use compositing_traits::display_list::ScrollType;
use embedder_traits::{Theme, UntrustedNodeAddress, ViewportDetails}; use embedder_traits::{Theme, UntrustedNodeAddress, ViewportDetails};
@ -25,8 +26,8 @@ use fonts_traits::StylesheetWebFontLoadFinishedCallback;
use fxhash::FxHashMap; use fxhash::FxHashMap;
use ipc_channel::ipc::IpcSender; use ipc_channel::ipc::IpcSender;
use layout_api::{ use layout_api::{
Layout, LayoutConfig, LayoutFactory, NodesFromPointQueryType, OffsetParentResponse, ReflowGoal, Layout, LayoutConfig, LayoutFactory, NodesFromPointQueryType, OffsetParentResponse, QueryMsg,
ReflowRequest, ReflowResult, TrustedNodeAddress, ReflowGoal, ReflowRequest, ReflowResult, TrustedNodeAddress,
}; };
use log::{debug, error, warn}; use log::{debug, error, warn};
use malloc_size_of::{MallocConditionalSizeOf, MallocSizeOf, MallocSizeOfOps}; use malloc_size_of::{MallocConditionalSizeOf, MallocSizeOf, MallocSizeOfOps};
@ -588,21 +589,31 @@ impl LayoutThread {
if reflow_request.restyle_reason.needs_restyle() { if reflow_request.restyle_reason.needs_restyle() {
return false; return false;
} }
// We always need to at least build a fragment tree.
if !self.fragment_tree.borrow().is_none() {
return false;
}
// If only the fragment tree is required, and it's up-to-date, layout is unnecessary. // If we have a fragment tree and it's up-to-date and this reflow
if !reflow_request.reflow_goal.needs_display() && self.fragment_tree.borrow().is_some() { // doesn't need more reflow results, we can skip the rest of layout.
let necessary_phases = ReflowPhases::necessary(&reflow_request.reflow_goal);
if necessary_phases.is_empty() {
return true; return true;
} }
// If only the stacking context tree is required, and it's up-to-date, layout is unnecessary. // If only the stacking context tree is required, and it's up-to-date,
if !reflow_request.reflow_goal.needs_display_list() && // layout is unnecessary, otherwise a layout is necessary.
self.stacking_context_tree.borrow().is_some() && if necessary_phases == ReflowPhases::StackingContextTreeConstruction {
!self.need_new_stacking_context_tree.get() return self.stacking_context_tree.borrow().is_some() &&
{ !self.need_new_stacking_context_tree.get();
return true;
} }
// Otherwise, the only interesting thing is whether the current display list is up-to-date. // Otherwise, the only interesting thing is whether the current display
// list is up-to-date.
assert_eq!(
necessary_phases,
ReflowPhases::StackingContextTreeConstruction | ReflowPhases::DisplayListConstruction
);
!self.need_new_display_list.get() !self.need_new_display_list.get()
} }
@ -910,8 +921,8 @@ impl LayoutThread {
} }
fn build_stacking_context_tree(&self, reflow_request: &ReflowRequest, damage: RestyleDamage) { fn build_stacking_context_tree(&self, reflow_request: &ReflowRequest, damage: RestyleDamage) {
if !reflow_request.reflow_goal.needs_display_list() && if !ReflowPhases::necessary(&reflow_request.reflow_goal)
!reflow_request.reflow_goal.needs_display() .contains(ReflowPhases::StackingContextTreeConstruction)
{ {
return; return;
} }
@ -973,7 +984,9 @@ impl LayoutThread {
damage: RestyleDamage, damage: RestyleDamage,
layout_context: &mut LayoutContext<'_>, layout_context: &mut LayoutContext<'_>,
) -> bool { ) -> bool {
if !reflow_request.reflow_goal.needs_display() { if !ReflowPhases::necessary(&reflow_request.reflow_goal)
.contains(ReflowPhases::DisplayListConstruction)
{
return false; return false;
} }
let Some(fragment_tree) = &*self.fragment_tree.borrow() else { let Some(fragment_tree) = &*self.fragment_tree.borrow() else {
@ -1363,3 +1376,41 @@ impl Drop for SnapshotSetter<'_> {
} }
} }
} }
bitflags! {
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct ReflowPhases: u8 {
const StackingContextTreeConstruction = 1 << 0;
const DisplayListConstruction = 1 << 1;
}
}
impl ReflowPhases {
/// Return the necessary phases of layout for the given [`ReflowGoal`]. Note that all
/// [`ReflowGoals`] need the basic restyle + box tree layout + fragment tree layout,
/// so [`ReflowPhases::empty()`] implies that.
fn necessary(reflow_goal: &ReflowGoal) -> Self {
match reflow_goal {
ReflowGoal::UpdateTheRendering | ReflowGoal::UpdateScrollNode(..) => {
Self::StackingContextTreeConstruction | Self::DisplayListConstruction
},
ReflowGoal::LayoutQuery(query) => match query {
QueryMsg::NodesFromPointQuery => {
Self::StackingContextTreeConstruction | Self::DisplayListConstruction
},
QueryMsg::ResolvedStyleQuery | QueryMsg::ScrollingAreaOrOffsetQuery => {
Self::StackingContextTreeConstruction
},
QueryMsg::ClientRectQuery |
QueryMsg::ContentBox |
QueryMsg::ContentBoxes |
QueryMsg::ElementInnerOuterTextQuery |
QueryMsg::InnerWindowDimensionsQuery |
QueryMsg::OffsetParentQuery |
QueryMsg::ResolvedFontStyleQuery |
QueryMsg::TextIndexQuery |
QueryMsg::StyleQuery => Self::empty(),
},
}
}
}

View file

@ -346,52 +346,6 @@ pub enum ReflowGoal {
UpdateScrollNode(ExternalScrollId, LayoutVector2D), UpdateScrollNode(ExternalScrollId, LayoutVector2D),
} }
impl ReflowGoal {
/// Returns true if the given ReflowQuery needs a full, up-to-date display list to
/// be present or false if it only needs stacking-relative positions.
pub fn needs_display_list(&self) -> bool {
match *self {
ReflowGoal::UpdateTheRendering | ReflowGoal::UpdateScrollNode(..) => true,
ReflowGoal::LayoutQuery(ref querymsg) => match *querymsg {
QueryMsg::ElementInnerOuterTextQuery |
QueryMsg::InnerWindowDimensionsQuery |
QueryMsg::NodesFromPointQuery |
QueryMsg::ResolvedStyleQuery |
QueryMsg::ScrollingAreaOrOffsetQuery |
QueryMsg::TextIndexQuery => true,
QueryMsg::ClientRectQuery |
QueryMsg::ContentBox |
QueryMsg::ContentBoxes |
QueryMsg::OffsetParentQuery |
QueryMsg::ResolvedFontStyleQuery |
QueryMsg::StyleQuery => false,
},
}
}
/// Returns true if the given ReflowQuery needs its display list send to WebRender or
/// false if a layout_thread display list is sufficient.
pub fn needs_display(&self) -> bool {
match *self {
ReflowGoal::UpdateTheRendering | ReflowGoal::UpdateScrollNode(..) => true,
ReflowGoal::LayoutQuery(ref querymsg) => match *querymsg {
QueryMsg::NodesFromPointQuery |
QueryMsg::TextIndexQuery |
QueryMsg::ElementInnerOuterTextQuery => true,
QueryMsg::ContentBox |
QueryMsg::ContentBoxes |
QueryMsg::ClientRectQuery |
QueryMsg::ScrollingAreaOrOffsetQuery |
QueryMsg::ResolvedStyleQuery |
QueryMsg::ResolvedFontStyleQuery |
QueryMsg::OffsetParentQuery |
QueryMsg::InnerWindowDimensionsQuery |
QueryMsg::StyleQuery => false,
},
}
}
}
#[derive(Clone, Debug, MallocSizeOf)] #[derive(Clone, Debug, MallocSizeOf)]
pub struct IFrameSize { pub struct IFrameSize {
pub browsing_context_id: BrowsingContextId, pub browsing_context_id: BrowsingContextId,