diff --git a/LayoutTests/TestExpectations b/LayoutTests/TestExpectations index 01a70b0ec317a..f2e864ed4d689 100644 --- a/LayoutTests/TestExpectations +++ b/LayoutTests/TestExpectations @@ -7596,16 +7596,6 @@ imported/w3c/web-platform-tests/css/css-view-transitions/web-animations-api-pars # Test doesn't seem to pass on Chrome Canary. imported/w3c/web-platform-tests/css/css-view-transitions/view-transition-types-mutable-no-document-element-crashtest.html [ Skip ] -# View transitions Level 2 - classes. -imported/w3c/web-platform-tests/css/css-view-transitions/pseudo-with-classes-entry.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-view-transitions/pseudo-with-classes-exit.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-view-transitions/pseudo-with-classes-mismatch-ident.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-view-transitions/pseudo-with-classes-mismatch-partial.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-view-transitions/pseudo-with-classes-mismatch-wildcard.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-view-transitions/pseudo-with-classes-old-with-class-new-without.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-view-transitions/shadow-part-with-class-inside-shadow-important.html [ ImageOnlyFailure ] -imported/w3c/web-platform-tests/css/css-view-transitions/shadow-part-with-class-inside-shadow.html [ ImageOnlyFailure ] - # View transitions Level 2 - cross document transitions. imported/w3c/web-platform-tests/css/css-view-transitions/navigation/chromium-paint-holding-timeout.html [ ImageOnlyFailure ] imported/w3c/web-platform-tests/css/css-view-transitions/navigation/no-view-transition-with-cross-origin-redirect.sub.html [ ImageOnlyFailure ] diff --git a/Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml b/Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml index 91fb8786313ec..61aaf475d33bf 100644 --- a/Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml +++ b/Source/WTF/Scripts/Preferences/UnifiedWebPreferences.yaml @@ -7555,16 +7555,16 @@ ViewGestureDebuggingEnabled: ViewTransitionClassesEnabled: type: bool category: animation - status: testable + status: stable humanReadableName: "View Transition Classes" humanReadableDescription: "Support specifying classes for view transitions" defaultValue: WebKitLegacy: - default: false + default: true WebKit: - default: false + default: true WebCore: - default: false + default: true ViewTransitionTypesEnabled: type: bool diff --git a/Source/WebCore/css/CSSSelector.cpp b/Source/WebCore/css/CSSSelector.cpp index 96562673cf245..3d96ca90acaba 100644 --- a/Source/WebCore/css/CSSSelector.cpp +++ b/Source/WebCore/css/CSSSelector.cpp @@ -385,6 +385,13 @@ static void appendPossiblyQuotedIdentifier(StringBuilder& builder, const Possibl serializeString(identifier.identifier, builder); } +WTF::TextStream& operator<<(WTF::TextStream& ts, PossiblyQuotedIdentifier identifier) +{ + StringBuilder builder; + appendPossiblyQuotedIdentifier(builder, identifier); + return ts << builder.toString(); +} + static void appendCommaSeparatedPossiblyQuotedIdentifierList(StringBuilder& builder, const FixedVector& list) { builder.append(interleave(list, appendPossiblyQuotedIdentifier, ", "_s)); diff --git a/Source/WebCore/css/CSSSelector.h b/Source/WebCore/css/CSSSelector.h index f0f1b66228052..8bb659b9666ae 100644 --- a/Source/WebCore/css/CSSSelector.h +++ b/Source/WebCore/css/CSSSelector.h @@ -39,6 +39,8 @@ struct PossiblyQuotedIdentifier { bool isNull() const { return identifier.isNull(); } }; +WTF::TextStream& operator<<(WTF::TextStream&, PossiblyQuotedIdentifier); + enum class SelectorSpecificityIncrement { ClassA = 0x10000, ClassB = 0x100, diff --git a/Source/WebCore/css/SelectorChecker.cpp b/Source/WebCore/css/SelectorChecker.cpp index 8893041e2ed84..b4a3908f33ead 100644 --- a/Source/WebCore/css/SelectorChecker.cpp +++ b/Source/WebCore/css/SelectorChecker.cpp @@ -1271,9 +1271,19 @@ bool SelectorChecker::checkOne(CheckingContext& checkingContext, LocalContext& c if (checkingContext.pseudoId != CSSSelector::pseudoId(selector.pseudoElement()) || !selector.argumentList()) return false; - // Wildcard always matches. - auto& argument = selector.argumentList()->first(); - return argument == starAtom() || argument == checkingContext.pseudoElementNameArgument; + auto& list = *selector.argumentList(); + auto& name = list.first(); + if (name != starAtom() && name != checkingContext.pseudoElementNameArgument) + return false; + + if (list.size() == 1) + return true; + + return std::ranges::all_of(list.begin() + 1, list.end(), + [&](const PossiblyQuotedIdentifier& classSelector) { + return checkingContext.classList.contains(classSelector.identifier); + } + ); } default: diff --git a/Source/WebCore/css/SelectorChecker.h b/Source/WebCore/css/SelectorChecker.h index ff7f142b25bd1..f650e2bf01bc1 100644 --- a/Source/WebCore/css/SelectorChecker.h +++ b/Source/WebCore/css/SelectorChecker.h @@ -86,6 +86,7 @@ class SelectorChecker { PseudoId pseudoId { PseudoId::None }; AtomString pseudoElementNameArgument; std::optional scrollbarState; + Vector classList; RefPtr scope; const Element* hasScope { nullptr }; bool matchesAllHasScopes { false }; diff --git a/Source/WebCore/dom/ViewTransition.cpp b/Source/WebCore/dom/ViewTransition.cpp index 0555cdb26d902..d843ade6273c4 100644 --- a/Source/WebCore/dom/ViewTransition.cpp +++ b/Source/WebCore/dom/ViewTransition.cpp @@ -331,6 +331,21 @@ static ExceptionOr checkDuplicateViewTransitionName(const AtomString& name return { }; } +static Vector effectiveViewTransitionClassList(RenderLayerModelObject& renderer, Element& originatingElement, Style::Scope& documentScope) +{ + auto classList = renderer.style().viewTransitionClasses(); + if (classList.isEmpty()) + return { }; + + auto scope = Style::Scope::forOrdinal(originatingElement, classList.first().scopeOrdinal); + if (!scope || scope != &documentScope) + return { }; + + return WTF::map(classList, [&](auto& item) { + return item.name; + }); +} + static LayoutRect captureOverflowRect(RenderLayerModelObject& renderer) { if (!renderer.hasLayer()) @@ -465,6 +480,10 @@ ExceptionOr ViewTransition::captureOldState() capture.oldImage = snapshotElementVisualOverflowClippedToViewport(*frame, renderer.get(), capture.oldOverflowRect); capture.oldLayerToLayoutOffset = layerToLayoutOffset(renderer.get()); + auto styleable = Styleable::fromRenderer(renderer); + ASSERT(styleable); + capture.classList = effectiveViewTransitionClassList(renderer, styleable->element, document()->styleScope()); + auto transitionName = renderer->style().viewTransitionName(); m_namedElements.add(transitionName->name, capture); } @@ -495,7 +514,9 @@ ExceptionOr ViewTransition::captureNewState() CapturedElement capturedElement; m_namedElements.add(name, capturedElement); } - m_namedElements.find(name)->newElement = *styleable; + auto namedElement = m_namedElements.find(name); + namedElement->classList = effectiveViewTransitionClassList(renderer, styleable->element, document()->styleScope()); + namedElement->newElement = *styleable; } return { }; }, *view->layer()); diff --git a/Source/WebCore/dom/ViewTransition.h b/Source/WebCore/dom/ViewTransition.h index 078908f66fa78..c2779e651d381 100644 --- a/Source/WebCore/dom/ViewTransition.h +++ b/Source/WebCore/dom/ViewTransition.h @@ -66,6 +66,7 @@ struct CapturedElement { LayoutSize oldSize; RefPtr oldProperties; WeakStyleable newElement; + Vector classList; RefPtr groupStyleProperties; }; diff --git a/Source/WebCore/rendering/updating/RenderTreeUpdater.cpp b/Source/WebCore/rendering/updating/RenderTreeUpdater.cpp index 71e0dec6b8690..68ccd6ded1178 100644 --- a/Source/WebCore/rendering/updating/RenderTreeUpdater.cpp +++ b/Source/WebCore/rendering/updating/RenderTreeUpdater.cpp @@ -372,15 +372,15 @@ void RenderTreeUpdater::updateAfterDescendants(Element& element, const Style::El static bool pseudoStyleCacheIsInvalid(RenderElement* renderer, RenderStyle* newStyle) { - const RenderStyle& currentStyle = renderer->style(); + const auto& currentStyle = renderer->style(); - const PseudoStyleCache* pseudoStyleCache = currentStyle.cachedPseudoStyles(); + const auto* pseudoStyleCache = currentStyle.cachedPseudoStyles(); if (!pseudoStyleCache) return false; for (auto& cache : pseudoStyleCache->styles) { - PseudoId pseudoId = cache->pseudoElementType(); - std::unique_ptr newPseudoStyle = renderer->getUncachedPseudoStyle({ pseudoId }, newStyle, newStyle); + Style::PseudoElementIdentifier pseudoElementIdentifier { cache->pseudoElementType(), cache->pseudoElementNameArgument() }; + auto newPseudoStyle = renderer->getUncachedPseudoStyle(pseudoElementIdentifier, newStyle, newStyle); if (!newPseudoStyle) return true; if (*newPseudoStyle != *cache) { diff --git a/Source/WebCore/style/ElementRuleCollector.cpp b/Source/WebCore/style/ElementRuleCollector.cpp index 2f2a649192255..c3a6171082585 100644 --- a/Source/WebCore/style/ElementRuleCollector.cpp +++ b/Source/WebCore/style/ElementRuleCollector.cpp @@ -453,6 +453,21 @@ void ElementRuleCollector::matchUARules(const RuleSet& rules) sortAndTransferMatchedRules(DeclarationOrigin::UserAgent); } +static Vector classListForNamedViewTransitionPseudoElement(const Document& document, const AtomString& name) +{ + auto* activeViewTransition = document.activeViewTransition(); + if (!activeViewTransition) + return { }; + + ASSERT(!name.isNull()); + + auto* capturedElement = activeViewTransition->namedElements().find(name); + if (!capturedElement) + return { }; + + return capturedElement->classList; +} + inline bool ElementRuleCollector::ruleMatches(const RuleData& ruleData, unsigned& specificity, ScopeOrdinal styleScopeOrdinal, const ContainerNode* scopingRoot) { // We know a sufficiently simple single part selector matches simply because we found it from the rule hash when filtering the RuleSet. @@ -508,6 +523,8 @@ inline bool ElementRuleCollector::ruleMatches(const RuleData& ruleData, unsigned context.pseudoId = m_pseudoElementRequest->pseudoId(); context.pseudoElementNameArgument = m_pseudoElementRequest->nameArgument(); context.scrollbarState = m_pseudoElementRequest->scrollbarState(); + if (isNamedViewTransitionPseudoElement(m_pseudoElementRequest->identifier())) + context.classList = classListForNamedViewTransitionPseudoElement(element().document(), context.pseudoElementNameArgument); } context.styleScopeOrdinal = styleScopeOrdinal; context.selectorMatchingState = m_selectorMatchingState;