Implement AIA fetching, for sites with broken cert chains#6
Merged
Conversation
Create initial Qt tabs while constructing a window, then navigate them after the completed window is shown. Move loading state ownership into ViewImplementation so both frontends synchronize with loads requested before their callbacks are attached. Carry navigation IDs with loading notifications. Only accept terminal notifications for the active navigation. This prevents a fresh process's initial about:blank load from racing with a command-line URL.
layout_absolutely_positioned_element() used to reach into the used values of the box's containing block and force its definite-size flags on, so that sizes of the abspos box depending on the containing block would resolve as definite. Since the ContainingBlockConstraints refactor, every sizing path of an absolutely positioned box receives its percentage bases explicitly, and its available space is built as definite sizes from the AbsposContainingBlockInfo rect, so nothing consults these flags on the containing block anymore. This mutation was the last direct dependency of layout_absolutely_positioned_element() on containing block used values, which is a problem for running partial relayout of absolutely positioned subtrees, where the containing block sits outside the laid out subtree and has no used values at all.
The throwaway layout states used for intrinsic size measurement pre-populated the used values of the measured box's containing block. Nothing reads that entry anymore: intrinsic sizing runs receive their percentage bases through ContainingBlockConstraints, abspos layout and inline containing block resolution are skipped in intrinsic sizing mode, and the remaining ancestor lookups go through try_get(), which treats missing ancestors as indefinite. Any overlooked reader would trip the VERIFY in LayoutState::get() rather than misbehave silently. Dropping the copies removes the last containing block read that happened under layout_absolutely_positioned_element(), which matters for partial relayout of absolutely positioned subtrees: there the containing block is outside the laid out subtree and has no used values to copy from.
measure_automatic_content_height() pre-populated its throwaway layout state with every ancestor that had used values, because flex and grid static-position handling used to create used values for absolutely positioned descendants through the lazy used-value creation mechanism, which tripped the throwaway state's subtree VERIFY when a descendant's containing block was an ancestor above the measured box. Since used value creation became explicit, registering an abspos child only records it on its containing formatting context root without touching any used values, and a registration target above the measured subtree root is never processed inside the measuring run, so nothing reads those ancestor entries anymore. Any overlooked reader would trip the VERIFY in LayoutState::get() rather than misbehave silently.
Its last callers were the throwaway layout states that copied containing block or ancestor used values into intrinsic sizing and content measurement runs, and those copies are gone now.
compute_inline_containing_block_rect() positioned atomic inline fragments by subtracting two LayoutState::cumulative_offset() calls, each of which recursively reads used values up the containing block chain to the root. Since measurement layout states stopped copying ancestor state, that up-walk reads past the measurement root when a box establishing an independent formatting context with auto height and a min-height contains an absolutely positioned element whose containing block is an inline: the measuring BFC lays out the abspos child inside the throwaway state, and resolving the inline containing block rect crashed on the "no used values" VERIFY in LayoutState. Only the difference between the two cumulative offsets ever mattered, and it equals the running offset the fragment walk already threads down: an atomic inline's fragment lives in the line boxes of its containing block, which is exactly the node being walked when the fragment is encountered. Handle atomic inline fragments directly in the line box fragment loop with that offset, instead of reaching them through the child walk and re-deriving their coordinate space from the containing_line_box_fragment back-pointer. This removes the last up-the-tree state reads from this function and makes it purely top-down. Adds a crash test that lays out an abspos element against an inline containing block inside an automatic content height measurement.
Measurement layout states (measure_automatic_content_height and the intrinsic sizing helpers) are throwaway: nothing in them is committed, and out-of-flow boxes cannot affect the measured metrics. Yet the measuring formatting contexts still laid out abspos children, and resolving their insets against an inline containing block or an anchor() function walks the containing block chain past the measurement root, into ancestor state that throwaway states no longer contain. This crashed on the "no used values" VERIFY; Give LayoutState an explicit Purpose declared by its creator (Commit for full layout and SVG partial relayout, Measurement for the seven throwaway sites) and skip layout_absolutely_positioned_children in measurement states. cumulative_offset() keeps its strict VERIFY. Adds a crash test for anchor() inset resolution inside an automatic content height measurement.
Table cells with percentage heights are laid out twice: once to measure the content height that row sizing needs (the percentage resolves against the table height, which isn't known yet), and once for real after it is. Both layouts ran in the committing LayoutState, with discard_used_values_for_descendants() erasing the first pass's descendant state in between so the second pass could recreate it. The first pass is a measurement in disguise: only the automatic content height and the first baseline survive it. Run it in a throwaway Measurement state instead, seeded with a full copy of the cell's used values so collapsed-border overrides, definite-size flags, and size constraints match the committing state exactly. The second pass becomes the one and only inside layout of these cells in the committing state, and discard_used_values_for_descendants() loses its only caller and is removed. With this, every box is created and placed at most once per committing LayoutState, enforced by the existing VERIFYs in create() and set_content_offset(). Absolutely positioned children of percentage-height cells are also no longer laid out twice.
register_contained_abspos_child() tolerated re-registration of the same box by replacing the stored static position instead of duplicating the entry. That tolerance existed for the second inside layout of percentage-height table cells; now that every box is laid out at most once per layout state, no code path can encounter the same abspos box twice: block flow, inline static position markers, and the flex/grid/table notification walks each see a box exactly once per state, and the table walk prunes at cells, whose contents register during the cells' own inside layout.
When the navigate event canceled a navigation or intercepted it into a same-document navigation, navigate() returned with the navigation ID still stamped as the navigable ongoing navigation, and nothing ever cleared it. The spec clears it in the queued same-document finalize, but our synchronous same-document commit path deliberately preserves navigation IDs it does not own, so the dead ID lingered forever. Later same-document traversals then hit the stale-traversal guard in apply the history step and treated themselves as superseded: on reddit, going back from an SPA-navigated page moved the session history step without committing anything, leaving the page, URL, and Navigation API state stuck, and WebDriver waited forever for the dead navigation when clicking links. Firing another traverse navigate event from that state aborted the never-finished intercepted event from a traversal task with no JavaScript on the stack and crashed WebContent, so give abort_a_navigate_event an execution context as well. The three new tests cover the intercepted push, canceled push, and pending-handler abort cases; the first two used to time out and the third crashed WebContent. Chromium and Firefox agree on the fixed behavior end to end.
Inline list item boxes were treated as atomic inline boxes even though their built-in marker children were skipped by the inline iterator. The list item therefore got a singular paintable and its marker occupied no space, so content started where the marker should have been. Treat inline-flow ListItemBox nodes as fragmented inline containers, emit their built-in marker as an atomic inline item, and share marker sizing with block layout. Generalize per-line paintables to box-model nodes so inline list items participate in fragment painting, containing block selection, stacking, relative positioning, and vertical alignment like other inline-flow containers.
Previously, a position:fixed element inside <foreignObject> used the viewport as its containing block. During partial SVG relayout, the viewport is outside the relayed-out subtree, so the fixed element was registered as an abspos child of the viewport but never laid out. LayoutState::commit() then cleared its paintable without rebuilding it, making getBoundingClientRect() return an empty rect until the next full layout. With foreignObject establishing the fixed positioning containing block, such elements are laid out by the foreignObject's formatting context, which runs inside the partial SVG relayout pass. This also makes the viewport pre-population in relayout_svg_root() (added in 9231b54 to fix a crash in this same scenario) unnecessary, so remove it.
Retain the navigation fetch controller on its resulting document so an abort can stop the main resource after response headers have committed. Also stop every active subresource fetch associated with the document and mark the document unsalvageable when cancellation was necessary.
Handle Escape in both frontends and ask WebContent to abort the active top-level navigation. Update loading state immediately so the spinner stops even if no terminal notification follows. Remember the URL of a stopped load until its document commits. Reload can then retry a load stopped while about:blank was still active.
Advance frame scheduling from the previous ideal deadline instead of the actual timer dispatch time. This carries fractional milliseconds between frames, allowing a 60 Hz target to alternate integer timer delays rather than rounding every interval up to 17 ms.
Move the final async scroll race check into the compositor when it receives a frame. WebContent no longer needs two synchronous compositor round trips around every display list recording operation.
Replace the fixed store pair with a stateful collection. Publish stores to the UI as vectors while retaining the existing two-buffer depth. Reserve the initial front store until the UI presents another buffer. Release older stores after the frontend finishes with them. This removes front/back acknowledgement assumptions from the compositor protocol.
Compute conservative display-list and visual-context damage. Compare commands by value and realign inserted or removed sequences. Compare context ancestry instead of indices to avoid full-page damage. Track damage per queued store so reused buffers repaint missed changes. Clip Skia replay to the damage and add focused tests.
Replay partial display lists into a GPU surface matching the damage rectangle, then copy that result into the shared backing store. This keeps Skia layers and intermediate effects bounded by the damaged area instead of the full high-DPI viewport.
Preserve the scheduled state when a pending frame is blocked by GPU work or backing-store ownership. Rearm the display scheduler so the frame can use the next vsync instead of restarting the loop after it becomes unblocked and losing a presentation interval.
Carry each presented frame damage rectangle through compositor IPC and LibWebView. Expose a frontend hook after accepting a backing store so native presentation paths can update only the affected content.
The acceptable anchor element rules in css-anchor-position-1 require an anchor's containing block chain to pass through the positioned element's containing block: "laid out strictly before" is satisfied either by sharing the containing block or, recursively, by the element generating the anchor's containing block being an acceptable anchor itself. We currently resolve anchor() against any registered element with a matching name, so anchors outside the containing block resolve instead of being rejected, the last registrant of a duplicate name shadows an earlier acceptable one, and specified fallback values are ignored. When the offset chain between such an anchor and the containing block crosses a formatting context root that has not been placed yet (a float, in cases 1 and 2), the resolved insets are derived from unplaced offsets and are not even self-consistent. The expectations capture this incorrect behavior; the following commit implements the containing block scoping and rebaselines them to match the spec and other engines.
resolve_anchor_insets() resolved anchor() against whatever element the registry returned last for a name, ignoring the acceptable anchor element rules entirely. Anchors outside the positioned element's containing block resolved instead of being rejected, an unacceptable last registrant shadowed earlier acceptable ones with the same name, and specified fallback values were ignored. Computing such an anchor's rect also subtracted two ICB-relative cumulative offsets whose chains can cross formatting context roots (floats, atomic inlines) that are not placed yet in the middle of layout, so the resolved insets were derived from unplaced offsets read as (0, 0). Implement the containing block scoping part of the rules: "laid out strictly before" holds either between boxes sharing a containing block or recursively through the element generating the anchor's containing block, so the anchor's containing block chain must pass through the positioned element's containing block. The registry lookup now walks the candidates in reverse tree order with an acceptability predicate, making the target anchor element the last acceptable element instead of the last registered one. With scoping in place the anchor rect only depends on boxes inside the containing block's subtree, which are all placed by the time its absolutely positioned children lay out. The rect is now accumulated from the anchor up to the containing block, so boxes above it, which may legitimately be unplaced, are never read.
Problem: The htmlcollection-cache-prune-after-gc.html test was timing out relatively frequently on the Linux Sanitizer CI runners Cause: The test forced a full GC after each of 800 child removals. Each is slow enough on a Sanitizer job that 800 surpassed the 120s timeout. Fix: Drop the count from 800 to just 100 — which actually reliably still reproduces the freed-cell-prune crash the test exists to catch. Verified locally on a Sanitizer build with the guard removed: 100/100 crashes at 100 and 0/50 at 25. So 100 sits well above the reliability floor of ~40, while cutting the GC work by 8X — and finishing without timing out.
Problem: In CI, already-loaded-image-invalidates-style-and-layout.html and other reftests that assign an img element’s src to an already-loaded image intermittently captured the previous image — with the whole image square painted the old color. Cause: When an img adopts an image already in available-images list, the cached-image path in “update the image data” sets the current request’s image data synchronously — but defers the style and layout invalidation into a queued element task. A rendering update that runs before that task — such as the one the src attribute change itself schedules — then paints the stale layout, because needs-layout hasn’t been set. Fix: Do sync style and layout invalidation, alongside the sync image- data update. Only the observably-async steps (restart the animation, set the current URL, fire the load event) remain in the queued task.
Lexical-based editors such as Reddit's comment composer handle Backspace on keydown by deleting a character themselves and cancelling the event, and additionally handle beforeinput deletions the same way if they occur. The expectations added here capture our current buggy behavior: we dispatch beforeinput and run the delete editing action even though the keydown was cancelled, so the editor deletes twice and a single Backspace removes two characters.
Cancelling a Backspace or Delete keydown must suppress the entire editing default action, including the beforeinput event. We instead kept going: beforeinput was dispatched and, unless the page cancelled that too, the deletion was performed with the input event suppressed. Editors that handle deletion on keydown and also handle beforeinput deletions, such as the Lexical editor used by Reddit's comment composer, then deleted twice: once from their keydown handler and once from their beforeinput handler, so a single Backspace press removed two characters. Chrome, Firefox and Safari dispatch no beforeinput and perform no editing action when the keydown is cancelled. This effectively reverts the EventHandler part of commit 8f012f4 along with the now-unused DispatchInputEvent plumbing it introduced, and rebaselines the tests: a cancelled Backspace keydown now leaves the editor contents alone and dispatches no input events.
Focusing a contenteditable element used to move the cursor to the end of the last text node in the editing host, or to the host itself when it had no text descendants. Chrome and Firefox instead place the cursor at the first valid caret position: the start of the first text node, or before the first <br> when the host has no text. The old behavior broke editor frameworks that only reconcile DOM mutations occurring inside their rendered leaf elements. X's Draft.js composer renders an empty block as <span data-offset-key><br></span>; with the cursor anchored at the editing host itself, typed text was inserted as a new text node directly under the host, where the editor could not map it back to its model, leaving the model empty and Backspace inoperative. The test expectations match Chrome's behavior for text, nested inline, empty <br> leaf, and multi-block editing hosts.
The expectations capture our current behavior: keyCode and which hold the virtual key code during keypress events. Other browsers set both to the character code (e.g. 104 for a keypress of "h"), and frameworks depend on it: React synthesizes its onBeforeInput text from String.fromCharCode(event.which) on keypress, so a wrong value corrupts controlled editors such as X's Draft.js composer.
For keypress events that produce a character, all major browsers set keyCode (and therefore which, which mirrors it) to the character code rather than the virtual key code, so a keypress of "h" reports 104 instead of 72. React synthesizes onBeforeInput text on keypress from String.fromCharCode(event.which), so reporting the virtual key code gives frameworks the wrong (uppercased) character. The keypress-legacy-key-attributes test expectations now match the values reported by other browsers.
PaintableBox::reset_for_relayout() clears the paintable's scroll frame references, but ViewportPaintable::assign_scroll_frames() only ran in the full layout path. After a partial relayout, paintables in the relaid subtree were left without scroll frames, so display list recording lost scroll offsets and the clip/scroll hierarchy for scroll containers inside the subtree; the find-in-page text blocks cache was similarly only invalidated by the full path and went stale. Reassign scroll frames from the partial relayout branch and invalidate the text blocks cache the same way the full path does.
LayoutState::commit() detaches the subtree root's old paintable and build_paint_tree() reattaches the rebuilt one with append_child(), so a partially relaid subtree whose root was not the last paintable child moved to the end of its parent's children. Paint and hit-test order between siblings with equal stacking follow paintable tree order, so the relaid subtree ended up covering later siblings that a full layout would paint on top of it - for example, relaying out the first of two overlapping absolutely positioned siblings put it above the second. Capture the old paintable's next sibling before detaching it and splice the rebuilt paint subtree back in at that position.
set_needs_layout_update() returns early when the node is already marked, assuming the first mark already dirtied the whole ancestor chain. That assumption breaks for outermost SVG roots registered for partial relayout: dirt inside the svg marks the root itself but stops propagating there, so a later invalidation on the root in the same update cycle, such as a changed CSS size, hits the early return and never reaches the ancestors. The update then stays confined to the svg subtree, which relays out with a stale size even though the new size must reflow the surrounding content. Marks on an already-dirty outermost SVG root now fall through and propagate to the ancestors, escalating such updates to a full layout. Marks on other already-dirty nodes still return early, and the fall-through walk stops at the first dirty ancestor, so repeated marks stay cheap.
Partial and full layout each carried their own list of post-commit refresh steps, and the lists had already drifted: only the full path re-derived selection state, so a partial relayout inside a subtree containing selected text dropped the selection highlight until the next selection change or full layout, because the commit rebuilds the paintable fragments that carry it. Refresh every structure derived from committed layout results - scroll frames, accumulated visual contexts, selection state, the text blocks cache, the stacking context tree, the display list, and scroll state - in a single Document::after_layout_commit() called by both paths, so a layout path cannot forget one. Viewport-rect broadcast and collection of content-visibility:auto boxes are gated on the layout tree having changed, which the partial path today never does; upcoming partial handling of confined layout tree updates will take that variant.
Layout indices key the per-pass used values store in LayoutState. They used to be assigned only by the dense renumbering at the start of a full layout pass, leaving nodes created by an incremental layout tree update stuck at index 0 until the next pass renumbered them. Partial relayout is about to run layout on freshly rebuilt subtrees without an intervening full pass, at which point the colliding indices would make those nodes share used values. Hand out an index from a document counter at construction instead. The full pass keeps renumbering the tree densely (the paged used values store is only compact for the dense range) and resets the counter past it, so between-pass indices never collide with the dense range.
create_independent_formatting_context_if_needed() and create_independent_formatting_context() were member functions that implicitly used `this` as the parent of the newly created context. Partial relayout needs to create a formatting context for a subtree root from Document::update_layout(), where there is no parent context at all. Make both functions static with an explicit parent parameter and pass `this` at the existing call sites. Intrinsic sizing helpers can now call the factory without casting away constness merely to select a factory object, while still preserving the current context as the parent.
Partial relayout is limited to SVG roots: the invalidation walk stops at the first outermost-SVG-root ancestor and records it in an SVG-typed dirty set on the document. Extending partial relayout to other kinds of boxes - the goal is using the closest absolutely positioned box as a boundary - needs the boundary check and the dirty set to work in terms of generic boxes. Introduce Box::is_partial_relayout_boundary(), which for now only returns true for outermost SVG roots, and use it as the stop condition in the ancestor walk. The SVG-typed dirty set and its registration function become Document::PartialRelayoutInvalidation, a small type that owns the attribution of pending updates to boundaries, so the bookkeeping the dispatch relies on lives in one place. The dispatch in Document::update_layout() still casts registered roots to SVGSVGBox since nothing else can qualify yet. No behavior change intended.
The partial relayout dispatch consumed the registered dirty set directly, trusting that every registered box was still a live, still qualifying boundary. That only holds because the dispatch runs exclusively when no layout tree update is pending, and it breaks the moment partial relayout starts handling structural updates: an in-place subtree rebuild can detach or replace a registered box between the invalidation and the dispatch. Make the dispatch resolve the live boundary set from the tree instead: filter out roots that did not survive, re-check the boundary predicate against the tree that is about to be laid out, and route the update to the full layout path when a root no longer qualifies. Boundaries are owned by the layout tree, not by the registration, so later structural partial relayout can re-discover them from rebuilt subtrees instead of anchoring boxes across the build.
The partial relayout pass hardcoded SVGFormattingContext and only worked for SVG roots, while the upcoming absolutely positioned boundaries are Block, Flex and Grid roots. Pick the formatting context through the static factory instead, and explicitly notify the context after the run so such roots lay out their contained absolutely positioned children the same way a parent formatting context would after dimensioning them in a full layout; that notification is a no-op for SVGFormattingContext, so SVG behavior is unchanged. The pass is also split into compute and commit halves, keeping the subtree layout computation reusable independently of the paintable tree mutation that publishes it.
layout_absolutely_positioned_element() receives everything it consumes from outside the element's subtree as two plain values: the containing block info and the fully resolved static position rect. Partial relayout will need to save those values during a full layout pass and replay them later to re-run absolutely positioned layout without re-running the ancestor formatting context, which requires them to be one persistable unit. Bundle them into an AbsposLayoutInputs struct, and move the types it is built from (Alignment, AbsposAxisMode, AbsposContainingBlockInfo, StaticPositionRect) to a standalone header so LayoutState can hold the struct without including FormattingContext.h. No functional change.
Replaying an absolutely positioned box's layout without running its ancestor formatting context needs the inputs that context resolved for the box. Record the AbsposLayoutInputs into the box's used values when the absolutely positioned element algorithm runs in a Normal-mode pass, and have LayoutState::commit() transfer them onto the Layout::Box: throwaway measurement states never commit, so their recordings are discarded, and a committing pass that no longer lays a box out as absolutely positioned clears the stale copy. An in-place layout tree update that replaces a box carries the saved inputs over to the replacement: it represents the same element in the same tree position, so the inputs remain those of the last committed layout. Nothing consumes the saved inputs yet.
A box whose subtree contains an absolutely or fixed positioned descendant with a containing block outside that subtree cannot become a partial relayout boundary: the descendant is laid out by a formatting context outside the subtree (the one rooted at the first formatting-context-creating box on the containing block chain), so the subtree cannot be laid out in isolation. The classic case is a position:fixed descendant, whose containing block is the viewport, escaping every box below it. Record that fact as a flag on every box strictly between an out-of-flow box and its containing block, derived in the same pre-order walk that recomputes containing block pointers before a full layout pass: the walk clears each box's flag on visit and out-of-flow boxes then mark their ancestors, so the flags always describe exactly the tree the pass is about to lay out. Boundary qualification consumes the flag; for the SVG roots that qualify today nothing can escape (foreignObject establishes both kinds of containing block), so this becomes observable once absolutely positioned boxes become boundaries.
The escaped-by-abspos facts feeding partial relayout boundary qualification are derived from containing block pointers, and those are only recomputed by a full layout pass. A style change that makes an element gain or lose a containing block for absolutely/fixed positioned descendants (transform and friends, perspective, transform-style, filter, backdrop-filter, contain, container-type, will-change) moves where those descendants' layout happens without updating either the pointers or the derived facts - most of these properties do not even dirty layout by themselves. Classify such value flips during style invalidation, mirroring how the stacking-context bit only reacts to establishment-relevant changes so that e.g. transform animations between two non-none values stay unaffected, and record them in a sticky escape bit on the document's partial relayout invalidation, routing the next layout to the full path, which re-derives every fact and clears the bit. Both appliers of computed invalidations honor the new bit: the style recalculation path and the animated-style update path, which applies invalidations without a full style recalculation. Display, position and content-visibility changes already force an in-place subtree rebuild, whose handling re-derives the facts for the rebuilt subtree, so they need no classification here.
In-place layout tree updates can replace a node while its containing block's committed inline paint data remains in use. The flat fragment and inline-box-piece lists then retain weak references to the old node, leaving dead entries whenever a subtree relayout skips the containing block - which is exactly what a partial relayout of a replaced inline-level boundary is going to do. Repoint both kinds of inline paint data at the replacement during the swap. The new node represents the same element at the same tree position, so the committed geometry remains valid until the containing block is rebuilt.
An incremental layout tree build replaces dirty subtrees in place, but some of its work can mutate the tree outside the subtree being replaced. Partial relayout of structural updates is only sound when every mutation stayed inside a relayout boundary, so it needs to know which subtrees were rebuilt and whether anything landed outside them. Track the root of the in-place subtree replacement currently being built, record every such root, and flag mutations outside the current rebuild root: an inline/block mix wrapping an ancestor's inline children into a fresh anonymous block, insertion parents above the rebuild root (inline ancestors are skipped, and out-of-flow boxes can join a trailing anonymous sibling), fresh subtrees that replace no box in place and so belong to no rebuild root (a ShadowRoot direct child or a top-layer element revealed from display:none marks itself), and the two ::backdrop maintenance paths (the ::backdrop box is inserted as a sibling of a replaced box, and its removal runs before the element's own rebuild root is even established). The report is exposed on TreeBuilder and not consumed by anything yet.
Geometry alone cannot tell which path handled an update: a partial relayout is only correct if it reproduces exactly what a full layout would have computed, so a silent fall-through to the full path is invisible in layout results. Count partial and full layout passes on the document and expose them as internals.partialLayoutCount() and internals.fullLayoutCount(), so the path that handled a given update becomes directly observable.
Each test mutates something in or around an absolutely positioned box and records the resulting geometry together with the partial/full layout pass counters. The expectations capture today's behavior: the geometry is already correct, but nearly every update takes a full layout pass, so tests asserting a partial pass currently end in FAIL. The following commits extend partial relayout to absolutely positioned boundaries case by case and rebaseline the tests they affect, so each expectation diff shows exactly which updates that commit moves off the full layout path. Tests asserting that an update must stay on the full path (an escaping fixed-position descendant, top-layer changes, anchor positioning in use anywhere in the document) pass as-is and stay untouched throughout.
Let the invalidation walk stop at qualifying absolutely positioned boxes, so a layout invalidation inside such a box re-lays out only its subtree instead of the whole document. A box qualifies when it creates a Block, Flex or Grid formatting context, no absolutely or fixed positioned descendant escapes it, and the inputs of its own layout were saved by the last committing pass. Unlike in-flow SVG roots, whose geometry is frozen, the boundary re-resolves its own size and position on every partial relayout by replaying the absolutely positioned element algorithm from the saved inputs, hosted by a context type that exists for exactly that (the algorithm is defined on the FormattingContext base and consumes nothing from the concrete context running it): the inputs pin everything the algorithm reads from outside the subtree, so the replay reproduces exactly what the ancestor formatting context computed, without laying out anything outside. An absolutely positioned outermost SVG root qualifies through this replay path like any other absolutely positioned box. Because replay re-solves the boundary's own size, content-dependent sizing (shrink-to-fit, intrinsic min/max constraints, aspect-ratio) and visible overflow do not disqualify a boundary - leaked content reaches ancestors only through their scrollable overflow, which is recomputed after the subtree commit. This keeps the common overlay patterns - dropdowns, tooltips, dialogs, positioned panels - on the partial path when their contents change. CSS anchor positioning is left out of scope: anchor names publish geometry that anchor() functions on positioned boxes anywhere in the document consume, and only a full layout pass re-resolves all of them. The dispatch refuses partial relayout while any anchor name is registered, an element whose registered names all vanish between layouts sets the escape bit (the dispatch-time check reads the live name maps, which no longer know them), and a box whose own inset properties carry anchor() functions never qualifies as a boundary, since only a full layout pass resolves those insets to plain values.
Structural DOM changes always forced a full layout because a layout tree rebuild could restructure anything. In-place subtree rebuilds now report their confinement, so insertions, removals and moves whose effects stay inside a partial relayout boundary re-lay out only that boundary's subtree. The partial path runs the incremental tree build itself, walks each rebuilt subtree to assign containing blocks to the new nodes and re-derive the escaped-by-abspos facts (catching e.g. a freshly inserted position:fixed box whose containing block is outside the boundary), and collects the boundary set from the post-build tree: the registered boundaries that survived the build plus the nearest boundary containing each rebuilt subtree. Updates that cannot be attributed to a boundary - a rebuilt subtree with no containing boundary, a dirty DOM node whose layout node is detached, or anything TreeBuilder reports as escaping its rebuild roots, which covers top-layer relocation and ::backdrop maintenance - take the full layout path. A container-relative length resolved against a container whose box has no paintable yet registers it for evaluation even while a layout pass is running, since a partial pass reports layout as up to date; the follow-up pass then takes the full path and resolves the container's size. Structural commits also refresh the cached containing-block membership used by scrollable-overflow measurement, since the rebuild can replace boxes it references. And because every rebuilt subtree allocates fresh layout node indices while only a full pass renumbers densely, repeated structural partial relayouts would grow the per-pass used values store without bound; once between-pass allocation outgrows the dense range, the next layout takes the full path, which compacts the indices again.
A style change that needs relayout but no tree rebuild on an absolutely positioned boundary used to dirty the root and force a full layout, even though replay re-resolves the boundary's own size and position from the saved layout inputs on every partial relayout anyway. Register the boundary itself instead of walking its ancestors and re-lay out only its subtree: this turns inset, size, margin and padding mutations on boundary-eligible boxes - the classic left/top animation pattern - into partial relayouts, with ancestor scrollable overflow recomputed along the containing block chain since a moved or resized boundary changes it. Most saved inputs survive a boundary-self style change untouched: the containing block rect and the static position rect derive from layout outside the subtree, and the axis modes are recomputed from the live insets at replay. Inputs whose parts were derived from the box's own computed values record that fact where they are derived - grid placement produces the containing block rect and alignments from grid-row/column and self-alignment, and a flex container derives the static position rect's alignment from the box's own align-self - and such saved inputs keep the full layout path. A style change whose relayout effect reaches the element's ::backdrop also keeps the full path: the backdrop's box is a sibling of the element's box, outside the subtree a boundary-self relayout covers.
Structural changes directly on a boundary element - appendChild or removeChild on the boundary, or text insertion that churns its anonymous wrappers - still took the full layout path, because TreeBuilder replaces the dirty element's layout box and partial relayout could not survive losing the box whose saved layout inputs and paintable it works from. Register the boundary itself for such whitelisted structural reasons without walking its ancestors: child list mutations cannot change the boundary's computed values, so its qualification and box kind are unaffected, and the in-place replacement cannot require restructuring surrounding anonymous siblings (which is also why the anonymous-parent escalation is skipped for this case). After the incremental build, the replaced boundary box is re-discovered through the rebuilt subtree roots and accepted: the saved layout inputs carried over to the replacement drive the replay, an SVG root's geometry is materialized from the previous paintable (still referenced by the DOM node until the next commit), and LayoutState::commit() gains an overload taking the paintable to replace, so the fresh paint subtree is spliced in at exactly its position - the box no longer owns that paintable, which the plain overload relies on. A replaced boundary applies the saved-input validity check unconditionally, since the change that drove the replacement can no longer be classified.
Accumulate only visual offsets from sticky ancestors when positioning a nested sticky box. Do not include the nearest scrolling ancestor because its offset is already represented by the scrollport rectangle; including it applies the prior scroll position twice on subsequent updates. Mirror the calculation in the compositor and cover consecutive viewport scroll updates with a reference test.
The old behavior was to fail `wpt` by providing it with the name of a test that does not exist, which necessitated `|| true`. This hid venv setup failures though, so that was less than ideal. This new approach uses `--list-tests` which does not cause `wpt` to exit with a non-zero status code, but still sets up the venv and allows setup failures to propagate. Remove `--skip-venv-setup` from the individual chunk runners since that is still required to _activate_ the venv.
A browser launched by WebDriver must always run as its own process, even if another instance of the browser owns the same profile.
Previously, each browser instance launched by WebDriver used its own temporary profile. These profiles were leaked whenever a browser did not exit cleanly. Browsers launched by the same WebDriver process now share a single profile, which is removed when the WebDriver process exits.
Profiles created during a WPT run are now grouped under a single directory, which is removed when the run ends. Profile roots leaked by unclean exits are removed at the start of the next run.
This implements TLS AIA-fetching support — by doing the following: 1. Install a cert-verification callback that, on a failure, records URLs from the AIA extension of any cert whose issuer is missing. 2. Make RequestServer fetch each missing intermediate on a fresh handle. 3. Parse each such intermediate, cache it, and then retry the request. Otherwise, without this change, some sites reachable in other browsers (which already do this) are unreachable in Ladybird — because we fail to load those whose responses have missing intermediate CA certs, even when a missing intermediate is pointed at by an AIA extension. Note: On retry the fetched intermediates are only ever offered to chain- building as untrusted certs. The completed chain must still validate to a locally-trusted root; thus, this doesn’t introduce new/specious trust. Fixes LadybirdBrowser#10520
Cover the two AIA-parsing primitives directly: extracting http caIssuers URLs from a cert’s AIA extension (https URLs skipped, capped per-cert), and parsing a fetched response body as a DER cert, a PKCS#7 certs-only bundle, or PEM. The test compiles AIA.cpp into its own binary, and links OpenSSL directly — rather than the whole RequestServer service.
Drive the full AIA-fetch pipeline against real TLS. The http-test-server fixture now generates a root/intermediate/leaf certificate chain, and serves HTTPS hosts that present a broken chain (leaf only); test-web trusts the generated root via its request-server certificate option.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Cherry-pick from upstream PR LadybirdBrowser#10661 by sideshowbarker