On editor.construct.net, calling dialog.showModal() produces a
~22 ms layout on a high-end desktop. The reporter argues that a modal floating on top of
the document should not require relaying out the whole document.
2020 follow-up by ikilpatrick@chromium.org: the original 22 ms appears to be partly explained by a synchronous font-fallback IPC at first dialog display, not pure layout cost.
Builds a 40×40 CSS-grid (1600 cells) and times showModal() +
close() across N iterations. Each iteration also reads a layout property on the
body (offsetHeight) to force a synchronous layout flush, so we can attribute any
cost back to showModal/close.
Open DevTools → Performance, click Run 10x, and look for layout work synchronously parented under the showModal call. If full-document layout is still being triggered, "Layout" will appear with the whole document scope on each iteration.
Steady state (after iteration 0):
showModal(): ~6–7 ms synchronousclose(): ~5–6 ms synchronousclose(): 0.00 ms on every iterationThe 0.00 ms forced reflow is the key signal: nothing is being deferred. The whole cost is paid synchronously inside the API calls. Iteration 0 is ~13 ms (extra ~7 ms), consistent with a one-time sync font-fallback IPC on first dialog display (separate bug 1104538).
HTMLDialogElement::showModal in
third_party/blink/renderer/core/html/html_dialog_element.cc does:
InertSubtreesChanged(document, old_modal_dialog);
document.UpdateStyleAndLayout(DocumentUpdateReason::kJavaScript);
That explicit UpdateStyleAndLayout was added in
ab14f17b660d0
(2021-11, Brufau): “[inert] Track inertness in ComputedStyle”. Since that
change, inert is an inherited ComputedStyle property, so when
InertSubtreesChanged marks documentElement dirty, every element in
the tree must have its ComputedStyle recomputed to flip its inert bit. The follow-on layout
pass is then also document-wide.
close() doesn't call UpdateStyleAndLayout explicitly, but it
calls InertSubtreesChanged and then previously_focused_element->Focus(),
which itself flushes style+layout. Same shape, same cost.
UA stylesheet rules on :modal and
:-internal-dialog-in-top-layer only target the dialog element, so they are not
the issue. Author-side :has(dialog:modal) rules would compound the cost but
aren't required to reproduce it.
Removing the explicit UpdateStyleAndLayout in showModal would
break correctness: SetFocusForDialog -> GetFocusDelegate walks the dialog
subtree and reads IsInert() / IsFocusable(), both of which depend
on the updated ComputedStyle. Without the flush the wrong element could be focused.
The real fix has to attack the cost itself:
IsInert() actually changes value when one modal replaces
another (or when the modal set goes from empty to one element).Either approach partially refines the 2021 inert-in-ComputedStyle architecture and would need sign-off from futhark@ / obrufau@.
Issue: crbug 41312488 (migrated from 710523)
Font-fallback follow-up: crbug 1104538
Source: html_dialog_element.cc