COMMIT_MESSAGE

 1[CSS Container Queries] Correctly resolve queries when container size is affected by a subsequent sibling
 2https://bugs.webkit.org/show_bug.cgi?id=241457
 3
 4Reviewed by NOBODY (OOPS!).
 5
 6We currently only take style changes before the container into account.
 7
 8This patch changes the query container resolution so that whenever we encounter a query container
 9we skip the subtree and restart the resolution from the main resolution loop. This way we
 10can optimize away unnecessary layouts while also handling backward dependencies.
 11
 12* LayoutTests/imported/w3c/web-platform-tests/css/css-contain/container-queries/sibling-layout-dependency-expected.txt:
 13* Source/WebCore/dom/Document.cpp:
 14(WebCore::Document::resolveStyle):
 15
 16Loop also when there are no style changes to apply but there are unresolved container. In this case we just resume
 17the style resolution where we left it.
 18
 19* Source/WebCore/dom/Element.cpp:
 20(WebCore::Element::invalidateForQueryContainerChange):
 21
 22Invalidate all descendants when a query container is resized. In practice we ended up computing them all anyway.
 23
 24* Source/WebCore/style/StyleChange.cpp:
 25(WebCore::Style::determineChange):
 26* Source/WebCore/style/StyleChange.h:
 27
 28Add new Descendants value for when container type or name changes.
 29
 30* Source/WebCore/style/StyleTreeResolver.cpp:
 31(WebCore::Style::TreeResolver::computeDescendantsToResolve):
 32(WebCore::Style::TreeResolver::pushParent):
 33(WebCore::Style::TreeResolver::popParent):
 34(WebCore::Style::TreeResolver::resolveComposedTree):
 35(WebCore::Style::TreeResolver::determineQueryContainerAction):
 36
 37Use two buckets for unresolved and resolved containers. Use the resolved bucket to avoid
 38recomputing containers we have resolved already.
 39
 40(WebCore::Style::TreeResolver::resolve):
 41
 42Move the containers from the unresolved to resolved bucket in the beginning.
 43
 44(WebCore::Style::TreeResolver::updateQueryContainer): Deleted.
 45* Source/WebCore/style/StyleTreeResolver.h:

Source/WebCore/dom/Document.cpp

