servo/components/layout_thread/lib.rs
Oriol Brufau 8e15389cae
Backport several style changes from Gecko (5) (#30099)
* style: Simplify selector flag setting now that flag setting is atomic

These bits are write-only, actually, and we don't even need to read
them.

Differential Revision: https://phabricator.services.mozilla.com/D141888

* Further changes required by Servo

* style: Support media queries for dynamic-range and video-dynamic-range

This is a stub that only matches "standard" for all platforms.

Differential Revision: https://phabricator.services.mozilla.com/D141053

* style: Remove :-moz-lwtheme-{brighttext,darktext}

They are just convenience for :root[lwthemetextcolor="light"] (and dark,
respectively), but they generally shouldn't be used for dark mode
theming. In the past it was the only way to do it but now we have
prefers-color-scheme.

While at it, change lwthemetextcolor to be "lwtheme-brighttext" for
consistency with similar code we have for popups etc, and move it to
_setDarkModeAttributes.

While at it, remove layout.css.moz-lwtheme.content.enabled (which is
false always, we unshipped these from content successfully).

Differential Revision: https://phabricator.services.mozilla.com/D141593

* style: layout.css.moz-locale-dir.content.enabled

We successfully removed these from content in bug 1740230 (Firefox 96).

Differential Revision: https://phabricator.services.mozilla.com/D141727

* style: Really honor background-color: transparent in HCM even for form controls

I forgot we were doing this "revert-or-initial" shenanigans (which is needed
for stuff like link colors to be honored), so we need to early-return.

Use a more explicit test rather than a reftest for this.

Differential Revision: https://phabricator.services.mozilla.com/D142063

* style: Ignore unchanged property for scroll-linked effect detector

I think this is cleaner.

Differential Revision: https://phabricator.services.mozilla.com/D141737

* style: Allow to derive Parse/ToCss/SpecifiedValueInfo on bitflags

We keep getting this pattern of properties that have a set of joint and
disjoint flags, and copy-pasting or writing the same parsing and
serialization code in slightly different ways.

container-type is one such type, and I think we should have a single way
of dealing with this, thus implement deriving for various traits for
bitflags, with an attribute that says which flags are single vs mixed.

See docs and properties I ported. The remaining ones I left TODOs with,
they are a bit trickier but can be ported with some care.

Differential Revision: https://phabricator.services.mozilla.com/D142418

* Further changes required by Servo

* style: Implement parsing / serialization for container{,-type,-name} CSS properties

Two noteworthy details that may seem random otherwise:

 * Moving values around in nsStyleDisplay is needed so that the struct
   remains under the size limit that we have to avoid jumping allocator
   buckets.

 * All the test expectation churn is because tests depend on
   `container-type: size` parsing to run, and now they run. Tests for
   the relevant bits I implemented are passing, with the only exception
   of some `container-name-computed.html` failures which are
   https://github.com/w3c/csswg-drafts/issues/7181. Safari agrees with
   us there.

Other notes when looking at the spec and seeing how it matches the
implementation:

 * `container` syntax doesn't match spec, but matches tests and sanity:
   https://github.com/w3c/csswg-drafts/issues/7180

 * `container-type` syntax doesn't _quite_ match spec, but matches tests
   and I think it's a spec bug since the definition for the missing
   keyword is gone:
   https://github.com/w3c/csswg-drafts/issues/7179

Differential Revision: https://phabricator.services.mozilla.com/D142419

* style: Remove assert that doesn't hold for text-decorations because of presentation hints

MANUAL PUSH: Orange fix CLOSED TREE

* style: Migrate `<th>` `text-align` behaviour from presentation hint to UA CSS

Differential Revision: https://phabricator.services.mozilla.com/D142494

* style: Deduplicate TokenList values faster

Remember whether we have already de-duplicated them once and avoid doing
that again.

This is an alternative approach that doesn't add overhead to attribute
setting in the general case.

Differential Revision: https://phabricator.services.mozilla.com/D142813

* style: Inherit used color-scheme from embedder <browser> elements

This allows popups and sidebars to use the chrome preferred
color-scheme.

This moves the responsibility of setting the content-preferred color
scheme to the appropriate browsers to the front-end (via tabs.css).

We still return the PreferredColorSchemeForContent() when there's no
pres context (e.g., for display:none in-process iframes). We could
potentially move a bunch of the pres-context data to the document
instead, but that should be acceptable IMO as for general web content
there's no behavior change in any case.

Differential Revision: https://phabricator.services.mozilla.com/D142578

* Further changes required by Servo

* style: Add basic @container rule parsing and boilerplate

For now parse a MediaFeatureCondition. That needs being made more
specific, but that is probably worth its own patch.

Differential Revision: https://phabricator.services.mozilla.com/D143192

* Further changes required by Servo

* style: Tweak cascade priority to split writing-mode and font properties

This makes the worst case for cascade performance slightly more
expensive (4 rather than three declaration walks), but my hope is that
it will make the average case faster, since the best case is now just
two walks instead of three, and writing mode properties are somewhat
rare.

This needs a test, but needs to wait until the writing-mode dependent
viewport units land (will wait to land with a test).

Differential Revision: https://phabricator.services.mozilla.com/D143261

* style: Implement new {small,large,dynamic} viewport units

Differential Revision: https://phabricator.services.mozilla.com/D143252

* Further changes required by Servo

* style: Implement new *vi and *vb units

Differential Revision: https://phabricator.services.mozilla.com/D143253

* style: Implement prefers-contrast: custom and let prefers-contrast ride the trains

Differential Revision: https://phabricator.services.mozilla.com/D143198

* style: Join servo style threads during shutdown

I was unable to change the BLOOM_KEY field to no longer be leaked, as the TLS
is also accessed on the main thread, which is not exited before the leak
checker shuts down.

Differential Revision: https://phabricator.services.mozilla.com/D143529

* Further changes required by Servo

* style: Fix visited handling after bug 1763750

Before bug 1763750, we unconditionally called compute_writing_mode,
which got the writing mode from the cascade mode for visited styles.

However after that bug we only do that if we apply any
writing-mode-related property.

We could just call compute_writing_mode unconditionally, but instead it
seems better to skip all that work for visited cascade and reuse the
mechanism introduced in that bug to only apply the visited-dependent
longhands.

We assert that all visited-dependent longhands are "late" longhands, so
as to also avoid applying the font group and such.

Differential Revision: https://phabricator.services.mozilla.com/D143490

* style: Clean-up viewport unit resolution a bit

I should've caught this when reviewing the new viewport units but alas :-)

Differential Revision: https://phabricator.services.mozilla.com/D143856

* style: Implement `contain: inline-size`

Differential Revision: https://phabricator.services.mozilla.com/D143501

* style: Simplify media query evaluation code a bit

This patch:

  * Removes generic <ident> support for media features. These were used
    for some privileged media features but are no longer used.

  * Simplifies media feature getters by shifting the responsibility of
    dealing with RangeOrOperator to the caller. This makes it easier to
    implement container-query / mediaqueries-4 syntax, and also cleans up
    the code a bunch.

There should be no change in behavior.

Differential Revision: https://phabricator.services.mozilla.com/D144051

* Further changes required by Servo

* style: Move transitions and animations to nsStyleUIReset

This mostly just moves code around, to minimize potential behavior
changes. There are some cleanups that we should try to do long term
(this "have an array with n different counts" is pretty weird).

But for now this should unblock people.

The destination struct (nsStyleUIReset) was chosen mainly because it's
small and non-inherited, and it doesn't seem like a worse place than
nsStyleDisplay.

Differential Revision: https://phabricator.services.mozilla.com/D144183

* Further changes required by Servo

* style: Make media feature evaluation take a computed::Context

This has no behavior change right now, but will simplify sharing code
with container queries.

Container queries will have container information in the
computed::Context (this is necessary anyways for container-based units),
so this avoids having to have different code for media and container
queries.

Differential Revision: https://phabricator.services.mozilla.com/D144152

* Further changes required by Servo

* style: Add scroll() to animation-timeline for style system

scroll() is defined in the spec proposal, and there is a temporary spec:
https://drafts.csswg.org/scroll-animations-1/rewrite#scroll-notation.

The spec is still under development, so we don't drop the orignal
scroll-timeline at rule. Instead, we add a new scroll() notation to
animation-timeline, and support both syntax for now.

Differential Revision: https://phabricator.services.mozilla.com/D143417

* style: Tweak contain bitflag definition order to avoid static constructors

This has no behavior change otherwise. The STRICT definition depended on
SIZE, which was defined later. That's fine in Rust, but in C++ it causes
the initialization to be dynamic because it doesn't have the definition
of SIZE yet (ugh).

This is the fix for the regression, though the following patch turns on
constexpr support in cbindgen, which would've caught this at build-time,
and guarantees that we don't have extra static constructors.

Differential Revision: https://phabricator.services.mozilla.com/D144316

* style: Move some of the media query code to a more generic queries module

No behavior change, just moving and renaming files.

The code in the "queries" module will be shared between @media and
@container.

@media has some other code that container queries doesn't need like
MediaList / MediaType / etc. That remains in the media_queries module.

Differential Revision: https://phabricator.services.mozilla.com/D144435

* Further changes required by Servo

* style: cleanup animation-name

Make the representation the same between Gecko and Servo code. This will
enable further clean-ups in the future.

Make serialization be correct, serializing as identifier unless it's an
invalid one (in which case we serialize as a string).

This changes our stringification behavior in the specified style, but
now it will match the computed style and be more correct over-all.

Differential Revision: https://phabricator.services.mozilla.com/D144473

* Further changes required by Servo

* style: Add support for parsing container-query-specific features

There are some mediaqueries-5 features that we still don't support and
explain the remaining failures in at-container-{parsing,serialization}.

Differential Revision: https://phabricator.services.mozilla.com/D144446

* Further changes required by Servo

* style: Introduce Optional<T> to represent optional values in the style system

cross-fade() was kinda doing this in its own way with PercentOrNone, but
since now we have more use-cases for this we should probably make this a
slightly more general solution.

I added some convenience APIs, but they're unused as of this patch so
let me know if you want them gone.

Differential Revision: https://phabricator.services.mozilla.com/D144831

* style: Fix insertRule with layer statements before imports

We need to do a bit more nuanced check because @layer statements might
go before imports.

Differential Revision: https://phabricator.services.mozilla.com/D144996

* style: Factor out parsing the query feature name

No behavior change.

Differential Revision: https://phabricator.services.mozilla.com/D145229

* style: Refactor media feature expression representation in preparation to support multi-range syntax

No behavior change.

Depends on D145229

Differential Revision: https://phabricator.services.mozilla.com/D145230

* style: Implement media feature expression multi-range syntax

Differential Revision: https://phabricator.services.mozilla.com/D145231

* style: Remove proton places tooltip code

There's nobody working on it, and tooltips should hopefully be nice
enough after recent changes (bug 1765423).

Having it enabled causes artifacts like bug 1767815 comment 3. We can
always rescue this from hg history if needed.

Differential Revision: https://phabricator.services.mozilla.com/D145621

* style: Simplify selector flags setup even more

In my investigation for bug 1766439, I am digging into why selector
matching regressed.

It doesn't help that the selector-matching code is instantiated a
gazillion times (so there's a ton of copies of the relevant functions).

This was needed in the past because we had different ways of setting the
selector flags on elements, but I unified that recently and now we only
need to either set them or not. That is the kind of thing that
MatchingContext is really good for, so pass that instead on
MatchingContext creation.

Differential Revision: https://phabricator.services.mozilla.com/D145428

* Further changes required by Servo

* style: Track @container condition id in style rules

Much like we track layer rules. Consolidate that "containing rule state
we pass down while building the cascade data" in a single struct that we
can easily restore.

For now, do nothing with it. I want to land this patch separately
because it touches the Rule struct and CascadeData rebuilds, which both
are performance sensitive.

Its layout shouldn't change because I also changed LayerId to be a u16
(this shouldn't matter in practice, since LayerOrder is already a u16).

Differential Revision: https://phabricator.services.mozilla.com/D145243

* Further changes required by Servo

* style: Fix layer statement rules with multiple layer names

MANUAL PUSH: Trivial orange fix CLOSED TREE.

* style: Implement piecewise linear function

Differential Revision: https://phabricator.services.mozilla.com/D145256

* style: Convert specified value tests to compile-time tests

These were written at a time where std::mem::size_of wasn't a `const fn` in
Rust.

Now that it is, we can make these tests live in the style crate, and the build
not to compile if they fail.

Differential Revision: https://phabricator.services.mozilla.com/D146103

* Further changes required by Servo

* style: Move size of tests to compile-time tests in the style crate

Same reasoning as the previous commit.

Differential Revision: https://phabricator.services.mozilla.com/D146104

* Further changes required by Servo

* style: Lint and 32-bit build fix.

MANUAL PUSH: Bustage fix CLOSED TREE

* style: Implement 'update' media feature

Differential Revision: https://phabricator.services.mozilla.com/D146338

* style: Clean up unused -moz-window-shadow values

After bug 1768278 and bug 1767815 there's no more uses of the cliprounded value
in the tree (also it causes artifacts on HiDPI screens so we probably don't
want new usages).

The "sheet" value is unused, and the other values other than "default" and
"none" are only derived from "default", so they don't need to be exposed in the
style system.

Differential Revision: https://phabricator.services.mozilla.com/D145821

* style: More container queries plumbing

Provide container information in computed::Context and use it to resolve
the container queries.

This still fails a lot of tests because we are not ensuring that layout
is up-to-date when we style the container descendants, but that's
expected.

Differential Revision: https://phabricator.services.mozilla.com/D146478

* Further changes required by Servo

* style: Ensure options in listbox selects are not stacking contexts by default

We could have a different property or something but this seems
reasonable as well probably.

Differential Revision: https://phabricator.services.mozilla.com/D146994

* style: Implement overflow-clip-margin: <length>

Differential Revision: https://phabricator.services.mozilla.com/D146432

* style: Change order of container shorthand

Since the initial value of container-type is an open issue [1],
I'm leaving that as-is for now.

[1] https://github.com/w3c/csswg-drafts/issues/7202

Differential Revision: https://phabricator.services.mozilla.com/D147338

* style: Make modal dialog code more generic, and make it apply to fullscreen too behind a pref

For now, don't turn it on by default yet, because I want to wait for
more discussion in https://github.com/w3c/csswg-drafts/issues/6965 and
so on. But I think the code is simple enough to land this.

Differential Revision: https://phabricator.services.mozilla.com/D147295

* style: Cache computed styles objects display: none subtrees

This reuses our existing undisplayed style generation, but in a
per-document rather than per-nsComputedDOMStyle object, which means that
we can avoid re-resolving styles of elements in display: none subtrees
much more often.

This brings the test-case in the bug to par with other browsers or
better, and is much simpler than the initial approach I tried back in
the day.

Differential Revision: https://phabricator.services.mozilla.com/D147547

* Further changes required by Servo

* style: Parse scroll-snap-stop style and propagate it to APZ side

Depends on D146147

Differential Revision: https://phabricator.services.mozilla.com/D145850

* style: Update color-mix() syntax to match the current spec

Test expectation updates for this in the latest patch of the bug.

Differential Revision: https://phabricator.services.mozilla.com/D147002

* Further changes required by Servo

* style: Make the color interpolation code more generic

It's really piece-wise premultiplied interpolation, with a special-case
for hue, so centralize the implementation.

Differential Revision: https://phabricator.services.mozilla.com/D147003

* style: Implement more color-mix() color-spaces

We had code to convert between these and the latest draft supports them so...

Differential Revision: https://phabricator.services.mozilla.com/D147004

* style: Fix color-mix() percentage normalization

Differential Revision: https://phabricator.services.mozilla.com/D147005

* style: Fix hue adjustment to match the spec

The value to sum is tau, not pi. This was caught by some tests, see
https://drafts.csswg.org/css-color/#shorter

Differential Revision: https://phabricator.services.mozilla.com/D147006

* style: Do hue interpolations in degrees rather than radians. r=barret

This gives us a bit more precision.

Differential Revision: https://phabricator.services.mozilla.com/D147007

* style: Improve Percentage -> LengthPercentage conversion

This doesn't change behavior because we only use them for images that
have no clamping.

Depends on D147008

Differential Revision: https://phabricator.services.mozilla.com/D147511

* style: Remove some dead vibrancy code

Drive-by cleanup.

Differential Revision: https://phabricator.services.mozilla.com/D147698

* style: Fix warnings about whitelist/blocklist functions being deprecated in bindgen 0.59

Differential Revision: https://phabricator.services.mozilla.com/D147695

* style: Update style to arrayvec 0.7

Differential Revision: https://phabricator.services.mozilla.com/D147476

* style: Update style to uluru 3.0

Differential Revision: https://phabricator.services.mozilla.com/D147477

* style: Remove -moz-scrollbar-thumb-proportional

It unconditionally matches on all platforms, so it's not returning any useful information.

Depends on D147689

Differential Revision: https://phabricator.services.mozilla.com/D147690

* style: Use ColorMix for interpolated colors in the computed style rather than ComplexColorRatios

This among other things preserves the right color-space when
interpolating currentColor.

Differential Revision: https://phabricator.services.mozilla.com/D147512

* Further changes required by Servo

* style: Add an input-region-margin to widgets, and implement it on Linux

Recompute the input region when resizing the widget and so on, and use
it to check for rollups.

Depends on D148211

Differential Revision: https://phabricator.services.mozilla.com/D148222

* Avoid complaints from ./mach test-tidy

* Update test expectations

---------

Co-authored-by: Emilio Cobos Álvarez <emilio@crisal.io>
Co-authored-by: Brad Werth <bwerth@mozilla.com>
Co-authored-by: David Shin <dshin@mozilla.com>
Co-authored-by: Hiroyuki Ikezoe <hikezoe.birchill@mozilla.com>
Co-authored-by: Nika Layzell <nika@thelayzells.com>
Co-authored-by: Boris Chiou <boris.chiou@gmail.com>
Co-authored-by: Autumn on Tape <autumn@cyfox.net>
Co-authored-by: Mike Hommey <mh+mozilla@glandium.org>
2023-08-15 23:11:39 +00:00

1952 lines
75 KiB
Rust

/* This Source Code Form is subject to the terms of the Mozilla Public
* 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/. */
// Work around https://github.com/rust-lang/rust/issues/62132
#![recursion_limit = "128"]
//! The layout thread. Performs layout on the DOM, builds display lists and sends them to be
//! painted.
#[macro_use]
extern crate crossbeam_channel;
#[macro_use]
extern crate layout;
#[macro_use]
extern crate lazy_static;
#[macro_use]
extern crate log;
#[macro_use]
extern crate profile_traits;
use app_units::Au;
use crossbeam_channel::{Receiver, Sender};
use embedder_traits::resources::{self, Resource};
use euclid::{default::Size2D as UntypedSize2D, Point2D, Rect, Scale, Size2D};
use fnv::FnvHashMap;
use fxhash::{FxHashMap, FxHashSet};
use gfx::font;
use gfx::font_cache_thread::FontCacheThread;
use gfx::font_context;
use gfx_traits::{node_id_from_scroll_id, Epoch};
use histogram::Histogram;
use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
use ipc_channel::router::ROUTER;
use layout::construct::ConstructionResult;
use layout::context::malloc_size_of_persistent_local_context;
use layout::context::LayoutContext;
use layout::context::RegisteredPainter;
use layout::context::RegisteredPainters;
use layout::display_list::items::WebRenderImageInfo;
use layout::display_list::{IndexableText, ToLayout};
use layout::flow::{Flow, FlowFlags, GetBaseFlow, ImmutableFlowUtils, MutableOwnedFlowUtils};
use layout::flow_ref::FlowRef;
use layout::incremental::{RelayoutMode, SpecialRestyleDamage};
use layout::parallel;
use layout::query::{
process_client_rect_query, process_content_box_request, process_content_boxes_request,
process_element_inner_text_query, process_node_scroll_area_request,
process_node_scroll_id_request, process_offset_parent_query,
process_resolved_font_style_request, process_resolved_style_request, LayoutRPCImpl,
LayoutThreadData,
};
use layout::sequential;
use layout::traversal::{
construct_flows_at_ancestors, ComputeStackingRelativePositions, PreorderFlowTraversal,
RecalcStyleAndConstructFlows,
};
use layout::wrapper::LayoutNodeLayoutData;
use layout::{layout_debug, LayoutData};
use layout_traits::LayoutThreadFactory;
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use metrics::{PaintTimeMetrics, ProfilerMetadataFactory, ProgressiveWebMetric};
use msg::constellation_msg::{
BackgroundHangMonitor, BackgroundHangMonitorRegister, HangAnnotation,
};
use msg::constellation_msg::{BrowsingContextId, MonitoredComponentId, TopLevelBrowsingContextId};
use msg::constellation_msg::{LayoutHangAnnotation, MonitoredComponentType, PipelineId};
use net_traits::image_cache::{ImageCache, UsePlaceholder};
use parking_lot::RwLock;
use profile_traits::mem::{self as profile_mem, Report, ReportKind, ReportsChan};
use profile_traits::time::{self as profile_time, profile, TimerMetadata};
use profile_traits::time::{TimerMetadataFrameType, TimerMetadataReflowType};
use script::layout_dom::{ServoLayoutDocument, ServoLayoutElement, ServoLayoutNode};
use script_layout_interface::message::{LayoutThreadInit, Msg, NodesFromPointQueryType, Reflow};
use script_layout_interface::message::{QueryMsg, ReflowComplete, ReflowGoal, ScriptReflow};
use script_layout_interface::rpc::TextIndexResponse;
use script_layout_interface::rpc::{LayoutRPC, OffsetParentResponse};
use script_layout_interface::wrapper_traits::LayoutNode;
use script_traits::{ConstellationControlMsg, LayoutControlMsg, LayoutMsg as ConstellationMsg};
use script_traits::{DrawAPaintImageResult, IFrameSizeMsg, PaintWorkletError, WindowSizeType};
use script_traits::{Painter, WebrenderIpcSender};
use script_traits::{ScrollState, UntrustedNodeAddress, WindowSizeData};
use servo_arc::Arc as ServoArc;
use servo_atoms::Atom;
use servo_config::opts::{self, DebugOptions};
use servo_url::{ImmutableOrigin, ServoUrl};
use std::borrow::ToOwned;
use std::cell::{Cell, RefCell};
use std::collections::HashMap;
use std::ops::{Deref, DerefMut};
use std::process;
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
use std::sync::{Arc, Mutex, MutexGuard};
use std::thread;
use std::time::Duration;
use style::animation::{AnimationSetKey, DocumentAnimationSet, ElementAnimationSet};
use style::context::SharedStyleContext;
use style::context::{QuirksMode, RegisteredSpeculativePainter, RegisteredSpeculativePainters};
use style::dom::{ShowSubtree, ShowSubtreeDataAndPrimaryValues, TElement, TNode};
use style::driver;
use style::error_reporting::RustLogReporter;
use style::global_style_data::{GLOBAL_STYLE_DATA, STYLE_THREAD_POOL};
use style::invalidation::element::restyle_hints::RestyleHint;
use style::logical_geometry::LogicalPoint;
use style::media_queries::{Device, MediaList, MediaType};
use style::properties::PropertyId;
use style::selector_parser::{PseudoElement, SnapshotMap};
use style::servo::restyle_damage::ServoRestyleDamage;
use style::shared_lock::{SharedRwLock, SharedRwLockReadGuard, StylesheetGuards};
use style::stylesheets::{
DocumentStyleSheet, Origin, Stylesheet, StylesheetInDocument, UserAgentStylesheets,
};
use style::stylist::Stylist;
use style::thread_state::{self, ThreadState};
use style::traversal::DomTraversal;
use style::traversal_flags::TraversalFlags;
use style_traits::CSSPixel;
use style_traits::DevicePixel;
use style_traits::SpeculativePainter;
use webrender_api::{units, ColorF, HitTestFlags, ScrollClamping};
/// Information needed by the layout thread.
pub struct LayoutThread {
/// The ID of the pipeline that we belong to.
id: PipelineId,
/// The ID of the top-level browsing context that we belong to.
top_level_browsing_context_id: TopLevelBrowsingContextId,
/// The URL of the pipeline that we belong to.
url: ServoUrl,
/// Performs CSS selector matching and style resolution.
stylist: Stylist,
/// Is the current reflow of an iframe, as opposed to a root window?
is_iframe: bool,
/// The port on which we receive messages from the script thread.
port: Receiver<Msg>,
/// The port on which we receive messages from the constellation.
pipeline_port: Receiver<LayoutControlMsg>,
/// The port on which we receive messages from the font cache thread.
font_cache_receiver: Receiver<()>,
/// The channel on which the font cache can send messages to us.
font_cache_sender: IpcSender<()>,
/// A means of communication with the background hang monitor.
background_hang_monitor: Box<dyn BackgroundHangMonitor>,
/// The channel on which messages can be sent to the constellation.
constellation_chan: IpcSender<ConstellationMsg>,
/// The channel on which messages can be sent to the script thread.
script_chan: IpcSender<ConstellationControlMsg>,
/// The channel on which messages can be sent to the time profiler.
time_profiler_chan: profile_time::ProfilerChan,
/// The channel on which messages can be sent to the memory profiler.
mem_profiler_chan: profile_mem::ProfilerChan,
/// Reference to the script thread image cache.
image_cache: Arc<dyn ImageCache>,
/// Public interface to the font cache thread.
font_cache_thread: FontCacheThread,
/// Is this the first reflow in this LayoutThread?
first_reflow: Cell<bool>,
/// Flag to indicate whether to use parallel operations
parallel_flag: bool,
/// Starts at zero, and increased by one every time a layout completes.
/// This can be used to easily check for invalid stale data.
generation: Cell<u32>,
/// The number of Web fonts that have been requested but not yet loaded.
outstanding_web_fonts: Arc<AtomicUsize>,
/// The root of the flow tree.
root_flow: RefCell<Option<FlowRef>>,
/// A counter for epoch messages
epoch: Cell<Epoch>,
/// The size of the viewport. This may be different from the size of the screen due to viewport
/// constraints.
viewport_size: UntypedSize2D<Au>,
/// A mutex to allow for fast, read-only RPC of layout's internal data
/// structures, while still letting the LayoutThread modify them.
///
/// All the other elements of this struct are read-only.
rw_data: Arc<Mutex<LayoutThreadData>>,
webrender_image_cache: Arc<RwLock<FnvHashMap<(ServoUrl, UsePlaceholder), WebRenderImageInfo>>>,
/// The executors for paint worklets.
registered_painters: RegisteredPaintersImpl,
/// Webrender interface.
webrender_api: WebrenderIpcSender,
/// Paint time metrics.
paint_time_metrics: PaintTimeMetrics,
/// The time a layout query has waited before serviced by layout thread.
layout_query_waiting_time: Histogram,
/// The sizes of all iframes encountered during the last layout operation.
last_iframe_sizes: RefCell<FnvHashMap<BrowsingContextId, Size2D<f32, CSSPixel>>>,
/// Flag that indicates if LayoutThread is busy handling a request.
busy: Arc<AtomicBool>,
/// Debug options, copied from configuration to this `LayoutThread` in order
/// to avoid having to constantly access the thread-safe global options.
debug: DebugOptions,
/// True to turn off incremental layout.
nonincremental_layout: bool,
}
impl LayoutThreadFactory for LayoutThread {
type Message = Msg;
/// Spawns a new layout thread.
fn create(
id: PipelineId,
top_level_browsing_context_id: TopLevelBrowsingContextId,
url: ServoUrl,
is_iframe: bool,
chan: (Sender<Msg>, Receiver<Msg>),
pipeline_port: IpcReceiver<LayoutControlMsg>,
background_hang_monitor_register: Box<dyn BackgroundHangMonitorRegister>,
constellation_chan: IpcSender<ConstellationMsg>,
script_chan: IpcSender<ConstellationControlMsg>,
image_cache: Arc<dyn ImageCache>,
font_cache_thread: FontCacheThread,
time_profiler_chan: profile_time::ProfilerChan,
mem_profiler_chan: profile_mem::ProfilerChan,
webrender_api_sender: WebrenderIpcSender,
paint_time_metrics: PaintTimeMetrics,
busy: Arc<AtomicBool>,
window_size: WindowSizeData,
) {
thread::Builder::new()
.name(format!("Layout{}", id))
.spawn(move || {
thread_state::initialize(ThreadState::LAYOUT);
// In order to get accurate crash reports, we install the top-level bc id.
TopLevelBrowsingContextId::install(top_level_browsing_context_id);
{
// Ensures layout thread is destroyed before we send shutdown message
let sender = chan.0;
let background_hang_monitor = background_hang_monitor_register
.register_component(
MonitoredComponentId(id, MonitoredComponentType::Layout),
Duration::from_millis(1000),
Duration::from_millis(5000),
None,
);
let layout = LayoutThread::new(
id,
top_level_browsing_context_id,
url,
is_iframe,
chan.1,
pipeline_port,
background_hang_monitor,
constellation_chan,
script_chan,
image_cache,
font_cache_thread,
time_profiler_chan,
mem_profiler_chan.clone(),
webrender_api_sender,
paint_time_metrics,
busy,
window_size,
);
let reporter_name = format!("layout-reporter-{}", id);
mem_profiler_chan.run_with_memory_reporting(
|| {
layout.start();
},
reporter_name,
sender,
Msg::CollectReports,
);
}
})
.expect("Thread spawning failed");
}
}
struct ScriptReflowResult {
script_reflow: ScriptReflow,
result: RefCell<Option<ReflowComplete>>,
}
impl Deref for ScriptReflowResult {
type Target = ScriptReflow;
fn deref(&self) -> &ScriptReflow {
&self.script_reflow
}
}
impl DerefMut for ScriptReflowResult {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.script_reflow
}
}
impl ScriptReflowResult {
fn new(script_reflow: ScriptReflow) -> ScriptReflowResult {
ScriptReflowResult {
script_reflow: script_reflow,
result: RefCell::new(Some(Default::default())),
}
}
}
impl Drop for ScriptReflowResult {
fn drop(&mut self) {
self.script_reflow
.script_join_chan
.send(self.result.borrow_mut().take().unwrap())
.unwrap();
}
}
/// The `LayoutThread` `rw_data` lock must remain locked until the first reflow,
/// as RPC calls don't make sense until then. Use this in combination with
/// `LayoutThread::lock_rw_data` and `LayoutThread::return_rw_data`.
pub enum RWGuard<'a> {
/// If the lock was previously held, from when the thread started.
Held(MutexGuard<'a, LayoutThreadData>),
/// If the lock was just used, and has been returned since there has been
/// a reflow already.
Used(MutexGuard<'a, LayoutThreadData>),
}
impl<'a> Deref for RWGuard<'a> {
type Target = LayoutThreadData;
fn deref(&self) -> &LayoutThreadData {
match *self {
RWGuard::Held(ref x) => &**x,
RWGuard::Used(ref x) => &**x,
}
}
}
impl<'a> DerefMut for RWGuard<'a> {
fn deref_mut(&mut self) -> &mut LayoutThreadData {
match *self {
RWGuard::Held(ref mut x) => &mut **x,
RWGuard::Used(ref mut x) => &mut **x,
}
}
}
struct RwData<'a, 'b: 'a> {
rw_data: &'b Arc<Mutex<LayoutThreadData>>,
possibly_locked_rw_data: &'a mut Option<MutexGuard<'b, LayoutThreadData>>,
}
impl<'a, 'b: 'a> RwData<'a, 'b> {
/// If no reflow has happened yet, this will just return the lock in
/// `possibly_locked_rw_data`. Otherwise, it will acquire the `rw_data` lock.
///
/// If you do not wish RPCs to remain blocked, just drop the `RWGuard`
/// returned from this function. If you _do_ wish for them to remain blocked,
/// use `block`.
fn lock(&mut self) -> RWGuard<'b> {
match self.possibly_locked_rw_data.take() {
None => RWGuard::Used(self.rw_data.lock().unwrap()),
Some(x) => RWGuard::Held(x),
}
}
}
fn add_font_face_rules(
stylesheet: &Stylesheet,
guard: &SharedRwLockReadGuard,
device: &Device,
font_cache_thread: &FontCacheThread,
font_cache_sender: &IpcSender<()>,
outstanding_web_fonts_counter: &Arc<AtomicUsize>,
load_webfonts_synchronously: bool,
) {
if load_webfonts_synchronously {
let (sender, receiver) = ipc::channel().unwrap();
stylesheet.effective_font_face_rules(&device, guard, |rule| {
if let Some(font_face) = rule.font_face() {
let effective_sources = font_face.effective_sources();
font_cache_thread.add_web_font(
font_face.family().clone(),
effective_sources,
sender.clone(),
);
receiver.recv().unwrap();
}
})
} else {
stylesheet.effective_font_face_rules(&device, guard, |rule| {
if let Some(font_face) = rule.font_face() {
let effective_sources = font_face.effective_sources();
outstanding_web_fonts_counter.fetch_add(1, Ordering::SeqCst);
font_cache_thread.add_web_font(
font_face.family().clone(),
effective_sources,
(*font_cache_sender).clone(),
);
}
})
}
}
impl LayoutThread {
/// Creates a new `LayoutThread` structure.
fn new(
id: PipelineId,
top_level_browsing_context_id: TopLevelBrowsingContextId,
url: ServoUrl,
is_iframe: bool,
port: Receiver<Msg>,
pipeline_port: IpcReceiver<LayoutControlMsg>,
background_hang_monitor: Box<dyn BackgroundHangMonitor>,
constellation_chan: IpcSender<ConstellationMsg>,
script_chan: IpcSender<ConstellationControlMsg>,
image_cache: Arc<dyn ImageCache>,
font_cache_thread: FontCacheThread,
time_profiler_chan: profile_time::ProfilerChan,
mem_profiler_chan: profile_mem::ProfilerChan,
webrender_api: WebrenderIpcSender,
paint_time_metrics: PaintTimeMetrics,
busy: Arc<AtomicBool>,
window_size: WindowSizeData,
) -> LayoutThread {
// Let webrender know about this pipeline by sending an empty display list.
webrender_api.send_initial_transaction(id.to_webrender());
let device = Device::new(
MediaType::screen(),
QuirksMode::NoQuirks,
window_size.initial_viewport,
window_size.device_pixel_ratio,
);
// Proxy IPC messages from the pipeline to the layout thread.
let pipeline_receiver = ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(pipeline_port);
// Ask the router to proxy IPC messages from the font cache thread to the layout thread.
let (ipc_font_cache_sender, ipc_font_cache_receiver) = ipc::channel().unwrap();
let font_cache_receiver =
ROUTER.route_ipc_receiver_to_new_crossbeam_receiver(ipc_font_cache_receiver);
LayoutThread {
id,
top_level_browsing_context_id: top_level_browsing_context_id,
url,
is_iframe,
port,
pipeline_port: pipeline_receiver,
script_chan,
background_hang_monitor,
constellation_chan: constellation_chan.clone(),
time_profiler_chan,
mem_profiler_chan,
registered_painters: RegisteredPaintersImpl(Default::default()),
image_cache,
font_cache_thread,
first_reflow: Cell::new(true),
font_cache_receiver,
font_cache_sender: ipc_font_cache_sender,
parallel_flag: true,
generation: Cell::new(0),
outstanding_web_fonts: Arc::new(AtomicUsize::new(0)),
root_flow: RefCell::new(None),
// Epoch starts at 1 because of the initial display list for epoch 0 that we send to WR
epoch: Cell::new(Epoch(1)),
viewport_size: Size2D::new(Au(0), Au(0)),
webrender_api,
stylist: Stylist::new(device, QuirksMode::NoQuirks),
rw_data: Arc::new(Mutex::new(LayoutThreadData {
constellation_chan,
display_list: None,
indexable_text: IndexableText::default(),
content_box_response: None,
content_boxes_response: Vec::new(),
client_rect_response: Rect::zero(),
scroll_id_response: None,
scroll_area_response: Rect::zero(),
resolved_style_response: String::new(),
resolved_font_style_response: None,
offset_parent_response: OffsetParentResponse::empty(),
scroll_offsets: HashMap::new(),
text_index_response: TextIndexResponse(None),
nodes_from_point_response: vec![],
element_inner_text_response: String::new(),
inner_window_dimensions_response: None,
})),
webrender_image_cache: Arc::new(RwLock::new(FnvHashMap::default())),
paint_time_metrics,
layout_query_waiting_time: Histogram::new(),
last_iframe_sizes: Default::default(),
busy,
debug: opts::get().debug.clone(),
nonincremental_layout: opts::get().nonincremental_layout,
}
}
/// Starts listening on the port.
fn start(mut self) {
let rw_data = self.rw_data.clone();
let mut possibly_locked_rw_data = Some(rw_data.lock().unwrap());
let mut rw_data = RwData {
rw_data: &rw_data,
possibly_locked_rw_data: &mut possibly_locked_rw_data,
};
while self.handle_request(&mut rw_data) {
// Loop indefinitely.
}
}
// Create a layout context for use in building display lists, hit testing, &c.
fn build_layout_context<'a>(
&'a self,
guards: StylesheetGuards<'a>,
snapshot_map: &'a SnapshotMap,
origin: ImmutableOrigin,
animation_timeline_value: f64,
animations: &DocumentAnimationSet,
stylesheets_changed: bool,
) -> LayoutContext<'a> {
let traversal_flags = match stylesheets_changed {
true => TraversalFlags::ForCSSRuleChanges,
false => TraversalFlags::empty(),
};
LayoutContext {
id: self.id,
origin,
style_context: SharedStyleContext {
stylist: &self.stylist,
options: GLOBAL_STYLE_DATA.options.clone(),
guards,
visited_styles_enabled: false,
animations: animations.clone(),
registered_speculative_painters: &self.registered_painters,
current_time_for_animations: animation_timeline_value,
traversal_flags,
snapshot_map: snapshot_map,
},
image_cache: self.image_cache.clone(),
font_cache_thread: Mutex::new(self.font_cache_thread.clone()),
webrender_image_cache: self.webrender_image_cache.clone(),
pending_images: Mutex::new(vec![]),
registered_painters: &self.registered_painters,
}
}
fn notify_activity_to_hang_monitor(&self, request: &Msg) {
let hang_annotation = match request {
Msg::AddStylesheet(..) => LayoutHangAnnotation::AddStylesheet,
Msg::RemoveStylesheet(..) => LayoutHangAnnotation::RemoveStylesheet,
Msg::SetQuirksMode(..) => LayoutHangAnnotation::SetQuirksMode,
Msg::Reflow(..) => LayoutHangAnnotation::Reflow,
Msg::GetRPC(..) => LayoutHangAnnotation::GetRPC,
Msg::CollectReports(..) => LayoutHangAnnotation::CollectReports,
Msg::PrepareToExit(..) => LayoutHangAnnotation::PrepareToExit,
Msg::ExitNow => LayoutHangAnnotation::ExitNow,
Msg::GetCurrentEpoch(..) => LayoutHangAnnotation::GetCurrentEpoch,
Msg::GetWebFontLoadState(..) => LayoutHangAnnotation::GetWebFontLoadState,
Msg::CreateLayoutThread(..) => LayoutHangAnnotation::CreateLayoutThread,
Msg::SetFinalUrl(..) => LayoutHangAnnotation::SetFinalUrl,
Msg::SetScrollStates(..) => LayoutHangAnnotation::SetScrollStates,
Msg::RegisterPaint(..) => LayoutHangAnnotation::RegisterPaint,
Msg::SetNavigationStart(..) => LayoutHangAnnotation::SetNavigationStart,
};
self.background_hang_monitor
.notify_activity(HangAnnotation::Layout(hang_annotation));
}
/// Receives and dispatches messages from the script and constellation threads
fn handle_request<'a, 'b>(&mut self, possibly_locked_rw_data: &mut RwData<'a, 'b>) -> bool {
enum Request {
FromPipeline(LayoutControlMsg),
FromScript(Msg),
FromFontCache,
}
// Notify the background-hang-monitor we are waiting for an event.
self.background_hang_monitor.notify_wait();
let request = select! {
recv(self.pipeline_port) -> msg => Request::FromPipeline(msg.unwrap()),
recv(self.port) -> msg => Request::FromScript(msg.unwrap()),
recv(self.font_cache_receiver) -> msg => { msg.unwrap(); Request::FromFontCache }
};
self.busy.store(true, Ordering::Relaxed);
let result = match request {
Request::FromPipeline(LayoutControlMsg::SetScrollStates(new_scroll_states)) => self
.handle_request_helper(
Msg::SetScrollStates(new_scroll_states),
possibly_locked_rw_data,
),
Request::FromPipeline(LayoutControlMsg::GetCurrentEpoch(sender)) => {
self.handle_request_helper(Msg::GetCurrentEpoch(sender), possibly_locked_rw_data)
},
Request::FromPipeline(LayoutControlMsg::GetWebFontLoadState(sender)) => self
.handle_request_helper(Msg::GetWebFontLoadState(sender), possibly_locked_rw_data),
Request::FromPipeline(LayoutControlMsg::ExitNow) => {
self.handle_request_helper(Msg::ExitNow, possibly_locked_rw_data)
},
Request::FromPipeline(LayoutControlMsg::PaintMetric(epoch, paint_time)) => {
self.paint_time_metrics.maybe_set_metric(epoch, paint_time);
true
},
Request::FromScript(msg) => self.handle_request_helper(msg, possibly_locked_rw_data),
Request::FromFontCache => {
let _rw_data = possibly_locked_rw_data.lock();
self.outstanding_web_fonts.fetch_sub(1, Ordering::SeqCst);
font_context::invalidate_font_caches();
self.script_chan
.send(ConstellationControlMsg::WebFontLoaded(self.id))
.unwrap();
true
},
};
self.busy.store(false, Ordering::Relaxed);
result
}
/// Receives and dispatches messages from other threads.
fn handle_request_helper<'a, 'b>(
&mut self,
request: Msg,
possibly_locked_rw_data: &mut RwData<'a, 'b>,
) -> bool {
self.notify_activity_to_hang_monitor(&request);
match request {
Msg::AddStylesheet(stylesheet, before_stylesheet) => {
let guard = stylesheet.shared_lock.read();
self.handle_add_stylesheet(&stylesheet, &guard);
match before_stylesheet {
Some(insertion_point) => self.stylist.insert_stylesheet_before(
DocumentStyleSheet(stylesheet.clone()),
DocumentStyleSheet(insertion_point),
&guard,
),
None => self
.stylist
.append_stylesheet(DocumentStyleSheet(stylesheet.clone()), &guard),
}
},
Msg::RemoveStylesheet(stylesheet) => {
let guard = stylesheet.shared_lock.read();
self.stylist
.remove_stylesheet(DocumentStyleSheet(stylesheet.clone()), &guard);
},
Msg::SetQuirksMode(mode) => self.handle_set_quirks_mode(mode),
Msg::GetRPC(response_chan) => {
response_chan
.send(Box::new(LayoutRPCImpl(self.rw_data.clone())) as Box<dyn LayoutRPC + Send>)
.unwrap();
},
Msg::Reflow(data) => {
let mut data = ScriptReflowResult::new(data);
profile(
profile_time::ProfilerCategory::LayoutPerform,
self.profiler_metadata(),
self.time_profiler_chan.clone(),
|| self.handle_reflow(&mut data, possibly_locked_rw_data),
);
},
Msg::SetScrollStates(new_scroll_states) => {
self.set_scroll_states(new_scroll_states, possibly_locked_rw_data);
},
Msg::CollectReports(reports_chan) => {
self.collect_reports(reports_chan, possibly_locked_rw_data);
},
Msg::GetCurrentEpoch(sender) => {
let _rw_data = possibly_locked_rw_data.lock();
sender.send(self.epoch.get()).unwrap();
},
Msg::GetWebFontLoadState(sender) => {
let _rw_data = possibly_locked_rw_data.lock();
let outstanding_web_fonts = self.outstanding_web_fonts.load(Ordering::SeqCst);
sender.send(outstanding_web_fonts != 0).unwrap();
},
Msg::CreateLayoutThread(info) => self.create_layout_thread(info),
Msg::SetFinalUrl(final_url) => {
self.url = final_url;
},
Msg::RegisterPaint(name, mut properties, painter) => {
debug!("Registering the painter");
let properties = properties
.drain(..)
.filter_map(|name| {
let id = PropertyId::parse_enabled_for_all_content(&*name).ok()?;
Some((name.clone(), id))
})
.filter(|&(_, ref id)| !id.is_shorthand())
.collect();
let registered_painter = RegisteredPainterImpl {
name: name.clone(),
properties,
painter,
};
self.registered_painters.0.insert(name, registered_painter);
},
Msg::PrepareToExit(response_chan) => {
self.prepare_to_exit(response_chan);
return false;
},
// Receiving the Exit message at this stage only happens when layout is undergoing a "force exit".
Msg::ExitNow => {
debug!("layout: ExitNow received");
self.exit_now();
return false;
},
Msg::SetNavigationStart(time) => {
self.paint_time_metrics.set_navigation_start(time);
},
}
true
}
fn collect_reports<'a, 'b>(
&self,
reports_chan: ReportsChan,
possibly_locked_rw_data: &mut RwData<'a, 'b>,
) {
let mut reports = vec![];
// Servo uses vanilla jemalloc, which doesn't have a
// malloc_enclosing_size_of function.
let mut ops = MallocSizeOfOps::new(servo_allocator::usable_size, None, None);
// FIXME(njn): Just measuring the display tree for now.
let rw_data = possibly_locked_rw_data.lock();
let display_list = rw_data.display_list.as_ref();
let formatted_url = &format!("url({})", self.url);
reports.push(Report {
path: path![formatted_url, "layout-thread", "display-list"],
kind: ReportKind::ExplicitJemallocHeapSize,
size: display_list.map_or(0, |sc| sc.size_of(&mut ops)),
});
reports.push(Report {
path: path![formatted_url, "layout-thread", "stylist"],
kind: ReportKind::ExplicitJemallocHeapSize,
size: self.stylist.size_of(&mut ops),
});
// The LayoutThread has data in Persistent TLS...
reports.push(Report {
path: path![formatted_url, "layout-thread", "local-context"],
kind: ReportKind::ExplicitJemallocHeapSize,
size: malloc_size_of_persistent_local_context(&mut ops),
});
reports_chan.send(reports);
}
fn create_layout_thread(&self, info: LayoutThreadInit) {
LayoutThread::create(
info.id,
self.top_level_browsing_context_id,
info.url.clone(),
info.is_parent,
info.layout_pair,
info.pipeline_port,
info.background_hang_monitor_register,
info.constellation_chan,
info.script_chan,
info.image_cache,
self.font_cache_thread.clone(),
self.time_profiler_chan.clone(),
self.mem_profiler_chan.clone(),
self.webrender_api.clone(),
info.paint_time_metrics,
info.layout_is_busy,
info.window_size,
);
}
/// Enters a quiescent state in which no new messages will be processed until an `ExitNow` is
/// received. A pong is immediately sent on the given response channel.
fn prepare_to_exit(&mut self, response_chan: Sender<()>) {
response_chan.send(()).unwrap();
loop {
match self.port.recv().unwrap() {
Msg::ExitNow => {
debug!("layout thread is exiting...");
self.exit_now();
break;
},
Msg::CollectReports(_) => {
// Just ignore these messages at this point.
},
_ => panic!("layout: unexpected message received after `PrepareToExitMsg`"),
}
}
}
/// Shuts down the layout thread now. If there are any DOM nodes left, layout will now (safely)
/// crash.
fn exit_now(&mut self) {
// Drop the root flow explicitly to avoid holding style data, such as
// rule nodes. The `Stylist` checks when it is dropped that all rule
// nodes have been GCed, so we want drop anyone who holds them first.
let waiting_time_min = self.layout_query_waiting_time.minimum().unwrap_or(0);
let waiting_time_max = self.layout_query_waiting_time.maximum().unwrap_or(0);
let waiting_time_mean = self.layout_query_waiting_time.mean().unwrap_or(0);
let waiting_time_stddev = self.layout_query_waiting_time.stddev().unwrap_or(0);
debug!(
"layout: query waiting time: min: {}, max: {}, mean: {}, standard_deviation: {}",
waiting_time_min, waiting_time_max, waiting_time_mean, waiting_time_stddev
);
self.root_flow.borrow_mut().take();
self.background_hang_monitor.unregister();
}
fn handle_add_stylesheet(&self, stylesheet: &Stylesheet, guard: &SharedRwLockReadGuard) {
// Find all font-face rules and notify the font cache of them.
// GWTODO: Need to handle unloading web fonts.
if stylesheet.is_effective_for_device(self.stylist.device(), &guard) {
add_font_face_rules(
&*stylesheet,
&guard,
self.stylist.device(),
&self.font_cache_thread,
&self.font_cache_sender,
&self.outstanding_web_fonts,
self.debug.load_webfonts_synchronously,
);
}
}
/// Sets quirks mode for the document, causing the quirks mode stylesheet to be used.
fn handle_set_quirks_mode<'a, 'b>(&mut self, quirks_mode: QuirksMode) {
self.stylist.set_quirks_mode(quirks_mode);
}
fn try_get_layout_root<'dom>(&self, node: impl LayoutNode<'dom>) -> Option<FlowRef> {
let result = node.mutate_layout_data()?.flow_construction_result.get();
let mut flow = match result {
ConstructionResult::Flow(mut flow, abs_descendants) => {
// Note: Assuming that the root has display 'static' (as per
// CSS Section 9.3.1). If it was absolutely positioned,
// it would return a reference to itself in `abs_descendants`
// and would lead to a circular reference. Otherwise, we
// set Root as CB and push remaining absolute descendants.
if flow
.base()
.flags
.contains(FlowFlags::IS_ABSOLUTELY_POSITIONED)
{
flow.set_absolute_descendants(abs_descendants);
} else {
flow.push_absolute_descendants(abs_descendants);
}
flow
},
_ => return None,
};
FlowRef::deref_mut(&mut flow).mark_as_root();
Some(flow)
}
/// Performs layout constraint solving.
///
/// This corresponds to `Reflow()` in Gecko and `layout()` in WebKit/Blink and should be
/// benchmarked against those two. It is marked `#[inline(never)]` to aid profiling.
#[inline(never)]
fn solve_constraints(layout_root: &mut dyn Flow, layout_context: &LayoutContext) {
let _scope = layout_debug_scope!("solve_constraints");
sequential::reflow(layout_root, layout_context, RelayoutMode::Incremental);
}
/// Performs layout constraint solving in parallel.
///
/// This corresponds to `Reflow()` in Gecko and `layout()` in WebKit/Blink and should be
/// benchmarked against those two. It is marked `#[inline(never)]` to aid profiling.
#[inline(never)]
fn solve_constraints_parallel(
traversal: &rayon::ThreadPool,
layout_root: &mut dyn Flow,
profiler_metadata: Option<TimerMetadata>,
time_profiler_chan: profile_time::ProfilerChan,
layout_context: &LayoutContext,
) {
let _scope = layout_debug_scope!("solve_constraints_parallel");
// NOTE: this currently computes borders, so any pruning should separate that
// operation out.
parallel::reflow(
layout_root,
profiler_metadata,
time_profiler_chan,
layout_context,
traversal,
);
}
/// Update the recorded iframe sizes of the contents of this layout thread and
/// when these sizes changes, send a message to the constellation informing it
/// of the new sizes.
fn update_iframe_sizes(
&self,
new_iframe_sizes: FnvHashMap<BrowsingContextId, Size2D<f32, CSSPixel>>,
) {
let old_iframe_sizes =
std::mem::replace(&mut *self.last_iframe_sizes.borrow_mut(), new_iframe_sizes);
if self.last_iframe_sizes.borrow().is_empty() {
return;
}
let size_messages: Vec<_> = self
.last_iframe_sizes
.borrow()
.iter()
.filter_map(|(browsing_context_id, size)| {
match old_iframe_sizes.get(&browsing_context_id) {
Some(old_size) if old_size != size => Some(IFrameSizeMsg {
browsing_context_id: *browsing_context_id,
size: *size,
type_: WindowSizeType::Resize,
}),
None => Some(IFrameSizeMsg {
browsing_context_id: *browsing_context_id,
size: *size,
type_: WindowSizeType::Initial,
}),
_ => None,
}
})
.collect();
if !size_messages.is_empty() {
let msg = ConstellationMsg::IFrameSizes(size_messages);
if let Err(e) = self.constellation_chan.send(msg) {
warn!("Layout resize to constellation failed ({}).", e);
}
}
}
/// Computes the stacking-relative positions of all flows and, if the painting is dirty and the
/// reflow type need it, builds the display list.
fn compute_abs_pos_and_build_display_list(
&self,
data: &Reflow,
reflow_goal: &ReflowGoal,
document: Option<&ServoLayoutDocument<LayoutData>>,
layout_root: &mut dyn Flow,
layout_context: &mut LayoutContext,
rw_data: &mut LayoutThreadData,
) {
let writing_mode = layout_root.base().writing_mode;
let (metadata, sender) = (self.profiler_metadata(), self.time_profiler_chan.clone());
profile(
profile_time::ProfilerCategory::LayoutDispListBuild,
metadata.clone(),
sender.clone(),
|| {
layout_root.mut_base().stacking_relative_position =
LogicalPoint::zero(writing_mode)
.to_physical(writing_mode, self.viewport_size)
.to_vector();
layout_root.mut_base().clip = data.page_clip_rect;
let traversal = ComputeStackingRelativePositions {
layout_context: layout_context,
};
traversal.traverse(layout_root);
if layout_root
.base()
.restyle_damage
.contains(ServoRestyleDamage::REPAINT) ||
rw_data.display_list.is_none()
{
if reflow_goal.needs_display_list() {
let background_color = get_root_flow_background_color(layout_root);
let mut build_state = sequential::build_display_list_for_subtree(
layout_root,
layout_context,
background_color,
data.page_clip_rect.size,
);
debug!("Done building display list.");
let root_size = {
let root_flow = layout_root.base();
root_flow.overflow.scroll.size
};
let origin = Rect::new(Point2D::new(Au(0), Au(0)), root_size).to_layout();
build_state.root_stacking_context.bounds = origin;
build_state.root_stacking_context.overflow = origin;
// We will not use build_state.iframe_sizes again, so it's safe to move it.
let iframe_sizes =
std::mem::replace(&mut build_state.iframe_sizes, FnvHashMap::default());
self.update_iframe_sizes(iframe_sizes);
rw_data.indexable_text = std::mem::replace(
&mut build_state.indexable_text,
IndexableText::default(),
);
rw_data.display_list = Some(build_state.to_display_list());
}
}
if !reflow_goal.needs_display() {
// Defer the paint step until the next ForDisplay.
//
// We need to tell the document about this so it doesn't
// incorrectly suppress reflows. See #13131.
document
.expect("No document in a non-display reflow?")
.needs_paint_from_layout();
return;
}
if let Some(document) = document {
document.will_paint();
}
let display_list = rw_data.display_list.as_mut().unwrap();
if self.debug.dump_display_list {
display_list.print();
}
if self.debug.dump_display_list_json {
println!("{}", serde_json::to_string_pretty(&display_list).unwrap());
}
debug!("Layout done!");
let viewport_size = units::LayoutSize::new(
self.viewport_size.width.to_f32_px(),
self.viewport_size.height.to_f32_px(),
);
let mut epoch = self.epoch.get();
epoch.next();
self.epoch.set(epoch);
// TODO: Avoid the temporary conversion and build webrender sc/dl directly!
let (builder, compositor_info, is_contentful) =
display_list.convert_to_webrender(self.id, viewport_size, epoch.into());
// Observe notifications about rendered frames if needed right before
// sending the display list to WebRender in order to set time related
// Progressive Web Metrics.
self.paint_time_metrics
.maybe_observe_paint_time(self, epoch, is_contentful.0);
self.webrender_api
.send_display_list(compositor_info, builder.finalize());
},
);
}
/// The high-level routine that performs layout threads.
fn handle_reflow<'a, 'b>(
&mut self,
data: &mut ScriptReflowResult,
possibly_locked_rw_data: &mut RwData<'a, 'b>,
) {
let document = unsafe { ServoLayoutNode::new(&data.document) };
let document = document.as_document().unwrap();
// Parallelize if there's more than 750 objects based on rzambre's suggestion
// https://github.com/servo/servo/issues/10110
self.parallel_flag = data.dom_count > 750;
debug!("layout: received layout request for: {}", self.url);
debug!("Number of objects in DOM: {}", data.dom_count);
debug!("layout: parallel? {}", self.parallel_flag);
let mut rw_data = possibly_locked_rw_data.lock();
// Record the time that layout query has been waited.
let now = time::precise_time_ns();
if let ReflowGoal::LayoutQuery(_, timestamp) = data.reflow_goal {
self.layout_query_waiting_time
.increment(now - timestamp)
.expect("layout: wrong layout query timestamp");
};
let root_element = match document.root_element() {
None => {
// Since we cannot compute anything, give spec-required placeholders.
debug!("layout: No root node: bailing");
match data.reflow_goal {
ReflowGoal::LayoutQuery(ref query_msg, _) => match query_msg {
&QueryMsg::ContentBoxQuery(_) => {
rw_data.content_box_response = None;
},
&QueryMsg::ContentBoxesQuery(_) => {
rw_data.content_boxes_response = Vec::new();
},
&QueryMsg::NodesFromPointQuery(..) => {
rw_data.nodes_from_point_response = Vec::new();
},
&QueryMsg::ClientRectQuery(_) => {
rw_data.client_rect_response = Rect::zero();
},
&QueryMsg::NodeScrollGeometryQuery(_) => {
rw_data.scroll_area_response = Rect::zero();
},
&QueryMsg::NodeScrollIdQuery(_) => {
rw_data.scroll_id_response = None;
},
&QueryMsg::ResolvedStyleQuery(_, _, _) => {
rw_data.resolved_style_response = String::new();
},
&QueryMsg::OffsetParentQuery(_) => {
rw_data.offset_parent_response = OffsetParentResponse::empty();
},
&QueryMsg::StyleQuery => {},
&QueryMsg::TextIndexQuery(..) => {
rw_data.text_index_response = TextIndexResponse(None);
},
&QueryMsg::ElementInnerTextQuery(_) => {
rw_data.element_inner_text_response = String::new();
},
&QueryMsg::ResolvedFontStyleQuery(..) => {
rw_data.resolved_font_style_response = None;
},
&QueryMsg::InnerWindowDimensionsQuery(_) => {
rw_data.inner_window_dimensions_response = None;
},
},
ReflowGoal::Full |
ReflowGoal::TickAnimations |
ReflowGoal::UpdateScrollNode(_) => {},
}
return;
},
Some(x) => x,
};
debug!(
"layout: processing reflow request for: {:?} ({}) (query={:?})",
root_element, self.url, data.reflow_goal
);
trace!("{:?}", ShowSubtree(root_element.as_node()));
let initial_viewport = data.window_size.initial_viewport;
let device_pixel_ratio = data.window_size.device_pixel_ratio;
let old_viewport_size = self.viewport_size;
let current_screen_size = Size2D::new(
Au::from_f32_px(initial_viewport.width),
Au::from_f32_px(initial_viewport.height),
);
let origin = data.origin.clone();
// Calculate the actual viewport as per DEVICE-ADAPT § 6
// If the entire flow tree is invalid, then it will be reflowed anyhow.
let document_shared_lock = document.style_shared_lock();
let author_guard = document_shared_lock.read();
let ua_stylesheets = &*UA_STYLESHEETS;
let ua_or_user_guard = ua_stylesheets.shared_lock.read();
let guards = StylesheetGuards {
author: &author_guard,
ua_or_user: &ua_or_user_guard,
};
let had_used_viewport_units = self.stylist.device().used_viewport_units();
let device = Device::new(
MediaType::screen(),
self.stylist.quirks_mode(),
initial_viewport,
device_pixel_ratio,
);
let sheet_origins_affected_by_device_change = self.stylist.set_device(device, &guards);
self.stylist
.force_stylesheet_origins_dirty(sheet_origins_affected_by_device_change);
self.viewport_size = current_screen_size;
let viewport_size_changed = self.viewport_size != old_viewport_size;
if viewport_size_changed && had_used_viewport_units {
if let Some(mut data) = root_element.mutate_data() {
data.hint.insert(RestyleHint::recascade_subtree());
}
}
if self.first_reflow.get() {
debug!("First reflow, rebuilding user and UA rules");
for stylesheet in &ua_stylesheets.user_or_user_agent_stylesheets {
self.stylist
.append_stylesheet(stylesheet.clone(), &ua_or_user_guard);
self.handle_add_stylesheet(&stylesheet.0, &ua_or_user_guard);
}
if self.stylist.quirks_mode() != QuirksMode::NoQuirks {
self.stylist.append_stylesheet(
ua_stylesheets.quirks_mode_stylesheet.clone(),
&ua_or_user_guard,
);
self.handle_add_stylesheet(
&ua_stylesheets.quirks_mode_stylesheet.0,
&ua_or_user_guard,
);
}
}
if data.stylesheets_changed {
debug!("Doc sheets changed, flushing author sheets too");
self.stylist
.force_stylesheet_origins_dirty(Origin::Author.into());
}
if viewport_size_changed {
if let Some(mut flow) = self.try_get_layout_root(root_element.as_node()) {
LayoutThread::reflow_all_nodes(FlowRef::deref_mut(&mut flow));
}
}
debug!(
"Shadow roots in document {:?}",
document.shadow_roots().len()
);
// Flush shadow roots stylesheets if dirty.
document.flush_shadow_roots_stylesheets(&mut self.stylist, guards.author.clone());
let restyles = std::mem::take(&mut data.pending_restyles);
debug!("Draining restyles: {}", restyles.len());
let mut map = SnapshotMap::new();
let elements_with_snapshot: Vec<_> = restyles
.iter()
.filter(|r| r.1.snapshot.is_some())
.map(|r| unsafe {
ServoLayoutNode::<LayoutData>::new(&r.0)
.as_element()
.unwrap()
})
.collect();
for (el, restyle) in restyles {
let el: ServoLayoutElement<LayoutData> =
unsafe { ServoLayoutNode::new(&el).as_element().unwrap() };
// If we haven't styled this node yet, we don't need to track a
// restyle.
let mut style_data = match el.mutate_data() {
Some(d) => d,
None => {
unsafe { el.unset_snapshot_flags() };
continue;
},
};
if let Some(s) = restyle.snapshot {
unsafe { el.set_has_snapshot() };
map.insert(el.as_node().opaque(), s);
}
// Stash the data on the element for processing by the style system.
style_data.hint.insert(restyle.hint.into());
style_data.damage = restyle.damage;
debug!("Noting restyle for {:?}: {:?}", el, style_data);
}
self.stylist.flush(&guards, Some(root_element), Some(&map));
// Create a layout context for use throughout the following passes.
let mut layout_context = self.build_layout_context(
guards.clone(),
&map,
origin,
data.animation_timeline_value,
&data.animations,
data.stylesheets_changed,
);
let pool = STYLE_THREAD_POOL.lock().unwrap();
let thread_pool = pool.pool();
let (thread_pool, num_threads) = if self.parallel_flag {
(thread_pool.as_ref(), pool.num_threads.unwrap_or(1))
} else {
(None, 1)
};
let dirty_root = unsafe {
ServoLayoutNode::new(&data.dirty_root.unwrap())
.as_element()
.unwrap()
};
let traversal = RecalcStyleAndConstructFlows::new(layout_context);
let token = {
let shared = <RecalcStyleAndConstructFlows as DomTraversal<
ServoLayoutElement<LayoutData>,
>>::shared_context(&traversal);
RecalcStyleAndConstructFlows::pre_traverse(dirty_root, shared)
};
if token.should_traverse() {
// Recalculate CSS styles and rebuild flows and fragments.
profile(
profile_time::ProfilerCategory::LayoutStyleRecalc,
self.profiler_metadata(),
self.time_profiler_chan.clone(),
|| {
// Perform CSS selector matching and flow construction.
let root = driver::traverse_dom::<
ServoLayoutElement<LayoutData>,
RecalcStyleAndConstructFlows,
>(&traversal, token, thread_pool);
unsafe {
construct_flows_at_ancestors(traversal.context(), root.as_node());
}
},
);
// TODO(pcwalton): Measure energy usage of text shaping, perhaps?
let text_shaping_time =
font::get_and_reset_text_shaping_performance_counter() / num_threads;
profile_time::send_profile_data(
profile_time::ProfilerCategory::LayoutTextShaping,
self.profiler_metadata(),
&self.time_profiler_chan,
0,
text_shaping_time as u64,
);
// Retrieve the (possibly rebuilt) root flow.
*self.root_flow.borrow_mut() = self.try_get_layout_root(root_element.as_node());
}
for element in elements_with_snapshot {
unsafe { element.unset_snapshot_flags() }
}
layout_context = traversal.destroy();
if self.debug.dump_style_tree {
println!(
"{:?}",
ShowSubtreeDataAndPrimaryValues(root_element.as_node())
);
}
if self.debug.dump_rule_tree {
layout_context
.style_context
.stylist
.rule_tree()
.dump_stdout(&guards);
}
// GC the rule tree if some heuristics are met.
layout_context.style_context.stylist.rule_tree().maybe_gc();
// Perform post-style recalculation layout passes.
if let Some(mut root_flow) = self.root_flow.borrow().clone() {
self.perform_post_style_recalc_layout_passes(
&mut root_flow,
&data.reflow_info,
&data.reflow_goal,
Some(&document),
&mut rw_data,
&mut layout_context,
thread_pool,
);
}
self.first_reflow.set(false);
self.respond_to_query_if_necessary(
&data.reflow_goal,
&mut *rw_data,
&mut layout_context,
data.result.borrow_mut().as_mut().unwrap(),
document_shared_lock,
);
}
fn respond_to_query_if_necessary(
&self,
reflow_goal: &ReflowGoal,
rw_data: &mut LayoutThreadData,
context: &mut LayoutContext,
reflow_result: &mut ReflowComplete,
shared_lock: &SharedRwLock,
) {
reflow_result.pending_images =
std::mem::replace(&mut *context.pending_images.lock().unwrap(), vec![]);
let mut root_flow = match self.root_flow.borrow().clone() {
Some(root_flow) => root_flow,
None => return,
};
let root_flow = FlowRef::deref_mut(&mut root_flow);
match *reflow_goal {
ReflowGoal::LayoutQuery(ref querymsg, _) => match querymsg {
&QueryMsg::ContentBoxQuery(node) => {
rw_data.content_box_response = process_content_box_request(node, root_flow);
},
&QueryMsg::ContentBoxesQuery(node) => {
rw_data.content_boxes_response = process_content_boxes_request(node, root_flow);
},
&QueryMsg::TextIndexQuery(node, point_in_node) => {
let point_in_node = Point2D::new(
Au::from_f32_px(point_in_node.x),
Au::from_f32_px(point_in_node.y),
);
rw_data.text_index_response =
TextIndexResponse(rw_data.indexable_text.text_index(node, point_in_node));
},
&QueryMsg::ClientRectQuery(node) => {
rw_data.client_rect_response = process_client_rect_query(node, root_flow);
},
&QueryMsg::NodeScrollGeometryQuery(node) => {
rw_data.scroll_area_response =
process_node_scroll_area_request(node, root_flow);
},
&QueryMsg::NodeScrollIdQuery(node) => {
let node: ServoLayoutNode<LayoutData> = unsafe { ServoLayoutNode::new(&node) };
rw_data.scroll_id_response =
Some(process_node_scroll_id_request(self.id, node));
},
&QueryMsg::ResolvedStyleQuery(node, ref pseudo, ref property) => {
let node: ServoLayoutNode<LayoutData> = unsafe { ServoLayoutNode::new(&node) };
rw_data.resolved_style_response =
process_resolved_style_request(context, node, pseudo, property, root_flow);
},
&QueryMsg::ResolvedFontStyleQuery(node, ref property, ref value) => {
let node: ServoLayoutNode<LayoutData> = unsafe { ServoLayoutNode::new(&node) };
let url = self.url.clone();
rw_data.resolved_font_style_response = process_resolved_font_style_request(
context,
node,
value,
property,
url,
shared_lock,
);
},
&QueryMsg::OffsetParentQuery(node) => {
rw_data.offset_parent_response = process_offset_parent_query(node, root_flow);
},
&QueryMsg::StyleQuery => {},
&QueryMsg::NodesFromPointQuery(client_point, ref reflow_goal) => {
let mut flags = match reflow_goal {
&NodesFromPointQueryType::Topmost => HitTestFlags::empty(),
&NodesFromPointQueryType::All => HitTestFlags::FIND_ALL,
};
// The point we get is not relative to the entire WebRender scene, but to this
// particular pipeline, so we need to tell WebRender about that.
flags.insert(HitTestFlags::POINT_RELATIVE_TO_PIPELINE_VIEWPORT);
let client_point = units::WorldPoint::from_untyped(client_point);
let results = self.webrender_api.hit_test(
Some(self.id.to_webrender()),
client_point,
flags,
);
rw_data.nodes_from_point_response =
results.iter().map(|result| result.node).collect()
},
&QueryMsg::ElementInnerTextQuery(node) => {
let node: ServoLayoutNode<LayoutData> = unsafe { ServoLayoutNode::new(&node) };
rw_data.element_inner_text_response =
process_element_inner_text_query(node, &rw_data.indexable_text);
},
&QueryMsg::InnerWindowDimensionsQuery(browsing_context_id) => {
rw_data.inner_window_dimensions_response = self
.last_iframe_sizes
.borrow()
.get(&browsing_context_id)
.cloned();
},
},
ReflowGoal::UpdateScrollNode(scroll_state) => {
self.update_scroll_node_state(&scroll_state, rw_data);
},
ReflowGoal::Full | ReflowGoal::TickAnimations => {},
}
}
fn update_scroll_node_state(&self, state: &ScrollState, rw_data: &mut LayoutThreadData) {
rw_data
.scroll_offsets
.insert(state.scroll_id, state.scroll_offset);
let point = Point2D::new(-state.scroll_offset.x, -state.scroll_offset.y);
self.webrender_api.send_scroll_node(
units::LayoutPoint::from_untyped(point),
state.scroll_id,
ScrollClamping::ToContentBounds,
);
}
fn set_scroll_states<'a, 'b>(
&mut self,
new_scroll_states: Vec<ScrollState>,
possibly_locked_rw_data: &mut RwData<'a, 'b>,
) {
let mut rw_data = possibly_locked_rw_data.lock();
let mut script_scroll_states = vec![];
let mut layout_scroll_states = HashMap::new();
for new_state in &new_scroll_states {
let offset = new_state.scroll_offset;
layout_scroll_states.insert(new_state.scroll_id, offset);
if new_state.scroll_id.is_root() {
script_scroll_states.push((UntrustedNodeAddress::from_id(0), offset))
} else if let Some(node_id) = node_id_from_scroll_id(new_state.scroll_id.0 as usize) {
script_scroll_states.push((UntrustedNodeAddress::from_id(node_id), offset))
}
}
let _ = self
.script_chan
.send(ConstellationControlMsg::SetScrollState(
self.id,
script_scroll_states,
));
rw_data.scroll_offsets = layout_scroll_states
}
/// Cancel animations for any nodes which have been removed from flow tree.
/// TODO(mrobinson): We should look into a way of doing this during flow tree construction.
/// This also doesn't yet handles nodes that have been reparented.
fn cancel_animations_for_nodes_not_in_flow_tree(
animations: &mut FxHashMap<AnimationSetKey, ElementAnimationSet>,
root_flow: &mut dyn Flow,
) {
// Assume all nodes have been removed until proven otherwise.
let mut invalid_nodes = animations.keys().cloned().collect();
fn traverse_flow(flow: &mut dyn Flow, invalid_nodes: &mut FxHashSet<AnimationSetKey>) {
flow.mutate_fragments(&mut |fragment| {
// Ideally we'd only not cancel ::before and ::after animations if they
// were actually in the tree. At this point layout has lost information
// about whether or not they exist, but have had their fragments accumulated
// together.
invalid_nodes.remove(&AnimationSetKey::new_for_non_pseudo(fragment.node));
invalid_nodes.remove(&AnimationSetKey::new_for_pseudo(
fragment.node,
PseudoElement::Before,
));
invalid_nodes.remove(&AnimationSetKey::new_for_pseudo(
fragment.node,
PseudoElement::After,
));
});
for kid in flow.mut_base().children.iter_mut() {
traverse_flow(kid, invalid_nodes)
}
}
traverse_flow(root_flow, &mut invalid_nodes);
// Cancel animations for any nodes that are no longer in the flow tree.
for node in &invalid_nodes {
if let Some(state) = animations.get_mut(node) {
state.cancel_all_animations();
}
}
}
fn perform_post_style_recalc_layout_passes(
&self,
root_flow: &mut FlowRef,
data: &Reflow,
reflow_goal: &ReflowGoal,
document: Option<&ServoLayoutDocument<LayoutData>>,
rw_data: &mut LayoutThreadData,
context: &mut LayoutContext,
thread_pool: Option<&rayon::ThreadPool>,
) {
Self::cancel_animations_for_nodes_not_in_flow_tree(
&mut *(context.style_context.animations.sets.write()),
FlowRef::deref_mut(root_flow),
);
profile(
profile_time::ProfilerCategory::LayoutRestyleDamagePropagation,
self.profiler_metadata(),
self.time_profiler_chan.clone(),
|| {
// Call `compute_layout_damage` even in non-incremental mode, because it sets flags
// that are needed in both incremental and non-incremental traversals.
let damage = FlowRef::deref_mut(root_flow).compute_layout_damage();
if self.nonincremental_layout ||
damage.contains(SpecialRestyleDamage::REFLOW_ENTIRE_DOCUMENT)
{
FlowRef::deref_mut(root_flow).reflow_entire_document()
}
},
);
if self.debug.trace_layout {
layout_debug::begin_trace(root_flow.clone());
}
// Resolve generated content.
profile(
profile_time::ProfilerCategory::LayoutGeneratedContent,
self.profiler_metadata(),
self.time_profiler_chan.clone(),
|| sequential::resolve_generated_content(FlowRef::deref_mut(root_flow), &context),
);
// Guess float placement.
profile(
profile_time::ProfilerCategory::LayoutFloatPlacementSpeculation,
self.profiler_metadata(),
self.time_profiler_chan.clone(),
|| sequential::guess_float_placement(FlowRef::deref_mut(root_flow)),
);
// Perform the primary layout passes over the flow tree to compute the locations of all
// the boxes.
if root_flow
.base()
.restyle_damage
.intersects(ServoRestyleDamage::REFLOW | ServoRestyleDamage::REFLOW_OUT_OF_FLOW)
{
profile(
profile_time::ProfilerCategory::LayoutMain,
self.profiler_metadata(),
self.time_profiler_chan.clone(),
|| {
let profiler_metadata = self.profiler_metadata();
if let Some(pool) = thread_pool {
// Parallel mode.
LayoutThread::solve_constraints_parallel(
pool,
FlowRef::deref_mut(root_flow),
profiler_metadata,
self.time_profiler_chan.clone(),
&*context,
);
} else {
//Sequential mode
LayoutThread::solve_constraints(FlowRef::deref_mut(root_flow), &context)
}
},
);
}
profile(
profile_time::ProfilerCategory::LayoutStoreOverflow,
self.profiler_metadata(),
self.time_profiler_chan.clone(),
|| {
sequential::store_overflow(context, FlowRef::deref_mut(root_flow) as &mut dyn Flow);
},
);
self.perform_post_main_layout_passes(
data,
root_flow,
reflow_goal,
document,
rw_data,
context,
);
}
fn perform_post_main_layout_passes(
&self,
data: &Reflow,
mut root_flow: &mut FlowRef,
reflow_goal: &ReflowGoal,
document: Option<&ServoLayoutDocument<LayoutData>>,
rw_data: &mut LayoutThreadData,
layout_context: &mut LayoutContext,
) {
// Build the display list if necessary, and send it to the painter.
self.compute_abs_pos_and_build_display_list(
data,
reflow_goal,
document,
FlowRef::deref_mut(&mut root_flow),
&mut *layout_context,
rw_data,
);
if self.debug.trace_layout {
layout_debug::end_trace(self.generation.get());
}
if self.debug.dump_flow_tree {
root_flow.print("Post layout flow tree".to_owned());
}
self.generation.set(self.generation.get() + 1);
}
fn reflow_all_nodes(flow: &mut dyn Flow) {
debug!("reflowing all nodes!");
flow.mut_base().restyle_damage.insert(
ServoRestyleDamage::REPAINT |
ServoRestyleDamage::STORE_OVERFLOW |
ServoRestyleDamage::REFLOW |
ServoRestyleDamage::REPOSITION,
);
for child in flow.mut_base().child_iter_mut() {
LayoutThread::reflow_all_nodes(child);
}
}
/// Returns profiling information which is passed to the time profiler.
fn profiler_metadata(&self) -> Option<TimerMetadata> {
Some(TimerMetadata {
url: self.url.to_string(),
iframe: if self.is_iframe {
TimerMetadataFrameType::IFrame
} else {
TimerMetadataFrameType::RootWindow
},
incremental: if self.first_reflow.get() {
TimerMetadataReflowType::FirstReflow
} else {
TimerMetadataReflowType::Incremental
},
})
}
}
impl ProfilerMetadataFactory for LayoutThread {
fn new_metadata(&self) -> Option<TimerMetadata> {
self.profiler_metadata()
}
}
// The default computed value for background-color is transparent (see
// http://dev.w3.org/csswg/css-backgrounds/#background-color). However, we
// need to propagate the background color from the root HTML/Body
// element (http://dev.w3.org/csswg/css-backgrounds/#special-backgrounds) if
// it is non-transparent. The phrase in the spec "If the canvas background
// is not opaque, what shows through is UA-dependent." is handled by rust-layers
// clearing the frame buffer to white. This ensures that setting a background
// color on an iframe element, while the iframe content itself has a default
// transparent background color is handled correctly.
fn get_root_flow_background_color(flow: &mut dyn Flow) -> ColorF {
let transparent = ColorF {
r: 0.0,
g: 0.0,
b: 0.0,
a: 0.0,
};
if !flow.is_block_like() {
return transparent;
}
let block_flow = flow.as_mut_block();
let kid = match block_flow.base.children.iter_mut().next() {
None => return transparent,
Some(kid) => kid,
};
if !kid.is_block_like() {
return transparent;
}
let kid_block_flow = kid.as_block();
let color = kid_block_flow.fragment.style.resolve_color(
kid_block_flow
.fragment
.style
.get_background()
.background_color
.clone(),
);
ColorF::new(
color.red_f32(),
color.green_f32(),
color.blue_f32(),
color.alpha_f32(),
)
}
fn get_ua_stylesheets() -> Result<UserAgentStylesheets, &'static str> {
fn parse_ua_stylesheet(
shared_lock: &SharedRwLock,
filename: &str,
content: &[u8],
) -> Result<DocumentStyleSheet, &'static str> {
Ok(DocumentStyleSheet(ServoArc::new(Stylesheet::from_bytes(
content,
ServoUrl::parse(&format!("chrome://resources/{:?}", filename)).unwrap(),
None,
None,
Origin::UserAgent,
MediaList::empty(),
shared_lock.clone(),
None,
None,
QuirksMode::NoQuirks,
))))
}
let shared_lock = &GLOBAL_STYLE_DATA.shared_lock;
// FIXME: presentational-hints.css should be at author origin with zero specificity.
// (Does it make a difference?)
let mut user_or_user_agent_stylesheets = vec![
parse_ua_stylesheet(
&shared_lock,
"user-agent.css",
&resources::read_bytes(Resource::UserAgentCSS),
)?,
parse_ua_stylesheet(
&shared_lock,
"servo.css",
&resources::read_bytes(Resource::ServoCSS),
)?,
parse_ua_stylesheet(
&shared_lock,
"presentational-hints.css",
&resources::read_bytes(Resource::PresentationalHintsCSS),
)?,
];
for &(ref contents, ref url) in &opts::get().user_stylesheets {
user_or_user_agent_stylesheets.push(DocumentStyleSheet(ServoArc::new(
Stylesheet::from_bytes(
&contents,
url.clone(),
None,
None,
Origin::User,
MediaList::empty(),
shared_lock.clone(),
None,
Some(&RustLogReporter),
QuirksMode::NoQuirks,
),
)));
}
let quirks_mode_stylesheet = parse_ua_stylesheet(
&shared_lock,
"quirks-mode.css",
&resources::read_bytes(Resource::QuirksModeCSS),
)?;
Ok(UserAgentStylesheets {
shared_lock: shared_lock.clone(),
user_or_user_agent_stylesheets: user_or_user_agent_stylesheets,
quirks_mode_stylesheet: quirks_mode_stylesheet,
})
}
lazy_static! {
static ref UA_STYLESHEETS: UserAgentStylesheets = {
match get_ua_stylesheets() {
Ok(stylesheets) => stylesheets,
Err(filename) => {
error!("Failed to load UA stylesheet {}!", filename);
process::exit(1);
},
}
};
}
struct RegisteredPainterImpl {
painter: Box<dyn Painter>,
name: Atom,
// FIXME: Should be a PrecomputedHashMap.
properties: FxHashMap<Atom, PropertyId>,
}
impl SpeculativePainter for RegisteredPainterImpl {
fn speculatively_draw_a_paint_image(
&self,
properties: Vec<(Atom, String)>,
arguments: Vec<String>,
) {
self.painter
.speculatively_draw_a_paint_image(properties, arguments);
}
}
impl RegisteredSpeculativePainter for RegisteredPainterImpl {
fn properties(&self) -> &FxHashMap<Atom, PropertyId> {
&self.properties
}
fn name(&self) -> Atom {
self.name.clone()
}
}
impl Painter for RegisteredPainterImpl {
fn draw_a_paint_image(
&self,
size: Size2D<f32, CSSPixel>,
device_pixel_ratio: Scale<f32, CSSPixel, DevicePixel>,
properties: Vec<(Atom, String)>,
arguments: Vec<String>,
) -> Result<DrawAPaintImageResult, PaintWorkletError> {
self.painter
.draw_a_paint_image(size, device_pixel_ratio, properties, arguments)
}
}
impl RegisteredPainter for RegisteredPainterImpl {}
struct RegisteredPaintersImpl(FnvHashMap<Atom, RegisteredPainterImpl>);
impl RegisteredSpeculativePainters for RegisteredPaintersImpl {
fn get(&self, name: &Atom) -> Option<&dyn RegisteredSpeculativePainter> {
self.0
.get(&name)
.map(|painter| painter as &dyn RegisteredSpeculativePainter)
}
}
impl RegisteredPainters for RegisteredPaintersImpl {
fn get(&self, name: &Atom) -> Option<&dyn RegisteredPainter> {
self.0
.get(&name)
.map(|painter| painter as &dyn RegisteredPainter)
}
}