This change reworks the layout DOM wrappers so that they are simpler and
easier to reason about. The main changes here:
**Combine layout wrappers into one interface:**
- `LayoutNode`/`ThreadSafeLayoutNode` is combined into `LayoutNode`:
The idea here is that `LayoutNode` is always thread-safe when used in
layout as long as no `unsafe` calls are used. These interfaces
only expose what is necessary for layout.
- `LayoutElement`/`ThreadSafeLayoutElement` is combined into
`LayoutElement`: See above.
**Expose two new interfaces to be used *only* with `stylo` and
`selectors`:**
`DangerousStyleNode` and `DangerousStyleElement`. `stylo`
and `selectors` have a different way of ensuring safety that is
incompatible with Servo's layout (access all of the DOM tree anywhere,
but ensure that writing only happens from a single-thread). These types
only implement things like `TElement`, `TNode` and are not intended to
be used by layout at all.
All traits and implementations are moved to files that are named after
the struct or trait inside them, in order to better understand what one
is looking at.
The main goals here are:
- Make it easier to reason about the safe use of the DOM APIs.
- Remove the interdependencies between the `stylo` and `selectors`
interface implementations and the layout interface. This helps
with the first point as well and makes it simpler to know where
a method is implemented.
- Reduce the amount of code.
- Make it possible to eliminate `TrustedNodeAddress` in the future.
- Document and bring the method naming up to modern Rust conventions.
This is a lot of code changes, but is very well tested by the WPT tests.
Unfortunately, it is difficult to make a change like this iteratively.
In addition, this new design comes with new documentation at
servo/book#225.
Testing: This should not change behavior so should be covered by
existing
WPT tests.
Signed-off-by: Martin Robinson <mrobinson@fastmail.fm>
This change merges the first letter implementation into the
`InlineFormattingContextBuilder` as its more associated with inline
layout. The downside is that, due to ownership issues in Rust, the
builder must be unwrapped after ensuring it. Additionally, ensure
that `::first-letter` boxes are properly stored in a box slot meaning
that restyles work properly.
This change also makes a few small cleanups to the `first_letter_range`
function in addition to moving it to the inline code.
Testing: This does not change behavior and so should be covered by
existing tests.
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Luke Warlow <lwarlow@igalia.com>
Co-authored-by: Oriol Brufau <obrufau@igalia.com>
When encountering a block-level inside an inline box, we would wrap it
(together with other block-level siblings, if any) inside an anonymous
block box.
That was complicating the logic for no real benefit, so just get rid of
these anonymous wrappers, and allow block-levels as direct children of
inlines.
This aligns Servo with WebKit, which did the same refactoring:
https://commits.webkit.org/304357@main
Blink still generates these wrappers, but its design doc acknowledges
that "the anonymous box is not strictly required".
Testing: No existing test fails. But note that, while minimal, this may
have some observable implications. For example, as an accidental
side-effect of #41492, Servo aligned with the CSSWG resolution in
https://github.com/w3c/csswg-drafts/issues/11462, but now we will revert
to the previous behavior. I will address it in a follow-up, and add a
test.
Fixes: #41636
Signed-off-by: Oriol Brufau <obrufau@igalia.com>
`OutsideMarker::repair_style()` was setting `list_item_style` to the
style of the marker, not the style of the list item.
This patch sets it to `node.parent_style(context)` instead, and it fixes
`ServoThreadSafeLayoutNode::parent_style()` to take the pseudo-element
chain into account. This requires modifying a bunch of logic to pass the
`SharedStyleContext` around.
Testing: Adding a new test
Signed-off-by: Oriol Brufau <obrufau@igalia.com>
This change adds support for the unspecified `-webkit-text-security`
property. All major browsers support this property. The benefit of
adding this property is that the first addition of a prefixed CSS
property will allow us to start running MotionMark.
This depends on servo/stylo#295.
Testing: Three new Servo-specific tests are added for this change. There
are a few `-webkit-text-security` tests in WPT, but they do not seem
to test basic functionality.
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This change is a reworking of the shaping code and simplification of the
`GlyphRun` data structure.
The shaper was written between 2012 and 2014 against an early version of
Rust. It was originally written to have a single glyph entry per UTF-8
code point. This is useful when you always need to iterate through
glyphs based on UTF-8 code points. Unfortunately, this required a
tri-level data structure to hold detailed glyphs and meant that CJK
characters took over 3x the memory usage of ASCII characters. In
addition, iterating through glyphs (the most common and basic operation
on shaped text) required doing a lookup involving a binary search for
detailed glyphs (ones that had large advances or mapped to more or less
than a single UTF-8 code point).
The new design of the `GlyphStore` is instead based on `chars` in the
input string. These are tracked during layout so that the resulting
glyph output can be interpreted relatively to its original character
offset in the containing IFC. We are already dealing with IFC text on a
per-character basis for a variety of reasons (such as text
transformation and whitespace collapse). In addition, we will now able
to
implement mapping between the character offsets before and after layout
transformations of the original DOM string.
Now the penalty of more complex glyph iteration is only paid when
transforming glyph offsets to character offsets. Currently this is only
done for selections and clicking in text boxes, both of which are much
less common than layout.
This change does not properly handle selections in RTL text, though
rendering and basic selection and visual movement works (though buggy).
It does not seem like this affects the performance of shaping based on
measurement using the text shaping performance counters. This likely
means that the performance of shaping is dominated on our machines by
HarfBuzz. We noticed no performance degradation in Speedometer when run
on a M3 Mac.
Followup work:
- Properly handle selection in RTL text.
- Support mapping from original DOM character offsets to offsets in
layout after text transformation and whitespace collapse. This is now
possible.
Testing: This causes some tests to pass and a few to fail. This is
likely
due to the fact that we are handling glyphs more consistently while
shaping. Of the new failures:
- `letter-spacing-bengali-yaphala-001.html`,
`letter-spacing-cursive-001.html`, `font-feature-settings-tibetan.html`
where passing before probably because we were not applying letter
spacing to detailed glyphs. These scripts should not have letter spacing
applied to them, because they are cursive -- which we never implemented
properly. It will be handled in a a followup.
- `shaping-arabic-diacritics-001.html`: This was a false pass. The tests
verifies that Arabic diacritics are applied to NBSP. This wasn't
happening before nor after this change, but the results matched anyway.
Now they don't, but before and after are equally broken.
-
Fixes: #216
Part of #35540.
---------
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Oriol Brufau <obrufau@igalia.com>
In DOM traversal, `display:contents `elements don't generate boxes, but
their styles still need to apply to children.
`ModernContainerBuilder` now keeps track of `display:contents` ancestors
during traversal then apply their style to text runs.
In `InlineFormattingContextBuilder::push_text` only use same style
parent if both selected and current inline style are identical.
Testing:
> PASS [expected FAIL]
/css/css-display/display-contents-dynamic-before-after-001.html
> PASS [expected FAIL]
/css/css-display/display-contents-dynamic-inline-flex-001-inline.html
> PASS [expected FAIL]
/css/css-display/display-contents-dynamic-inline-flex-001-none.html
> FAIL [expected PASS] /css/css-display/display-contents-fieldset.html
> PASS [expected FAIL]
/css/css-display/display-contents-first-line-002.html
> PASS [expected FAIL] /css/css-display/display-contents-inline-001.html
> PASS [expected FAIL]
/css/css-display/display-contents-inline-flex-001.html
> PASS [expected FAIL]
/css/css-display/display-contents-line-height.html
Fixes: https://github.com/servo/servo/issues/41797
cc: @xiaochengh
Signed-off-by: Oriol Brufau <obrufau@igalia.com>
Co-authored-by: Oriol Brufau <obrufau@igalia.com>
Co-authored-by: Martin Robinson <mrobinson@igalia.com>
Instead of handling selection in text input during box tree / fragment
tree construction, fully handle it during display list construction.
This means that when the selection changes in script, it updates
automatically in the `FragmentTree` and only a new display list is
necessary. This avoids a layout while changing the selection in text
fields.
Testing: This fixes a few rendering issues, but these are very hard
to isolate and test for. It causes one test to start failing, but this
is
because a cursor that wasn't rendered properly now starts showing
up.
Fixes: #41920.
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Oriol Brufau <obrufau@igalia.com>
Changes `LayoutBox::InlineLevel()` to have a raw `InlineItem` instead of
an `ArcRefCell<InlineItem>`. `InlineItem` is an enum where all the
options already use `ArcRefCell`, so the outer `ArcRefCell` wasn't
really necessary.
Testing: Not needed, no behavior change
Signed-off-by: Oriol Brufau <obrufau@igalia.com>
Co-authored-by: Martin Robinson <mrobinson@igalia.com>
We now follow the same approach as Blink: sequences of block-levels in
an inline formatting context get wrapped in an anonymous block, which is
treated as a line item. Inline ancestors are no longer split.
This means that inline elements will now generate a single inline box,
and the box tree makes more sense in general. This will help for
incremental layout.
This also means that block-level elements will now be properly affected
by effects like opacity of filters set on inline ancestors.
Recently, WebKit has done some similar work, but without the anonymous
blocks. We might also consider removing them in the future.
Google's explainer:
https://docs.google.com/document/d/15kgdIHhb9EVNup6Ir5NWwJxpzY5GH0ED7Ld3iMW3HlA/
Testing: Several tests are now passing
Fixes: #39813
Signed-off-by: Oriol Brufau <obrufau@igalia.com>
And let the Compositor create the `PainterId` when it creates a new
`Painter`. Also make inline formatting code take `LayoutContext` rather
than threading it via the `InlineFormattingContextBuilder`.
Testing: Should preserve existing behavior, so covered by existing
tests.
Signed-off-by: Mukilan Thiyagarajan <mukilan@igalia.com>
Co-authored-by: Martin Robinson <mrobinson@igalia.com>
This patch removes the `block_in_inline_splits` parameter, since the
single caller was just passing None. Then this parameter will only be
provided for `start_inline_box_internal()`, which is made private since
its an internal method as the name indicates.
Testing: Not needed, no behavior change
Signed-off-by: Oriol Brufau <obrufau@igalia.com>
Motivation: The font cache currently has to store a cache of Keys which
need to be given by the webrender instance.
Having a cache for every WebViewId in the future when we have every
webview have the different webrender::DocumentId might be too wasteful
to store this key cache per DocumentId. This proposes to include in the
WebViewId another id, the RenderingGroupId. This id can be easily
changed
to be equivalent to the DocumentId when we support multiple DocumentIds
for a unique Webrender instance.
Additionally this will keep it easier to integrate the currently out of
tree patches for multiple rendering contexts with different webrenders.
Change:
We introduce the RenderingGroupId in the WebViewId and allow a method to
extract it. The font key cache uses this cache
and forwards it to the Compositor when requesting new changes. The
compositor currently ignores this id.
Additionally, the WebView can return the RenderingGroupId. The WebViewId
also has an appropiate constructor for specifying a RenderingGroupId.
Because there currently will be only one RenderingGroupId the
performance will be minimal.
Signed-off-by: Narfinger <Narfinger@users.noreply.github.com>
Testing: This should be covered by WPT tests and normal browsing
behavior works fine.
---------
Signed-off-by: Narfinger <Narfinger@users.noreply.github.com>
This method could iterate all the items in the inline formatting context
that was being created. This patch turns it into a field, replacing
`has_uncollapsible_text_content` (so this doesn't increase memory).
Testing: Not needed, no behavior change
Signed-off-by: Oriol Brufau <obrufau@igalia.com>
This shows up sometimes in code reviews, so it makes sense that tidy
enforces it. `rustfmt` supports this via comment normalization, but it
does many other things and is still an unstable feature (with bugs).
Testing: There are new tidy tests for this change.
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This changes extend the incremental box tree construction for inline
boxes. Since an `InlineItem` can be split into multiple `InlineItem`s by
a block element, the reason such an inline item is marked as damaged may
simply be the removal of the block element or the need to reconstruct
its box tree. Therefore, under the current LayoutDamage design,
theoretically, even damaged inline items might still have some of their
splits reusable. However, based on the principle of simplicity and
effectiveness, this PR only considers reusing undamaged inline boxes.
Testing: This should not change observable behavior and is thus covered
by existing WPT tests.
Signed-off-by: sharpshooter_pt <ibluegalaxy_taoj@163.com>
Layout: Add incremental box tree construction for inline floats and
abspos
Due to false positives in the memory benchmark on CI, the previous PR
[37868](https://github.com/servo/servo/pull/37868) reverted. Now it is
resubmitted.
Signed-off-by: sharpshooter_pt <ibluegalaxy_taoj@163.com>
This changes extend the incremental box tree construction for inline
out-of-flow-box, including the
`InlineItem::OutOfFlowAbsolutelyPositionedBox` and
`InlineItem::OutOfFlowFloatBox`.
Testing: This should not change observable behavior and is thus covered
by existing WPT tests.
Signed-off-by: sharpshooter_pt <ibluegalaxy_taoj@163.com>
This changes extend the incremental box tree construction for inline
atomic
Testing: This should not change observable behavior and is thus covered
by existing WPT tests.
Signed-off-by: sharpshooter_pt <ibluegalaxy_taoj@163.com>
Previously, `rendered_text_collection_steps` ignores
`TextTransformCase::Capitalize` due to limitation of iterator. Now we
handle the case outside.
Testing: Added a new test as not covered by existing wpt-test, except
for the indirectly related WebDriver test.
`./mach test-wpt -r
tests\wpt\tests\webdriver\tests\classic\get_element_text\get.py
--product servodriver`
Fixes: #37469
---------
Signed-off-by: Euclid Ye <yezhizhenjiakang@gmail.com>
Text decorations have a special kind of propagation. Instead of
propating these during box tree construction, move propagation to
stacking context tree construction. This will allow for using a very
easy type of incremental layout when text decorations change. For
instance, when a link changes color during hovering over it, we can skip
all of box and fragment tree construction.
In addition, propagation works a bit better now and color and style
properly move down from their originating `Fragment`s.
This introduces three new failures, because now we are drawing the
text-decoration with the correct color in more places, which exposes an
issue we have with text-decorations not being drawn in relation to the
baseline (taking into account `vertical-align`).
Testing: There are tests for these changes.
Fixes#31736.
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Oriol Brufau <obrufau@igalia.com>
When making last-minute changes to the repaint-only layout pass, damage
propagation was broken, meaning that full layout was always done. This
change fixes that, meaning that times in the `blaster.html` test case
now reflect those described in the original commit message from #36978.
In addition, some style repair is now fixed:
- `InlineFormattingContext`s now keep a `SharedInlineStyles` for the
root of the IFC
which is updated during style repair.
- `BlockFormattingContext`s now properly update their style.
These changes are verified by turning on repaint only layout for more
properties
in Stylo via servo/stylo#183.
Testing: Manual performance testing via `blaster.html`.
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This change adds the simplest kind of incremental layout. When Servo
detects that all style changes only require a repaint, only run stacking
context tree and WebRender display list generation. This means that
these kind of restyles do not need a re-layout. Instead, the existing
box and fragment trees will be used and the styles of damaged nodes will
be updated in their box and fragment tree nodes.
This requires a new style repair DOM traversal for nodes that have had
their style damaged. In addition, careful accounting of all the places
where we store style must happen in order ot update those styles.
Testing: This is covered by existing WPT tests as it should not change
observable behavior.
We have created a test case which shows a 50% speedup when run
in Servo, even though there still a long way to go to match the speed
of other browsers:
https://gist.github.com/mrobinson/44ec87d028c0198917a7715a06dd98a0
Co-authored-by: Oriol Brufau <obrufau@igalia.com>
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Oriol Brufau <obrufau@igalia.com>
`TextRun`s use their parent style to render. Previously, these styles
were cloned and stored directly in the box tree `TextRun` and resulting
`TextFragment`s. This presents a problem for incremental layout.
Wrapping the style in another layer of shared ownership and mutability
will allow updating all `TextFragment`s during repaint-only incremental
layout by simply updating the box tree styles of the original text
parents.
This adds a new set of borrows when accessing text styles, but also
makes it so that during box tree block construction
`InlineFormattingContext`s are created lazily and now
`InlineFormattingContextBuilder::finish` consumes the builder, making
the API make a bit more sense. This should also improve performance of
box tree block construction slightly.
Testing: This should not change observable behavior and thus is covered
by existing WPT tests.
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Oriol Brufau <obrufau@igalia.com>
This makes it so that layout is no longer generic on the node type,
depending directly on `script`'s `ServoLayoutNode`. In addition to
greatly simplifying layout, this is necessary because incremental layout
needs to be able to create pseudo-element styles without having a handle
on the original `impl LayoutNode`. We feel this is a reasonable
tradeoff.
Testing: No functional changes, so covered by existing WPT tests.
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Oriol Brufau <obrufau@igalia.com>
This is a followup to #36629, continuing to implement script-based
layout queries using the `Fragment`s attached to the `BoxTree`. In this
change, geometry queris (apart from parent offset) are calculated using
`Fragment`s hanging of the `BoxTree`.
In order to make this work, all `Fragment`s for inlines split by blocks,
need to be accessible in the `BoxTree`. This required some changes to
the way that box tree items were stored in DOM `BoxSlot`s. Now every
inline level item can have more than a single `BoxTree` item. These are
carefully collected by the `InlineFormattingContextBuilder` -- currently
a bit fragile, but with more documentation.
Testing: There are tests for these changes.
Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Oriol Brufau <obrufau@igalia.com>
Now that legacy layout has been removed, the name `layout_2020` doesn't
make much sense any longer, also it's 2025 now for better or worse. The
split between the "layout thread" and "layout" also doesn't make as much
sense since layout doesn't run on it's own thread. There's a possibility
that it will in the future, but that should be something that the user
of the crate controls rather than layout iself.
This is part of the larger layout interface cleanup and optimization
that
@Looriool and I are doing.
Testing: Covered by existing tests as this is just code movement.
Signed-off-by: Martin Robinson <mrobinson@igalia.com>