@@void Document::resolveStyle(ResolveStyleType type)
20952095 Style::TreeResolver resolver(*this, WTFMove(m_pendingRenderTreeUpdate));
20962096 auto styleUpdate = resolver.resolve();
20972097
2098  while (resolver.hasUnresolvedQueryContainers() && styleUpdate) {
2099  SetForScope resolvingContainerQueriesScope(m_isResolvingContainerQueries, true);
2100 
2101  updateRenderTree(WTFMove(styleUpdate));
2102 
2103  if (frameView.layoutContext().needsLayout())
2104  frameView.layoutContext().layout();
 2098 while (resolver.hasUnresolvedQueryContainers()) {
 2099 if (styleUpdate) {
 2100 SetForScope resolvingContainerQueriesScope(m_isResolvingContainerQueries, true);
 2101
 2102 updateRenderTree(WTFMove(styleUpdate));
 2103
 2104 if (frameView.layoutContext().needsLayout())
 2105 frameView.layoutContext().layout();
 2106 }
21052107
21062108 styleUpdate = resolver.resolve();
21072109 }

Source/WebCore/dom/Element.cpp

@@void Element::invalidateStyleForSubtreeInternal()
22672267
22682268void Element::invalidateForQueryContainerChange()
22692269{
2270  // FIXME: This doesn't really need to recompute the element style.
2271  Node::invalidateStyle(Style::Validity::ElementInvalid);
 2270 // FIXME: Ideally we would just recompute things that are actually affected by containers queries within the subtree.
 2271 Node::invalidateStyle(Style::Validity::SubtreeInvalid);
22722272}
22732273
22742274void Element::invalidateEventListenerRegions()

Source/WebCore/style/StyleChange.cpp

@@Change determineChange(const RenderStyle& s1, const RenderStyle& s2)
5757 if (s1.hasTextCombine() != s2.hasTextCombine())
5858 return Change::Renderer;
5959
60  if (!s1.descendantAffectingNonInheritedPropertiesEqual(s2))
61  return Change::Inherited;
62 
6360 // Query container changes affect descendant style.
6461 if (s1.containerType() != s2.containerType() || s1.containerNames() != s2.containerNames())
 62 return Change::Descendants;
 63
 64 if (!s1.descendantAffectingNonInheritedPropertiesEqual(s2))
6565 return Change::Inherited;
6666
6767 if (!s1.nonFastPathInheritedEqual(s2))

Source/WebCore/style/StyleChange.h

@@enum class Change : uint8_t {
3636 NonInherited,
3737 FastPathInherited,
3838 Inherited,
 39 Descendants,
3940 Renderer
4041};
4142

Source/WebCore/style/StyleTreeResolver.cpp

@@auto TreeResolver::computeDescendantsToResolve(Change change, Validity validity,
199199 case Change::FastPathInherited:
200200 case Change::Inherited:
201201 return DescendantsToResolve::Children;
 202 case Change::Descendants:
 203 return DescendantsToResolve::All;
202204 case Change::Renderer:
203205 return DescendantsToResolve::All;
204206 };

@@ElementUpdate TreeResolver::createAnimatedElementUpdate(std::unique_ptr<RenderSt
625627void TreeResolver::pushParent(Element& element, const RenderStyle& style, Change change, DescendantsToResolve descendantsToResolve)
626628{
627629 scope().selectorMatchingState.selectorFilter.pushParent(&element);
 630 if (style.containerType() != ContainerType::None)
 631 scope().selectorMatchingState.queryContainers.append(element);
628632
629633 Parent parent(element, style, change, descendantsToResolve);
630634

@@void TreeResolver::popParent()
645649 auto& parentElement = *parent().element;
646650
647651 parentElement.setHasValidStyle();
648  parentElement.clearChildNeedsStyleRecalc();
 652 // Don't clear the child flag if the are unresolved containers because we are going to resume the style resolution.
 653 if (m_unresolvedQueryContainers.isEmpty())
 654 parentElement.clearChildNeedsStyleRecalc();
649655
650656 if (parent().didPushScope)
651657 popScope();

@@void TreeResolver::resolveComposedTree()
834840 if (!style)
835841 resetStyleForNonRenderedDescendants(element);
836842
837  bool shouldIterateChildren = style && (element.childNeedsStyleRecalc() || descendantsToResolve != DescendantsToResolve::None);
 843 auto queryContainerAction = determineQueryContainerAction(element, style, previousContainerType);
838844
839  if (style && updateQueryContainer(element, *style, previousContainerType) == QueryContainerAction::Layout)
840  shouldIterateChildren = false;
 845 bool shouldIterateChildren = [&] {
 846 // display::none, no need to resolve descendants.
 847 if (!style)
 848 return false;
 849 // Style resolution will be resumed after the container has been resolved.
 850 if (queryContainerAction == QueryContainerAction::Resolve)
 851 return false;
 852 return element.childNeedsStyleRecalc() || descendantsToResolve != DescendantsToResolve::None;
 853 }();
 854
 855 // Ensure we respect DescendantsToResolve::All after resuming the style resolution.
 856 if (queryContainerAction == QueryContainerAction::Resolve && descendantsToResolve == DescendantsToResolve::All)
 857 element.invalidateStyleForSubtreeInternal();
841858
842859 if (!m_didSeePendingStylesheet)
843860 m_didSeePendingStylesheet = hasLoadingStylesheet(m_document.styleScope(), element, !shouldIterateChildren);

@@void TreeResolver::resolveComposedTree()
860877 popParentsToDepth(1);
861878}
862879
863 auto TreeResolver::updateQueryContainer(Element& element, const RenderStyle& style, ContainerType previousContainerType) -> QueryContainerAction
 880auto TreeResolver::determineQueryContainerAction(Element& element, const RenderStyle* style, ContainerType previousContainerType) -> QueryContainerAction
864881{
865  if (style.containerType() != ContainerType::None)
866  scope().selectorMatchingState.queryContainers.append(element);
867 
868  if (m_unresolvedQueryContainers.remove(&element))
869  return QueryContainerAction::Continue;
 882 if (!style)
 883 return QueryContainerAction::None;
870884
871  // Render tree needs to be updated before proceeding to children also if we have a former query container
872  // because container query resolution for descendants relies on it being up-to-date.
873  if (style.containerType() == ContainerType::None && previousContainerType == ContainerType::None)
 885 // FIXME: Render tree needs to be updated before proceeding to children also if we have a former query container
 886 // because container unit resolution for descendants relies on it being up-to-date.
 887 if (style->containerType() == ContainerType::None && previousContainerType == ContainerType::None)
874888 return QueryContainerAction::None;
875889
876  if (m_update->isEmpty())
877  return QueryContainerAction::Continue;
 890 if (m_resolvedQueryContainers.contains(&element))
 891 return QueryContainerAction::None;
878892
879  // Bail out from TreeResolver to build a render tree and do a layout. Resolution continues after.
880893 m_unresolvedQueryContainers.add(&element);
881  return QueryContainerAction::Layout;
 894 return QueryContainerAction::Resolve;
882895}
883896
884897std::unique_ptr<Update> TreeResolver::resolve()
885898{
 899 m_resolvedQueryContainers.add(m_unresolvedQueryContainers.begin(), m_unresolvedQueryContainers.end());
 900 m_unresolvedQueryContainers.clear();
 901
886902 Element* documentElement = m_document.documentElement();
887903 if (!documentElement) {
888904 m_document.styleScope().resolver();
889905 return nullptr;
890906 }
891907
892  // FIXME: Just need to restore the ancestor marking.
893  for (auto& queryContainer : m_unresolvedQueryContainers)
894  queryContainer->invalidateStyleForSubtreeInternal();
895 
896908 if (!documentElement->childNeedsStyleRecalc() && !documentElement->needsStyleRecalc())
897909 return WTFMove(m_update);
898910

Source/WebCore/style/StyleTreeResolver.h

@@private:
6363
6464 void resolveComposedTree();
6565
66  enum class QueryContainerAction : uint8_t { None, Continue, Layout };
67  QueryContainerAction updateQueryContainer(Element&, const RenderStyle&, ContainerType previousContainerType);
 66 enum class QueryContainerAction : uint8_t { None, Resolve };
 67 QueryContainerAction determineQueryContainerAction(Element&, const RenderStyle*, ContainerType previousContainerType);
6868
6969 enum class DescendantsToResolve : uint8_t { None, ChildrenWithExplicitInherit, Children, All };
7070 std::pair<ElementUpdate, DescendantsToResolve> resolveElement(Element&, ResolutionType);

@@private:
130130 bool m_didSeePendingStylesheet { false };
131131
132132 HashSet<RefPtr<Element>> m_unresolvedQueryContainers;
 133 HashSet<RefPtr<Element>> m_resolvedQueryContainers;
133134
134135 std::unique_ptr<Update> m_update;
135136};

LayoutTests/imported/w3c/web-platform-tests/css/css-contain/container-queries/sibling-layout-dependency-expected.txt

11XXXX
22
3 FAIL Sibling style mutation assert_equals: expected "10" but got "20"
4 FAIL Sibling style mutation, parent is affected assert_equals: expected "10" but got "20"
5 FAIL Sibling style mutation, ancestor is affected assert_equals: expected "10" but got "20"
6 FAIL Sibling text mutation assert_equals: expected "10" but got "20"
 3PASS Sibling style mutation
 4PASS Sibling style mutation, parent is affected
 5PASS Sibling style mutation, ancestor is affected
 6PASS Sibling text mutation
77