Handle changes to CSS `contain` and `content-visibility` without needing to reconstruct frames
Categories
(Core :: CSS Parsing and Computation, task)
Tracking
()
Tracking | Status | |
---|---|---|
firefox123 | --- | fixed |
People
(Reporter: dholbert, Assigned: emilio)
References
(Blocks 2 open bugs)
Details
Attachments
(12 files, 5 obsolete files)
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
397 bytes,
text/html
|
Details | |
407 bytes,
text/html
|
Details | |
328 bytes,
text/html
|
Details | |
346 bytes,
text/html
|
Details | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review | |
48 bytes,
text/x-phabricator-request
|
Details | Review |
Right now, we reconstruct frames in response to a change in the CSS contain
property. Also for changes to CSS content-visibility
as of bug 1765515.
I think there are two reasons for this:
(1) these properties can cause the element to become a containing block for fixed-pos elements -- but nsChangeHint_UpdateContainingBlock
should be able to handle that without reconstruction.
(2) these properties can cause the element to "establish an independent formatting context" (via "contain:layout" or "paint", i.e. layout & paint containment) which in practice means we add NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS
. I don't think we have a way of managing these bits dynamically right now (aside from via frame-reconstruction), since we seem to typically just set them in e.g. nsBlockFrame::Init()
and other construction codepaths, and we never seem to remove them.
Ideally it would be nice to have these properties not need to reconstruct, but I think we need a solution to (2) in order to achieve that.
Tentatively setting ni=emilio to put this on his radar per https://phabricator.services.mozilla.com/D144180#4712895
Reporter | ||
Comment 1•2 years ago
|
||
A few source links, for reference.
Here's where we trigger frame-reconstruction for changes to contain
:
https://searchfox.org/mozilla-central/rev/2fd70a667e6f89a9ec6622ecb302b1ab48e4a062/layout/style/nsStyleStruct.cpp#2391,2394
Here's where we handle fixed-pos containing-block changes for filter
via nsChangeHint_UpdateContainingBlock
(as an example for what we might do here, to address thing (1)):
https://searchfox.org/mozilla-central/rev/2fd70a667e6f89a9ec6622ecb302b1ab48e4a062/layout/style/nsStyleStruct.cpp#3332-3334
Here's where we currently set NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS
for contain:layout and paint in nsBlockFrame::Init
:
https://searchfox.org/mozilla-central/rev/2fd70a667e6f89a9ec6622ecb302b1ab48e4a062/layout/generic/nsBlockFrame.cpp#7406,7408
(To handle this robustly without reconstruction, we would need to add code to set and unset those bits on an already-existing nsBlockFrame, and handle the associated fallout to e.g. floats in the subtree, maybe with something similar to nsChangeHint_UpdateContainingBlock
but for floats.)
Comment 2•2 years ago
|
||
Another complication with relation to contain
is that contain: style
can modify counters and quotes. It seems that these changes also require a full reconstruction of the frame.
Assignee | ||
Comment 3•9 months ago
|
||
Assignee | ||
Updated•9 months ago
|
Assignee | ||
Updated•9 months ago
|
Updated•9 months ago
|
Updated•9 months ago
|
Comment 4•9 months ago
|
||
Comment 5•9 months ago
|
||
Attached is a patch doing some logging. I'm not quite sure I understand Cathie's comments on https://phabricator.services.mozilla.com/D196617 (I guess she was testing with her own changes) ; FWIW, below are the events I receive with D196050. Essentially show/hide on the target and reorder on the targetParent in both cases. But I suspect the async RecreateAccessible
should be changed (see next comment), so probably we can still listen to other events.
0:20.88 INFO Setting content-visibility: auto on target
0:20.89 GECKO(74217) SubtreeVisibleForA111yChange
0:20.89 GECKO(74217) [MozDbg] [/home/fred/src-obj/mozilla-unified/accessible/base/nsEventShell.cpp:25] aEvent->GetAccessible()->GetNode() = div['target'].div['targetParent'].body['body'].html.#document @ 0x7fa31e703d80
0:20.89 GECKO(74217) [MozDbg] [/home/fred/src-obj/mozilla-unified/accessible/base/nsEventShell.cpp:26] aEvent->GetEventType() = 2
0:20.89 GECKO(74217) [MozDbg] [/home/fred/src-obj/mozilla-unified/accessible/base/nsEventShell.cpp:25] aEvent->GetAccessible()->GetNode() = div['targetParent'].body['body'].html.#document @ 0x7fa31e703c80
0:20.89 GECKO(74217) [MozDbg] [/home/fred/src-obj/mozilla-unified/accessible/base/nsEventShell.cpp:26] aEvent->GetEventType() = 27
0:20.89 GECKO(74217) [MozDbg] [/home/fred/src-obj/mozilla-unified/accessible/base/nsEventShell.cpp:25] aEvent->GetAccessible()->GetNode() = div['target'].div['targetParent'].body['body'].html.#document @ 0x7fa31e703d80
0:20.89 GECKO(74217) [MozDbg] [/home/fred/src-obj/mozilla-unified/accessible/base/nsEventShell.cpp:26] aEvent->GetEventType() = 1
0:20.89 GECKO(74217) [MozDbg] [/home/fred/src-obj/mozilla-unified/accessible/base/nsEventShell.cpp:25] aEvent->GetAccessible()->GetNode() = div['targetParent'].body['body'].html.#document @ 0x7fa31e703c80
0:20.89 GECKO(74217) [MozDbg] [/home/fred/src-obj/mozilla-unified/accessible/base/nsEventShell.cpp:26] aEvent->GetEventType() = 26
0:20.89 GECKO(74217) [MozDbg] [/home/fred/src-obj/mozilla-unified/accessible/base/nsEventShell.cpp:25] aEvent->GetAccessible()->GetNode() = div['targetParent'].body['body'].html.#document @ 0x7fa31e703c80
0:20.89 GECKO(74217) [MozDbg] [/home/fred/src-obj/mozilla-unified/accessible/base/nsEventShell.cpp:26] aEvent->GetEventType() = 3
0:20.96 GECKO(74217) [MozDbg] [/home/fred/src-obj/mozilla-unified/accessible/base/nsEventShell.cpp:25] aEvent->GetAccessible()->GetNode() = toolbarbutton['reload-button'].toolbaritem['stop-reload-button'].hbox['nav-bar-customization-target'].toolbar['nav-bar'].toolbox['navigator-toolbox'].body.html['main-window'].#document @ 0x7fafc7a6a660
0:20.96 GECKO(74217) [MozDbg] [/home/fred/src-obj/mozilla-unified/accessible/base/nsEventShell.cpp:26] aEvent->GetEventType() = 5
0:20.96 GECKO(74217) [MozDbg] [/home/fred/src-obj/mozilla-unified/accessible/base/nsEventShell.cpp:25] aEvent->GetAccessible()->GetNode() = toolbarbutton['reload-button'].toolbaritem['stop-reload-button'].hbox['nav-bar-customization-target'].toolbar['nav-bar'].toolbox['navigator-toolbox'].body.html['main-window'].#document @ 0x7fafc7a6a660
0:20.96 GECKO(74217) [MozDbg] [/home/fred/src-obj/mozilla-unified/accessible/base/nsEventShell.cpp:26] aEvent->GetEventType() = 5
0:20.96 GECKO(74217) [MozDbg] [/home/fred/src-obj/mozilla-unified/accessible/base/nsEventShell.cpp:25] aEvent->GetAccessible()->GetNode() = toolbarbutton['reload-button'].toolbaritem['stop-reload-button'].hbox['nav-bar-customization-target'].toolbar['nav-bar'].toolbox['navigator-toolbox'].body.html['main-window'].#document @ 0x7fafc7a6a660
0:20.96 GECKO(74217) [MozDbg] [/home/fred/src-obj/mozilla-unified/accessible/base/nsEventShell.cpp:26] aEvent->GetEventType() = 5
0:25.99 INFO Setting content-visibility: hidden on target
0:26.00 GECKO(74217) SubtreeVisibleForA111yChange
0:26.00 GECKO(74217) [MozDbg] [/home/fred/src-obj/mozilla-unified/accessible/base/nsEventShell.cpp:25] aEvent->GetAccessible()->GetNode() = div['target'].div['targetParent'].body['body'].html.#document @ 0x7fa31e703d80
0:26.00 GECKO(74217) [MozDbg] [/home/fred/src-obj/mozilla-unified/accessible/base/nsEventShell.cpp:26] aEvent->GetEventType() = 2
0:26.00 GECKO(74217) [MozDbg] [/home/fred/src-obj/mozilla-unified/accessible/base/nsEventShell.cpp:25] aEvent->GetAccessible()->GetNode() = div['targetParent'].body['body'].html.#document @ 0x7fa31e703c80
0:26.00 GECKO(74217) [MozDbg] [/home/fred/src-obj/mozilla-unified/accessible/base/nsEventShell.cpp:26] aEvent->GetEventType() = 27
0:26.00 GECKO(74217) [MozDbg] [/home/fred/src-obj/mozilla-unified/accessible/base/nsEventShell.cpp:25] aEvent->GetAccessible()->GetNode() = div['target'].div['targetParent'].body['body'].html.#document @ 0x7fa31e703d80
0:26.00 GECKO(74217) [MozDbg] [/home/fred/src-obj/mozilla-unified/accessible/base/nsEventShell.cpp:26] aEvent->GetEventType() = 1
0:26.00 GECKO(74217) [MozDbg] [/home/fred/src-obj/mozilla-unified/accessible/base/nsEventShell.cpp:25] aEvent->GetAccessible()->GetNode() = div['targetParent'].body['body'].html.#document @ 0x7fa31e703c80
0:26.00 GECKO(74217) [MozDbg] [/home/fred/src-obj/mozilla-unified/accessible/base/nsEventShell.cpp:26] aEvent->GetEventType() = 26
0:26.00 GECKO(74217) [MozDbg] [/home/fred/src-obj/mozilla-unified/accessible/base/nsEventShell.cpp:25] aEvent->GetAccessible()->GetNode() = div['targetParent'].body['body'].html.#document @ 0x7fa31e703c80
0:26.00 GECKO(74217) [MozDbg] [/home/fred/src-obj/mozilla-unified/accessible/base/nsEventShell.cpp:26] aEvent->GetEventType() = 3
0:31.44 GECKO(74217) [MozDbg] [/home/fred/src-obj/mozilla-unified/accessible/base/nsEventShell.cpp:25] aEvent->GetAccessible()->GetNode() = #document @ 0x7ff402982c00
0:31.44 GECKO(74217) [MozDbg] [/home/fred/src-obj/mozilla-unified/accessible/base/nsEventShell.cpp:26] aEvent->GetEventType() = 4
Comment 6•9 months ago
|
||
@emilio: I'm not quite sure what you are trying to achieve with the async part of RecreateAccessible
. As I read it, the async mode is essentially doing the same as the DocAccessible::ContentInserted
call of the sync mode. But the two of them seem to be async for me since they call "mNotificationController->ScheduleContentInsertion". In any case, the only difference seems to be the use of DocAccessible::PruneOrInsertSubtree
before deciding to insert a node for insertion. I guess the parts of this function that remove content is not important here, since RecreateAccessible
always does that recursively before insertion. But the parts that prune some nodes (i.e. return insert = false to prevent insertion) and those that call PruneOrInsertSubtree
recursively on the children seems still relevant here. Otherwise, I don't know how we can properly rebuild the subtree when switching from hidden to non-hidden or when we have nodes to prune in the subtree?
Comment 7•9 months ago
|
||
Comment 8•9 months ago
|
||
Extend a bit the test for dynamic changes to content-visibility, adding
the following checks:
- If there are text descendants in the content-visibility subtree,
make sure they are created when switching to auto ; and removed
when switching back to hidden. - If there is a content-visibility: hidden or a visibility: hidden
descendant, make sure pruning still applies when switching to
auto. - If there is a shadow host in the content-visibility subtree,
make sure that shadow subtree is properly updated when
content-visibility changes.
Comment 9•9 months ago
|
||
@cathie: @emilio: I've written more a11y tests in order to catch any possible issue discussed in the currently proposed patches.
Comment 10•9 months ago
|
||
Folks are probably already aware of this, but just to be clear, we generally want to avoid recreating an accessible if it didn't change semantically. If a style changes but the accessible is still in the tree, we don't want to recreate it if we can avoid it. Obviously, if it gets hidden, we should prune it.
Note that we have cases where we recreate unnecessarily; e.g. CSS visibility in bug 1638713. We want to try to avoid introducing new bugs like that, though.
Comment 11•9 months ago
|
||
Updated•9 months ago
|
Comment 12•9 months ago
|
||
Updated•9 months ago
|
Comment 13•9 months ago
|
||
@emilio: OK, I've attached a new patch that makes the a11y test pass (including the cases I added in D196964) and is closer to existing code.
(In reply to James Teh [:Jamie] from comment #10)
Folks are probably already aware of this, but just to be clear, we generally want to avoid recreating an accessible if it didn't change semantically. If a style changes but the accessible is still in the tree, we don't want to recreate it if we can avoid it. Obviously, if it gets hidden, we should prune it.
Note that we have cases where we recreate unnecessarily; e.g. CSS visibility in bug 1638713. We want to try to avoid introducing new bugs like that, though.
Thanks you Jamie for the extra information. Unfortunately, my new patch still deletes & recreates accessible for content-visibility changes. It looks like changing that would be the same effort as fixing the existing CSS visibility bug...
Updated•9 months ago
|
Updated•9 months ago
|
Comment 14•9 months ago
|
||
I extracted the typo fixes into bug 1871361
Updated•9 months ago
|
Comment 15•9 months ago
|
||
(In reply to Daniel Holbert [:dholbert] from comment #0)
Right now, we reconstruct frames in response to a change in the CSS
contain
property. Also for changes to CSScontent-visibility
as of bug 1765515.I think there are two reasons for this:
(1) these properties can cause the element to become a containing block for fixed-pos elements -- butnsChangeHint_UpdateContainingBlock
should be able to handle that without reconstruction.
This is covered by several tests from https://phabricator.services.mozilla.com/D196957 and indeed earlier versions of the patcheset were missing the nsChangeHint_UpdateContainingBlock
bit making the tests fail.
(2) these properties can cause the element to "establish an independent formatting context" (via "contain:layout" or "paint", i.e. layout & paint containment) which in practice means we add
NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS
. I don't think we have a way of managing these bits dynamically right now (aside from via frame-reconstruction), since we seem to typically just set them in e.g.nsBlockFrame::Init()
and other construction codepaths, and we never seem to remove them.
I will need to check that, I haven't written any new tests for that and the ones I found were not performing dynamic changes. For the record, I pasting comment from private matrix chat with emilio on Dec 19:
I think the other bit that might need fixing is floats / margin collapsing. Making a block a BFC should start containing floats and not-collapsing margins
I think that's also triggered by layout / paint containment
Updated•9 months ago
|
Comment 16•9 months ago
|
||
Comment 17•9 months ago
|
||
I uploaded more tests for dynamic layout/paint containment and change to independent formatting context (checking it is a BFC via floats).
This approach does not work well for dynamic content-visibility change because of extra restriction implied by non-visible content-visibility. IIRC I had tried a test via margins in the past and that didn't work either.
Anyway, I hope this test should be enough to check this NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS stuff.
Comment 18•9 months ago
|
||
(In reply to Frédéric Wang (:fredw) from comment #17)
Anyway, I hope this test should be enough to check this NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS stuff.
The IFC test via floats continue to pass after D197043 (even after removing the abs/fixed/flex parts). Indeed the mentioned flag does not seem involved here (see below).
(In reply to Daniel Holbert [:dholbert] from comment #1)
Here's where we currently set
NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS
for contain:layout and paint innsBlockFrame::Init
:
https://searchfox.org/mozilla-central/rev/2fd70a667e6f89a9ec6622ecb302b1ab48e4a062/layout/generic/nsBlockFrame.cpp#7406,7408
(To handle this robustly without reconstruction, we would need to add code to set and unset those bits on an already-existing nsBlockFrame, and handle the associated fallout to e.g. floats in the subtree, maybe with something similar tonsChangeHint_UpdateContainingBlock
but for floats.)
So IIUC NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS is actually only used for the legacy -webkit-line-clamp
. https://searchfox.org/mozilla-central/search?q=symbol:M_52ebc03086d8257e&redirect=false
Since this is a not-yet standardized feature I'd suggest not to block shipping content-visibility for that and instead open a separate bug as a dependency of bug 1540681. WDYT?
Assignee | ||
Comment 19•9 months ago
|
||
Hmm, I don't see how it's only used for line-clamp. NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS
contains two bits, that are used separately in a bunch of other places, right?
Comment 20•9 months ago
|
||
OK I stand corrected I didn't notice it was NS_BLOCK_FLOAT_MGR | NS_BLOCK_MARGIN_ROOT...
The WPT tests I wrote are enabled to detect any regression with current patches though, but I can't explain why without further investigation.
Comment 21•9 months ago
|
||
Comment 23•9 months ago
|
||
Comment 24•9 months ago
|
||
Comment 25•9 months ago
|
||
Comment 26•9 months ago
|
||
Comment 27•9 months ago
|
||
Comment 28•9 months ago
|
||
(In reply to Frédéric Wang (:fredw) from comment #20)
OK I stand corrected I didn't notice it was NS_BLOCK_FLOAT_MGR | NS_BLOCK_MARGIN_ROOT...
The WPT tests I wrote are enabled to detect any regression with current patches though, but I can't explain why without further investigation.
OK I debugged what's happening and I just realized we never addressed Cathie's comment here: https://phabricator.services.mozilla.com/D196050#inline-1089249 which means we are actually not fixing the issue for CSS contain
. This explains why D196050 only made content-visibility tests regress... In any case, now I have tests so I can investigate options suggested in comment 1.
Assignee | ||
Updated•8 months ago
|
Comment 29•8 months ago
|
||
bugherder |
Comment 31•8 months ago
|
||
This patch introduces an NS_BLOCK_DYNAMIC_BFC state bit that has the
same effect has setting the NS_BLOCK_FLOAT_MGR and NS_BLOCK_MARGIN_ROOT
bits. The following changes are made:
-
NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS is renamed
NS_BLOCK_FORMATTING_CONTEXT_STATIC_STATE_BITS. All the places
that set NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS use that new
name, except paint/layout containments which use the new
NS_BLOCK_DYNAMIC_BFC flag. -
HasAllStateBits(NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS) tests
get a check for HasAnyStateBits(NS_BLOCK_DYNAMIC_BFC) included. -
NS_BLOCK_FLOAT_MGR in HasAnyStateBits calls is replaced with
NS_BLOCK_FLOAT_MGR_STATE_BITS, defined as the bitwise or of
NS_BLOCK_FLOAT_MGR and NS_BLOCK_DYNAMIC_BFC. -
NS_BLOCK_MARGIN_ROOT in HasAnyStateBits is replaced with
NS_BLOCK_MARGIN_ROOT_BITS, defined as the bitwise or of
NS_BLOCK_MARGIN_ROOT and NS_BLOCK_DYNAMIC_BFC.
This patch does not introduce any observable behavior change, but will
prepare for dynamic update of NS_BLOCK_DYNAMIC_BFC without having to rebuild
the frame tree.
Updated•8 months ago
|
Updated•8 months ago
|
Updated•8 months ago
|
Updated•8 months ago
|
Comment 32•8 months ago
|
||
(In reply to Frédéric Wang (:fredw) from comment #28)
OK I debugged what's happening and I just realized we never addressed Cathie's comment here: https://phabricator.services.mozilla.com/D196050#inline-1089249 which means we are actually not fixing the issue for CSS
contain
. This explains why D196050 only made content-visibility tests regress... In any case, now I have tests so I can investigate options suggested in comment 1.
I've uploaded a new version of the patch that handles NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS dynamically. It uses MaybeHasFloats to verify whether we need a reconstruct. probably not optimized but it's fast and is hopefully good enough.
(In reply to James Teh [:Jamie] (away returning 22 Jan) from comment #10)
Folks are probably already aware of this, but just to be clear, we generally want to avoid recreating an accessible if it didn't change semantically. If a style changes but the accessible is still in the tree, we don't want to recreate it if we can avoid it. Obviously, if it gets hidden, we should prune it.
Note that we have cases where we recreate unnecessarily; e.g. CSS visibility in bug 1638713. We want to try to avoid introducing new bugs like that, though.
I thought a bit more about this.
content-visibility: hidden
is actually closer to display: none
in the sense that that the descendants are never exposed in the accessibility tree. So something like bug 1638713 is not possible. I updated the patch to make clear it does not reach the "Remove the subtree of this invisible element, and ask any shown descendant to add themselves back." code.
Comment 33•8 months ago
|
||
Updated•8 months ago
|
Comment 34•8 months ago
|
||
nsCSSFrameConstructor::ConstructSelectFrame is the only place where
these functions are called and the flags parameter is always set to
NS_BLOCK_FLOAT_MGR.
Updated•8 months ago
|
Comment 35•8 months ago
|
||
These flags are generally always set simultaneously via
NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS. This commit changes the three
remaining places where only NS_BLOCK_FLOAT_MGR is set:
- nsFileControlFrame, used by <input type="file">
- nsComboboxControlFrame used by <select>
- nsSelectsAreaFrame, used by <select multiple>
This commit may affect the places where NS_BLOCK_MARGIN_ROOT
is currently tested for these elements. Currently, that's done that way:
- By calling HasAllStateBits(NS_BLOCK_FORMATTING_CONTEXT_STATE_BITS)
(nsIFrame::CanBeDynamicReflowRoot is tweaked to follow that format) - By calling HasAllStateBits(NS_BLOCK_MARGIN_ROOT)
TODO: check a bit whether/why it's ok to do so....
Updated•8 months ago
|
Comment 36•8 months ago
|
||
Comment 37•8 months ago
|
||
Updated•8 months ago
|
Updated•8 months ago
|
Updated•8 months ago
|
Comment 38•8 months ago
|
||
Assignee | ||
Updated•8 months ago
|
Comment 39•8 months ago
|
||
bugherder |
Comment 40•8 months ago
|
||
bugherder |
Comment 41•8 months ago
|
||
Updated•8 months ago
|
Updated•8 months ago
|
Comment 42•8 months ago
|
||
bugherder |
Comment 43•8 months ago
|
||
Comment 44•8 months ago
|
||
bugherder |
Comment 45•8 months ago
|
||
Closing now.
Follow-up bug 1874826 and bug 1874823 opened.
Updated•8 months ago
|
Updated•8 months ago
|
Updated•8 months ago
|
Description
•