Skip to content

Commit 3e856cb

Browse files
authored
layout: Introduce ReflowPhasesRun (servo#38467)
There were various booleans on `ReflowResults` that represented various actions that might have been taken during a reflow request. Replace those with a bitflags that better represents what reflow phases have actually been run. Update variable names to reflect what they mean. In addition, run some post-layout tasks unconditionally. They are already contingent on the results returned from layout. This simplifies and clarifies the code a good deal. Testing: This should not change observable behavior and thus is covered by existing WPT tests. Signed-off-by: Martin Robinson <[email protected]>
1 parent 92a9d24 commit 3e856cb

File tree

5 files changed

+102
-115
lines changed

5 files changed

+102
-115
lines changed

components/layout/layout_impl.rs

Lines changed: 45 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ use fxhash::FxHashMap;
2727
use ipc_channel::ipc::IpcSender;
2828
use layout_api::{
2929
IFrameSizes, Layout, LayoutConfig, LayoutDamage, LayoutFactory, NodesFromPointQueryType,
30-
OffsetParentResponse, QueryMsg, ReflowGoal, ReflowRequest, ReflowRequestRestyle, ReflowResult,
31-
TrustedNodeAddress,
30+
OffsetParentResponse, QueryMsg, ReflowGoal, ReflowPhasesRun, ReflowRequest,
31+
ReflowRequestRestyle, ReflowResult, TrustedNodeAddress,
3232
};
3333
use log::{debug, error, warn};
3434
use malloc_size_of::{MallocConditionalSizeOf, MallocSizeOf, MallocSizeOfOps};
@@ -672,13 +672,13 @@ impl LayoutThread {
672672
self.maybe_print_reflow_event(&reflow_request);
673673

674674
if self.can_skip_reflow_request_entirely(&reflow_request) {
675-
// We could skip the layout, but we might need to update the scroll node.
676-
let update_scroll_reflow_target_scrolled =
677-
self.handle_update_scroll_node_request(&reflow_request);
678-
679-
return Some(ReflowResult::new_without_relayout(
680-
update_scroll_reflow_target_scrolled,
681-
));
675+
// We can skip layout, but we might need to update a scroll node.
676+
return self
677+
.handle_update_scroll_node_request(&reflow_request)
678+
.then(|| ReflowResult {
679+
reflow_phases_run: ReflowPhasesRun::UpdatedScrollNodeOffset,
680+
..Default::default()
681+
});
682682
}
683683

684684
let document = unsafe { ServoLayoutNode::new(&reflow_request.document) };
@@ -698,30 +698,34 @@ impl LayoutThread {
698698
animation_timeline_value: reflow_request.animation_timeline_value,
699699
});
700700

701-
let (damage, iframe_sizes) = self.restyle_and_build_trees(
701+
let (mut reflow_phases_run, damage, iframe_sizes) = self.restyle_and_build_trees(
702702
&mut reflow_request,
703703
document,
704704
root_element,
705705
&image_resolver,
706706
);
707-
self.calculate_overflow(damage);
708-
self.build_stacking_context_tree(&reflow_request, damage);
709-
let built_display_list = self.build_display_list(&reflow_request, damage, &image_resolver);
710-
711-
let update_scroll_reflow_target_scrolled =
712-
self.handle_update_scroll_node_request(&reflow_request);
707+
if self.calculate_overflow(damage) {
708+
reflow_phases_run.insert(ReflowPhasesRun::CalculatedOverflow);
709+
}
710+
if self.build_stacking_context_tree(&reflow_request, damage) {
711+
reflow_phases_run.insert(ReflowPhasesRun::BuiltStackingContextTree);
712+
}
713+
if self.build_display_list(&reflow_request, damage, &image_resolver) {
714+
reflow_phases_run.insert(ReflowPhasesRun::BuiltDisplayList);
715+
}
716+
if self.handle_update_scroll_node_request(&reflow_request) {
717+
reflow_phases_run.insert(ReflowPhasesRun::UpdatedScrollNodeOffset);
718+
}
713719

714720
let pending_images = std::mem::take(&mut *image_resolver.pending_images.lock());
715721
let pending_rasterization_images =
716722
std::mem::take(&mut *image_resolver.pending_rasterization_images.lock());
717723

718724
Some(ReflowResult {
719-
built_display_list,
725+
reflow_phases_run,
720726
pending_images,
721727
pending_rasterization_images,
722728
iframe_sizes: Some(iframe_sizes),
723-
update_scroll_reflow_target_scrolled,
724-
processed_relayout: true,
725729
})
726730
}
727731

@@ -793,11 +797,11 @@ impl LayoutThread {
793797
document: ServoLayoutDocument<'_>,
794798
root_element: ServoLayoutElement<'_>,
795799
image_resolver: &Arc<ImageResolver>,
796-
) -> (RestyleDamage, IFrameSizes) {
800+
) -> (ReflowPhasesRun, RestyleDamage, IFrameSizes) {
797801
let mut snapshot_map = SnapshotMap::new();
798802
let _snapshot_setter = match reflow_request.restyle.as_mut() {
799803
Some(restyle) => SnapshotSetter::new(restyle, &mut snapshot_map),
800-
None => return (RestyleDamage::empty(), IFrameSizes::default()),
804+
None => return Default::default(),
801805
};
802806

803807
let document_shared_lock = document.style_shared_lock();
@@ -877,7 +881,7 @@ impl LayoutThread {
877881

878882
if !token.should_traverse() {
879883
layout_context.style_context.stylist.rule_tree().maybe_gc();
880-
return (RestyleDamage::empty(), IFrameSizes::default());
884+
return Default::default();
881885
}
882886

883887
dirty_root = driver::traverse_dom(&recalc_style_traversal, token, rayon_pool).as_node();
@@ -895,7 +899,7 @@ impl LayoutThread {
895899

896900
if !damage.contains(RestyleDamage::RELAYOUT) {
897901
layout_context.style_context.stylist.rule_tree().maybe_gc();
898-
return (damage, IFrameSizes::default());
902+
return (ReflowPhasesRun::empty(), damage, IFrameSizes::default());
899903
}
900904

901905
let mut box_tree = self.box_tree.borrow_mut();
@@ -956,13 +960,17 @@ impl LayoutThread {
956960
layout_context.style_context.stylist.rule_tree().maybe_gc();
957961

958962
let mut iframe_sizes = layout_context.iframe_sizes.lock();
959-
(damage, std::mem::take(&mut *iframe_sizes))
963+
(
964+
ReflowPhasesRun::RanLayout,
965+
damage,
966+
std::mem::take(&mut *iframe_sizes),
967+
)
960968
}
961969

962970
#[servo_tracing::instrument(name = "Overflow Calculation", skip_all)]
963-
fn calculate_overflow(&self, damage: RestyleDamage) {
971+
fn calculate_overflow(&self, damage: RestyleDamage) -> bool {
964972
if !damage.contains(RestyleDamage::RECALCULATE_OVERFLOW) {
965-
return;
973+
return false;
966974
}
967975

968976
if let Some(fragment_tree) = &*self.fragment_tree.borrow() {
@@ -976,22 +984,27 @@ impl LayoutThread {
976984
// display list the next time one is requested.
977985
self.need_new_display_list.set(true);
978986
self.need_new_stacking_context_tree.set(true);
987+
true
979988
}
980989

981990
#[servo_tracing::instrument(name = "Stacking Context Tree Construction", skip_all)]
982-
fn build_stacking_context_tree(&self, reflow_request: &ReflowRequest, damage: RestyleDamage) {
991+
fn build_stacking_context_tree(
992+
&self,
993+
reflow_request: &ReflowRequest,
994+
damage: RestyleDamage,
995+
) -> bool {
983996
if !ReflowPhases::necessary(&reflow_request.reflow_goal)
984997
.contains(ReflowPhases::StackingContextTreeConstruction)
985998
{
986-
return;
999+
return false;
9871000
}
9881001
let Some(fragment_tree) = &*self.fragment_tree.borrow() else {
989-
return;
1002+
return false;
9901003
};
9911004
if !damage.contains(RestyleDamage::REBUILD_STACKING_CONTEXT) &&
9921005
!self.need_new_stacking_context_tree.get()
9931006
{
994-
return;
1007+
return false;
9951008
}
9961009

9971010
let mut stacking_context_tree = self.stacking_context_tree.borrow_mut();
@@ -1041,6 +1054,8 @@ impl LayoutThread {
10411054
);
10421055
}
10431056
}
1057+
1058+
true
10441059
}
10451060

10461061
/// Build the display list for the current layout and send it to the renderer. If no display

components/script/dom/document.rs

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ use ipc_channel::ipc;
4343
use js::rust::{HandleObject, HandleValue, MutableHandleValue};
4444
use keyboard_types::{Code, Key, KeyState, Modifiers, NamedKey};
4545
use layout_api::{
46-
PendingRestyle, ReflowGoal, RestyleReason, TrustedNodeAddress, node_id_from_scroll_id,
46+
PendingRestyle, ReflowGoal, ReflowPhasesRun, RestyleReason, TrustedNodeAddress,
47+
node_id_from_scroll_id,
4748
};
4849
use metrics::{InteractiveFlag, InteractiveWindow, ProgressiveWebMetrics};
4950
use net_traits::CookieSource::NonHTTP;
@@ -3694,8 +3695,8 @@ impl Document {
36943695
// > Step 22: For each doc of docs, update the rendering or user interface of
36953696
// > doc and its node navigable to reflect the current state.
36963697
//
3697-
// Returns true if a reflow occured.
3698-
pub(crate) fn update_the_rendering(&self) -> bool {
3698+
// Returns the set of reflow phases run as a [`ReflowPhasesRun`].
3699+
pub(crate) fn update_the_rendering(&self) -> ReflowPhasesRun {
36993700
self.update_animating_images();
37003701

37013702
// All dirty canvases are flushed before updating the rendering.
@@ -3730,9 +3731,7 @@ impl Document {
37303731
receiver.recv().unwrap();
37313732
}
37323733

3733-
self.window()
3734-
.reflow(ReflowGoal::UpdateTheRendering)
3735-
.reflow_issued
3734+
self.window().reflow(ReflowGoal::UpdateTheRendering)
37363735
}
37373736

37383737
/// From <https://drafts.csswg.org/css-font-loading/#fontfaceset-pending-on-the-environment>:

components/script/dom/window.rs

Lines changed: 28 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -52,8 +52,8 @@ use js::rust::{
5252
};
5353
use layout_api::{
5454
FragmentType, Layout, PendingImage, PendingImageState, PendingRasterizationImage, QueryMsg,
55-
ReflowGoal, ReflowRequest, ReflowRequestRestyle, RestyleReason, TrustedNodeAddress,
56-
combine_id_with_fragment_type,
55+
ReflowGoal, ReflowPhasesRun, ReflowRequest, ReflowRequestRestyle, RestyleReason,
56+
TrustedNodeAddress, combine_id_with_fragment_type,
5757
};
5858
use malloc_size_of::MallocSizeOf;
5959
use media::WindowGLContext;
@@ -225,25 +225,6 @@ impl LayoutBlocker {
225225

226226
type PendingImageRasterizationKey = (PendingImageId, DeviceIntSize);
227227

228-
/// Feedbacks of the reflow that is required by the one who is initiating the reflow.
229-
pub(crate) struct WindowReflowResult {
230-
/// Whether the reflow actually happened and it sends a new display list to the embedder.
231-
pub reflow_issued: bool,
232-
/// Whether the reflow is for [ReflowGoal::UpdateScrollNode] and the target is scrolled.
233-
/// Specifically, a node is scrolled whenever the scroll position of it changes. Note
234-
/// that reflow that is cancalled would not scroll the target.
235-
pub update_scroll_reflow_target_scrolled: bool,
236-
}
237-
238-
impl WindowReflowResult {
239-
fn new_empty() -> Self {
240-
WindowReflowResult {
241-
reflow_issued: false,
242-
update_scroll_reflow_target_scrolled: false,
243-
}
244-
}
245-
}
246-
247228
#[dom_struct]
248229
pub(crate) struct Window {
249230
globalscope: GlobalScope,
@@ -2165,16 +2146,14 @@ impl Window {
21652146
// TODO Step 1
21662147
// TODO(mrobinson, #18709): Add smooth scrolling support to WebRender so that we can
21672148
// properly process ScrollBehavior here.
2168-
let WindowReflowResult {
2169-
update_scroll_reflow_target_scrolled,
2170-
..
2171-
} = self.reflow(ReflowGoal::UpdateScrollNode(scroll_id, Vector2D::new(x, y)));
2149+
let reflow_phases_run =
2150+
self.reflow(ReflowGoal::UpdateScrollNode(scroll_id, Vector2D::new(x, y)));
21722151

21732152
// > If the scroll position did not change as a result of the user interaction or programmatic
21742153
// > invocation, where no translations were applied as a result, then no scrollend event fires
21752154
// > because no scrolling occurred.
21762155
// Even though the note mention the scrollend, it is relevant to the scroll as well.
2177-
if update_scroll_reflow_target_scrolled {
2156+
if reflow_phases_run.contains(ReflowPhasesRun::UpdatedScrollNodeOffset) {
21782157
match element {
21792158
Some(el) => self.Document().handle_element_scroll_event(el),
21802159
None => self.Document().handle_viewport_scroll_event(),
@@ -2210,25 +2189,25 @@ impl Window {
22102189
///
22112190
/// NOTE: This method should almost never be called directly! Layout and rendering updates should
22122191
/// happen as part of the HTML event loop via *update the rendering*.
2213-
pub(crate) fn reflow(&self, reflow_goal: ReflowGoal) -> WindowReflowResult {
2192+
pub(crate) fn reflow(&self, reflow_goal: ReflowGoal) -> ReflowPhasesRun {
22142193
let document = self.Document();
22152194

22162195
// Never reflow inactive Documents.
22172196
if !document.is_fully_active() {
2218-
return WindowReflowResult::new_empty();
2197+
return ReflowPhasesRun::empty();
22192198
}
22202199

22212200
self.Document().ensure_safe_to_run_script_or_layout();
22222201

22232202
// If layouts are blocked, we block all layouts that are for display only. Other
22242203
// layouts (for queries and scrolling) are not blocked, as they do not display
2225-
// anything and script excpects the layout to be up-to-date after they run.
2204+
// anything and script expects the layout to be up-to-date after they run.
22262205
let pipeline_id = self.pipeline_id();
22272206
if reflow_goal == ReflowGoal::UpdateTheRendering &&
22282207
self.layout_blocker.get().layout_blocked()
22292208
{
22302209
debug!("Suppressing pre-load-event reflow pipeline {pipeline_id}");
2231-
return WindowReflowResult::new_empty();
2210+
return ReflowPhasesRun::empty();
22322211
}
22332212

22342213
debug!("script: performing reflow for goal {reflow_goal:?}");
@@ -2279,37 +2258,30 @@ impl Window {
22792258
highlighted_dom_node: document.highlighted_dom_node().map(|node| node.to_opaque()),
22802259
};
22812260

2282-
let Some(results) = self.layout.borrow_mut().reflow(reflow) else {
2283-
return WindowReflowResult::new_empty();
2261+
let Some(reflow_result) = self.layout.borrow_mut().reflow(reflow) else {
2262+
return ReflowPhasesRun::empty();
22842263
};
22852264

2286-
// We are maintaining the previous behavior of layout where we are skipping these behavior if we are not
2287-
// doing layout calculation.
2288-
if results.processed_relayout {
2289-
debug!("script: layout complete");
2290-
if let Some(marker) = marker {
2291-
self.emit_timeline_marker(marker.end());
2292-
}
2293-
2294-
self.handle_pending_images_post_reflow(
2295-
results.pending_images,
2296-
results.pending_rasterization_images,
2297-
);
2298-
if let Some(iframe_sizes) = results.iframe_sizes {
2299-
document
2300-
.iframes_mut()
2301-
.handle_new_iframe_sizes_after_layout(self, iframe_sizes);
2302-
}
2303-
document.update_animations_post_reflow();
2304-
self.update_constellation_epoch();
2305-
} else {
2306-
debug!("script: layout-side reflow finished without relayout");
2265+
debug!("script: layout complete");
2266+
if let Some(marker) = marker {
2267+
self.emit_timeline_marker(marker.end());
23072268
}
23082269

2309-
WindowReflowResult {
2310-
reflow_issued: results.built_display_list,
2311-
update_scroll_reflow_target_scrolled: results.update_scroll_reflow_target_scrolled,
2270+
self.handle_pending_images_post_reflow(
2271+
reflow_result.pending_images,
2272+
reflow_result.pending_rasterization_images,
2273+
);
2274+
2275+
if let Some(iframe_sizes) = reflow_result.iframe_sizes {
2276+
document
2277+
.iframes_mut()
2278+
.handle_new_iframe_sizes_after_layout(self, iframe_sizes);
23122279
}
2280+
2281+
document.update_animations_post_reflow();
2282+
self.update_constellation_epoch();
2283+
2284+
reflow_result.reflow_phases_run
23132285
}
23142286

23152287
pub(crate) fn maybe_send_idle_document_state_to_constellation(&self) {

components/script/script_thread.rs

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,9 @@ use js::jsapi::{
6868
};
6969
use js::jsval::UndefinedValue;
7070
use js::rust::ParentRuntime;
71-
use layout_api::{LayoutConfig, LayoutFactory, RestyleReason, ScriptThreadFactory};
71+
use layout_api::{
72+
LayoutConfig, LayoutFactory, ReflowPhasesRun, RestyleReason, ScriptThreadFactory,
73+
};
7274
use media::WindowGLContext;
7375
use metrics::MAX_TASK_NS;
7476
use net_traits::image_cache::{ImageCache, ImageCacheResponseMessage};
@@ -1238,6 +1240,8 @@ impl ScriptThread {
12381240
///
12391241
/// Attempt to update the rendering and then do a microtask checkpoint if rendering was actually
12401242
/// updated.
1243+
///
1244+
/// Returns true if any reflows produced a new display list.
12411245
pub(crate) fn update_the_rendering(&self, can_gc: CanGc) -> bool {
12421246
self.last_render_opportunity_time.set(Some(Instant::now()));
12431247
self.cancel_scheduled_update_the_rendering();
@@ -1275,7 +1279,7 @@ impl ScriptThread {
12751279
// steps per doc in docs. Currently `<iframe>` resizing depends on a parent being able to
12761280
// queue resize events on a child and have those run in the same call to this method, so
12771281
// that needs to be sorted out to fix this.
1278-
let mut saw_any_reflows = false;
1282+
let mut built_any_display_lists = false;
12791283
for pipeline_id in documents_in_order.iter() {
12801284
let document = self
12811285
.documents
@@ -1358,7 +1362,10 @@ impl ScriptThread {
13581362

13591363
// > Step 22: For each doc of docs, update the rendering or user interface of
13601364
// > doc and its node navigable to reflect the current state.
1361-
saw_any_reflows = document.update_the_rendering() || saw_any_reflows;
1365+
built_any_display_lists = document
1366+
.update_the_rendering()
1367+
.contains(ReflowPhasesRun::BuiltDisplayList) ||
1368+
built_any_display_lists;
13621369

13631370
// TODO: Process top layer removals according to
13641371
// https://drafts.csswg.org/css-position-4/#process-top-layer-removals.
@@ -1367,7 +1374,7 @@ impl ScriptThread {
13671374
// Perform a microtask checkpoint as the specifications says that *update the rendering*
13681375
// should be run in a task and a microtask checkpoint is always done when running tasks.
13691376
self.perform_a_microtask_checkpoint(can_gc);
1370-
saw_any_reflows
1377+
built_any_display_lists
13711378
}
13721379

13731380
/// Schedule a rendering update ("update the rendering"), if necessary. This

0 commit comments

Comments
 (0)