From def69bfc9d9e2333a4c6bc5a254169754f0f4116 Mon Sep 17 00:00:00 2001 From: Dana Estra Date: Mon, 2 Oct 2023 14:06:46 -0700 Subject: [PATCH] Ruby in WebVTT are not rendered correctly https://bugs.webkit.org/show_bug.cgi?id=262064 rdar://116011365 Reviewed by Jer Noble and Eric Carlson. This patch creates a new class WebVTTRubyElement, a subclass of WebVTTElementImpl And RubyElement. Now, each ruby tag in a VTT is associated with a WebVTTRubyElement, Which has access to RubyElement::createElementRenderer() to render it appropriately. Added a layout test to check that ruby elements in WebVTT cues are positioned correctly. * LayoutTests/media/track/captions-webvtt/ruby.vtt: Added. * LayoutTests/media/track/webvtt-ruby.html: Added. * LayoutTests/media/track/webvtt-ruby-expected.txt: Added. * Source/WebCore/css/SelectorCheckerTestFunctions.h: (WebCore::matchesLangPseudoClass): (WebCore::matchesFutureCuePseudoClass): (WebCore::matchesPastCuePseudoClass): * Source/WebCore/dom/Node.h: (WebCore::Node::isWebVTTRubyElement const): (WebCore::Node::isWebVTTRubyTextElement const): * Source/WebCore/html/RubyElement.h: (): Deleted. * Source/WebCore/html/RubyTextElement.h: (): Deleted. * Source/WebCore/html/track/VTTCue.cpp: (WebCore::VTTCue::markFutureAndPastNodes): * Source/WebCore/html/track/WebVTTElement.cpp: (WebCore::WebVTTElement::WebVTTElement): (WebCore::WebVTTElementImpl::create): (WebCore::WebVTTElementImpl::cloneElementWithoutAttributesAndChildren): (WebCore::WebVTTElementImpl::createEquivalentHTMLElement): (WebCore::WebVTTElement::create): Deleted. (WebCore::WebVTTElement::cloneElementWithoutAttributesAndChildren): Deleted. (WebCore::WebVTTElement::createEquivalentHTMLElement): Deleted. * Source/WebCore/html/track/WebVTTElement.h: (WebCore::WebVTTElementImpl::WebVTTElementImpl): (isType): * Source/WebCore/html/track/WebVTTParser.cpp: (WebCore::WebVTTTreeBuilder::currentType const): (WebCore::WebVTTTreeBuilder::buildFromString): (WebCore::WebVTTTreeBuilder::constructTreeFromToken): * Source/WebCore/page/CaptionUserPreferencesMediaAF.cpp: (WebCore::CaptionUserPreferencesMediaAF::captionsStyleSheetOverride const): * Source/WebCore/style/ElementRuleCollector.cpp: (WebCore::Style::ElementRuleCollector::collectMatchingShadowPseudoElementRules): Canonical link: https://commits.webkit.org/268746@main --- .../media/track/captions-webvtt/ruby.vtt | 4 + .../media/track/webvtt-ruby-expected.txt | 12 +++ LayoutTests/media/track/webvtt-ruby.html | 38 ++++++++ .../css/SelectorCheckerTestFunctions.h | 20 ++++- Source/WebCore/dom/Node.h | 2 + Source/WebCore/html/RubyElement.h | 6 +- Source/WebCore/html/RubyTextElement.h | 6 +- Source/WebCore/html/track/VTTCue.cpp | 14 +-- Source/WebCore/html/track/WebVTTElement.cpp | 46 ++++++---- Source/WebCore/html/track/WebVTTElement.h | 86 ++++++++++++++++--- Source/WebCore/html/track/WebVTTParser.cpp | 29 ++++--- .../page/CaptionUserPreferencesMediaAF.cpp | 5 +- Source/WebCore/style/ElementRuleCollector.cpp | 2 +- 13 files changed, 218 insertions(+), 52 deletions(-) create mode 100644 LayoutTests/media/track/captions-webvtt/ruby.vtt create mode 100644 LayoutTests/media/track/webvtt-ruby-expected.txt create mode 100644 LayoutTests/media/track/webvtt-ruby.html diff --git a/LayoutTests/media/track/captions-webvtt/ruby.vtt b/LayoutTests/media/track/captions-webvtt/ruby.vtt new file mode 100644 index 000000000000..320bf77a41fc --- /dev/null +++ b/LayoutTests/media/track/captions-webvtt/ruby.vtt @@ -0,0 +1,4 @@ +WEBVTT + +00:00:00.000 --> 00:00:05.000 align:left +This is a ruby baseruby diff --git a/LayoutTests/media/track/webvtt-ruby-expected.txt b/LayoutTests/media/track/webvtt-ruby-expected.txt new file mode 100644 index 000000000000..b6c7b48daed9 --- /dev/null +++ b/LayoutTests/media/track/webvtt-ruby-expected.txt @@ -0,0 +1,12 @@ + +EVENT(canplay) +EVENT(addtrack) +EXPECTED (video.textTracks.length == '1') OK +RUN(video.textTracks[0].mode = 'showing') +RUN(video.currentTime = 1) +EVENT(seeked) +EXPECTED (window.internals.shadowRoot(video).querySelector('rt') != 'null') OK +EXPECTED (rubyText.offsetTop < rubyBase.offsetTop == 'true') OK +EXPECTED (rubyBase.offsetTop == (rubyText.offsetTop + rubyText.offsetHeight) == 'true') OK +END OF TEST + diff --git a/LayoutTests/media/track/webvtt-ruby.html b/LayoutTests/media/track/webvtt-ruby.html new file mode 100644 index 000000000000..65550d2f78c6 --- /dev/null +++ b/LayoutTests/media/track/webvtt-ruby.html @@ -0,0 +1,38 @@ + + + + WebVTTRubyText elements should appear above WebVTTRuby elements + + + + + + + + + \ No newline at end of file diff --git a/Source/WebCore/css/SelectorCheckerTestFunctions.h b/Source/WebCore/css/SelectorCheckerTestFunctions.h index fda679a95def..c804b7ea5430 100644 --- a/Source/WebCore/css/SelectorCheckerTestFunctions.h +++ b/Source/WebCore/css/SelectorCheckerTestFunctions.h @@ -206,6 +206,10 @@ ALWAYS_INLINE bool matchesLangPseudoClass(const Element& element, const FixedVec #if ENABLE(VIDEO) if (is(element)) language = downcast(element).language(); + else if (is(element)) + language = downcast(element).language(); + else if (is(element)) + language = downcast(element).language(); else #endif language = element.effectiveLang(); @@ -469,12 +473,24 @@ ALWAYS_INLINE bool matchesPictureInPicturePseudoClass(const Element& element) ALWAYS_INLINE bool matchesFutureCuePseudoClass(const Element& element) { - return is(element) && !downcast(element).isPastNode(); + if (auto* webVTTElement = dynamicDowncast(element)) + return !webVTTElement->isPastNode(); + if (auto* webVTTRubyElement = dynamicDowncast(element)) + return !webVTTRubyElement->isPastNode(); + if (auto* webVTTRubyTextElement = dynamicDowncast(element)) + return !webVTTRubyTextElement->isPastNode(); + return false; } ALWAYS_INLINE bool matchesPastCuePseudoClass(const Element& element) { - return is(element) && downcast(element).isPastNode(); + if (is(element)) + return downcast(element).isPastNode(); + if (is(element)) + return downcast(element).isPastNode(); + if (is(element)) + return downcast(element).isPastNode(); + return false; } ALWAYS_INLINE bool matchesPlayingPseudoClass(const Element& element) diff --git a/Source/WebCore/dom/Node.h b/Source/WebCore/dom/Node.h index d0211591f98b..23ace7391820 100644 --- a/Source/WebCore/dom/Node.h +++ b/Source/WebCore/dom/Node.h @@ -220,6 +220,8 @@ class Node : public EventTarget { #if ENABLE(VIDEO) virtual bool isWebVTTElement() const { return false; } + virtual bool isWebVTTRubyElement() const { return false; } + virtual bool isWebVTTRubyTextElement() const { return false; } #endif bool isStyledElement() const { return hasNodeFlag(NodeFlag::IsHTMLElement) || hasNodeFlag(NodeFlag::IsSVGElement) || hasNodeFlag(NodeFlag::IsMathMLElement); } virtual bool isAttributeNode() const { return false; } diff --git a/Source/WebCore/html/RubyElement.h b/Source/WebCore/html/RubyElement.h index 27ec07f32711..a3ac4096a0f0 100644 --- a/Source/WebCore/html/RubyElement.h +++ b/Source/WebCore/html/RubyElement.h @@ -29,14 +29,16 @@ namespace WebCore { -class RubyElement final : public HTMLElement { +class RubyElement : public HTMLElement { WTF_MAKE_ISO_ALLOCATED(RubyElement); public: static Ref create(Document&); static Ref create(const QualifiedName&, Document&); -private: +protected: RubyElement(const QualifiedName&, Document&); + +private: RenderPtr createElementRenderer(RenderStyle&&, const RenderTreePosition&) override; }; diff --git a/Source/WebCore/html/RubyTextElement.h b/Source/WebCore/html/RubyTextElement.h index 725b4e527a41..40eef1126f4e 100644 --- a/Source/WebCore/html/RubyTextElement.h +++ b/Source/WebCore/html/RubyTextElement.h @@ -29,14 +29,16 @@ namespace WebCore { -class RubyTextElement final : public HTMLElement { +class RubyTextElement : public HTMLElement { WTF_MAKE_ISO_ALLOCATED(RubyTextElement); public: static Ref create(Document&); static Ref create(const QualifiedName&, Document&); -private: +protected: RubyTextElement(const QualifiedName&, Document&); + +private: RenderPtr createElementRenderer(RenderStyle&&, const RenderTreePosition&) override; }; diff --git a/Source/WebCore/html/track/VTTCue.cpp b/Source/WebCore/html/track/VTTCue.cpp index 987a232264af..45e255432b70 100644 --- a/Source/WebCore/html/track/VTTCue.cpp +++ b/Source/WebCore/html/track/VTTCue.cpp @@ -944,12 +944,16 @@ void VTTCue::markFutureAndPastNodes(ContainerNode* root, const MediaTime& previo isPastNode = false; } - if (is(*child)) { + if (is(*child)) downcast(*child).setIsPastNode(isPastNode); - // Make an elemenet id match a cue id for style matching purposes. - if (!id().isEmpty()) - downcast(*child).setIdAttribute(id()); - } + else if (is(*child)) + downcast(*child).setIsPastNode(isPastNode); + else if (is(*child)) + downcast(*child).setIsPastNode(isPastNode); + + // Make an element id match a cue id for style matching purposes. + if (!id().isEmpty() && is(*child)) + downcast(*child).setIdAttribute(id()); } } diff --git a/Source/WebCore/html/track/WebVTTElement.cpp b/Source/WebCore/html/track/WebVTTElement.cpp index 18b06f28dac5..52c731d3fc87 100644 --- a/Source/WebCore/html/track/WebVTTElement.cpp +++ b/Source/WebCore/html/track/WebVTTElement.cpp @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Apple Inc. All rights reserved. + * Copyright (C) 2013-2023 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -30,6 +30,9 @@ #include "ElementInlines.h" #include "HTMLSpanElement.h" +#include "RenderRuby.h" +#include "RenderRubyText.h" +#include "RenderTreePosition.h" #include "RubyElement.h" #include "RubyTextElement.h" #include "TextTrack.h" @@ -38,6 +41,8 @@ namespace WebCore { WTF_MAKE_ISO_ALLOCATED_IMPL(WebVTTElement); +WTF_MAKE_ISO_ALLOCATED_IMPL(WebVTTRubyElement); +WTF_MAKE_ISO_ALLOCATED_IMPL(WebVTTRubyTextElement); static const QualifiedName& nodeTypeToTagName(WebVTTNodeType nodeType) { @@ -73,26 +78,37 @@ static const QualifiedName& nodeTypeToTagName(WebVTTNodeType nodeType) } } -WebVTTElement::WebVTTElement(WebVTTNodeType nodeType, Document& document) - : Element(nodeTypeToTagName(nodeType), document, CreateElement) - , m_isPastNode(0) - , m_webVTTNodeType(nodeType) +WebVTTElement::WebVTTElement(WebVTTNodeType nodeType, AtomString language, Document& document) + : WebVTTElementImpl(nodeType, language) + , Element(nodeTypeToTagName(nodeType), document, CreateElement) { } -Ref WebVTTElement::create(WebVTTNodeType nodeType, Document& document) +Ref WebVTTElementImpl::create(WebVTTNodeType nodeType, AtomString language, Document& document) { - return adoptRef(*new WebVTTElement(nodeType, document)); + switch (nodeType) { + default: + case WebVTTNodeTypeNone: + case WebVTTNodeTypeClass: + case WebVTTNodeTypeItalic: + case WebVTTNodeTypeLanguage: + case WebVTTNodeTypeBold: + case WebVTTNodeTypeUnderline: + case WebVTTNodeTypeVoice: + return adoptRef(*new WebVTTElement(nodeType, language, document)); + case WebVTTNodeTypeRuby: + return adoptRef(*new WebVTTRubyElement(language, document)); + case WebVTTNodeTypeRubyText: + return adoptRef(*new WebVTTRubyTextElement(language, document)); + } } -Ref WebVTTElement::cloneElementWithoutAttributesAndChildren(Document& targetDocument) +Ref WebVTTElementImpl::cloneElementWithoutAttributesAndChildren(Document& targetDocument) { - Ref clone = create(static_cast(m_webVTTNodeType), targetDocument); - clone->setLanguage(m_language); - return clone; + return create(static_cast(m_webVTTNodeType), m_language, targetDocument); } -Ref WebVTTElement::createEquivalentHTMLElement(Document& document) +Ref WebVTTElementImpl::createEquivalentHTMLElement(Document& document) { RefPtr htmlElement; @@ -101,8 +117,8 @@ Ref WebVTTElement::createEquivalentHTMLElement(Document& document) case WebVTTNodeTypeLanguage: case WebVTTNodeTypeVoice: htmlElement = HTMLSpanElement::create(document); - htmlElement->setAttributeWithoutSynchronization(HTMLNames::titleAttr, attributeWithoutSynchronization(voiceAttributeName())); - htmlElement->setAttributeWithoutSynchronization(HTMLNames::langAttr, attributeWithoutSynchronization(langAttributeName())); + htmlElement->setAttributeWithoutSynchronization(HTMLNames::titleAttr, toElement().attributeWithoutSynchronization(voiceAttributeName())); + htmlElement->setAttributeWithoutSynchronization(HTMLNames::langAttr, toElement().attributeWithoutSynchronization(langAttributeName())); break; case WebVTTNodeTypeItalic: htmlElement = HTMLElement::create(HTMLNames::iTag, document); @@ -123,7 +139,7 @@ Ref WebVTTElement::createEquivalentHTMLElement(Document& document) ASSERT(htmlElement); if (htmlElement) - htmlElement->setAttributeWithoutSynchronization(HTMLNames::classAttr, attributeWithoutSynchronization(HTMLNames::classAttr)); + htmlElement->setAttributeWithoutSynchronization(HTMLNames::classAttr, toElement().attributeWithoutSynchronization(HTMLNames::classAttr)); return htmlElement.releaseNonNull(); } diff --git a/Source/WebCore/html/track/WebVTTElement.h b/Source/WebCore/html/track/WebVTTElement.h index 1c5c85e32b28..e1d54b2de60d 100644 --- a/Source/WebCore/html/track/WebVTTElement.h +++ b/Source/WebCore/html/track/WebVTTElement.h @@ -1,5 +1,5 @@ /* - * Copyright (C) 2013 Apple Inc. All rights reserved. + * Copyright (C) 2013-2023 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions @@ -28,6 +28,8 @@ #if ENABLE(VIDEO) #include "HTMLElement.h" +#include "RubyElement.h" +#include "RubyTextElement.h" #include namespace WebCore { @@ -44,13 +46,14 @@ enum WebVTTNodeType { WebVTTNodeTypeVoice }; -class WebVTTElement final : public Element { - WTF_MAKE_ISO_ALLOCATED(WebVTTElement); +class WebVTTElement; + +class WebVTTElementImpl { public: - static Ref create(const WebVTTNodeType, Document&); + static Ref create(const WebVTTNodeType, AtomString language, Document&); Ref createEquivalentHTMLElement(Document&); - Ref cloneElementWithoutAttributesAndChildren(Document&) override; + Ref cloneElementWithoutAttributesAndChildren(Document&); void setWebVTTNodeType(WebVTTNodeType type) { m_webVTTNodeType = static_cast(type); } WebVTTNodeType webVTTNodeType() const { return static_cast(m_webVTTNodeType); } @@ -66,28 +69,89 @@ class WebVTTElement final : public Element { static NeverDestroyed voiceAttr(nullAtom(), "voice"_s, nullAtom()); return voiceAttr; } - + static const QualifiedName& langAttributeName() { static NeverDestroyed voiceAttr(nullAtom(), "lang"_s, nullAtom()); return voiceAttr; } -private: - WebVTTElement(WebVTTNodeType, Document&); - - bool isWebVTTElement() const override { return true; } +protected: + WebVTTElementImpl(WebVTTNodeType nodeType, AtomString language) + : m_isPastNode { false } + , m_webVTTNodeType { static_cast(nodeType) } + , m_language { language } + { + } + virtual ~WebVTTElementImpl() = default; + virtual Element& toElement() = 0; unsigned m_isPastNode : 1; unsigned m_webVTTNodeType : 4; - + AtomString m_language; }; +class WebVTTElement final : public WebVTTElementImpl, public Element { + WTF_MAKE_ISO_ALLOCATED(WebVTTElement); +public: + Ref cloneElementWithoutAttributesAndChildren(Document& document) final { return WebVTTElementImpl::cloneElementWithoutAttributesAndChildren(document); } + +private: + friend class WebVTTElementImpl; + WebVTTElement(WebVTTNodeType, AtomString language, Document&); + + bool isWebVTTElement() const final { return true; } + Element& toElement() final { return *this; } +}; + +class WebVTTRubyElement final : public WebVTTElementImpl, public RubyElement { + WTF_MAKE_ISO_ALLOCATED(WebVTTRubyElement); +public: + Ref cloneElementWithoutAttributesAndChildren(Document& document) final { return WebVTTElementImpl::cloneElementWithoutAttributesAndChildren(document); } + +private: + friend class WebVTTElementImpl; + WebVTTRubyElement(AtomString language, Document& document) + : WebVTTElementImpl(WebVTTNodeTypeRuby, language) + , RubyElement(HTMLNames::rubyTag, document) + { + } + + bool isWebVTTRubyElement() const final { return true; } + Element& toElement() final { return *this; } +}; + +class WebVTTRubyTextElement final : public WebVTTElementImpl, public RubyTextElement { + WTF_MAKE_ISO_ALLOCATED(WebVTTRubyTextElement); +public: + Ref cloneElementWithoutAttributesAndChildren(Document& document) final { return WebVTTElementImpl::cloneElementWithoutAttributesAndChildren(document); } + +private: + friend class WebVTTElementImpl; + WebVTTRubyTextElement(AtomString language, Document& document) + : WebVTTElementImpl(WebVTTNodeTypeRubyText, language) + , RubyTextElement(HTMLNames::rtTag, document) + { + } + + bool isWebVTTRubyTextElement() const final { return true; } + Element& toElement() final { return *this; } +}; + + } // namespace WebCore SPECIALIZE_TYPE_TRAITS_BEGIN(WebCore::WebVTTElement) static bool isType(const WebCore::Node& node) { return node.isWebVTTElement(); } SPECIALIZE_TYPE_TRAITS_END() +SPECIALIZE_TYPE_TRAITS_BEGIN(WebCore::WebVTTRubyElement) + static bool isType(const WebCore::Node& node) { return node.isWebVTTRubyElement(); } +SPECIALIZE_TYPE_TRAITS_END() + +SPECIALIZE_TYPE_TRAITS_BEGIN(WebCore::WebVTTRubyTextElement) + static bool isType(const WebCore::Node& node) { return node.isWebVTTRubyTextElement(); } +SPECIALIZE_TYPE_TRAITS_END() + #endif // ENABLE(VIDEO) diff --git a/Source/WebCore/html/track/WebVTTParser.cpp b/Source/WebCore/html/track/WebVTTParser.cpp index aa5478b61a47..ce88197601ee 100644 --- a/Source/WebCore/html/track/WebVTTParser.cpp +++ b/Source/WebCore/html/track/WebVTTParser.cpp @@ -509,7 +509,10 @@ class WebVTTTreeBuilder { private: void constructTreeFromToken(Document&); + WebVTTNodeType currentType() const { return m_typeStack.isEmpty() ? WebVTTNodeTypeNone : m_typeStack.last(); } + WebVTTToken m_token; + Vector m_typeStack; RefPtr m_currentNode; Vector m_languageStack; Document& m_document; @@ -531,7 +534,7 @@ Ref WebVTTTreeBuilder::buildFromString(const String& cueText) WebVTTTokenizer tokenizer(cueText); m_languageStack.clear(); - + m_typeStack.clear(); while (tokenizer.nextToken(m_token)) constructTreeFromToken(m_document); @@ -694,12 +697,12 @@ void WebVTTTreeBuilder::constructTreeFromToken(Document& document) if (nodeType == WebVTTNodeTypeNone) break; - WebVTTNodeType currentType = is(*m_currentNode) ? downcast(*m_currentNode).webVTTNodeType() : WebVTTNodeTypeNone; // is only allowed if the current node is . - if (nodeType == WebVTTNodeTypeRubyText && currentType != WebVTTNodeTypeRuby) + if (nodeType == WebVTTNodeTypeRubyText && currentType() != WebVTTNodeTypeRuby) break; - auto child = WebVTTElement::create(nodeType, document); + auto language = !m_languageStack.isEmpty() ? m_languageStack.last() : emptyAtom(); + auto child = WebVTTElementImpl::create(nodeType, language, document); if (!m_token.classes().isEmpty()) child->setAttributeWithoutSynchronization(classAttr, m_token.classes()); @@ -709,10 +712,9 @@ void WebVTTTreeBuilder::constructTreeFromToken(Document& document) m_languageStack.append(m_token.annotation()); child->setAttributeWithoutSynchronization(WebVTTElement::langAttributeName(), m_languageStack.last()); } - if (!m_languageStack.isEmpty()) - child->setLanguage(m_languageStack.last()); m_currentNode->parserAppendChild(child); m_currentNode = WTFMove(child); + m_typeStack.append(nodeType); break; } case WebVTTTokenTypes::EndTag: { @@ -722,23 +724,26 @@ void WebVTTTreeBuilder::constructTreeFromToken(Document& document) // The only non-VTTElement would be the DocumentFragment root. (Text // nodes and PIs will never appear as m_currentNode.) - if (!is(*m_currentNode)) + if (currentType() == WebVTTNodeTypeNone) break; - WebVTTNodeType currentType = downcast(*m_currentNode).webVTTNodeType(); - bool matchesCurrent = nodeType == currentType; + bool matchesCurrent = nodeType == currentType(); if (!matchesCurrent) { // auto-closes - if (currentType == WebVTTNodeTypeRubyText && nodeType == WebVTTNodeTypeRuby) { - if (m_currentNode->parentNode()) + if (currentType() == WebVTTNodeTypeRubyText && nodeType == WebVTTNodeTypeRuby) { + if (m_currentNode->parentNode()) { m_currentNode = m_currentNode->parentNode(); + m_typeStack.removeLast(); + } } else break; } if (nodeType == WebVTTNodeTypeLanguage) m_languageStack.removeLast(); - if (m_currentNode->parentNode()) + if (m_currentNode->parentNode()) { m_currentNode = m_currentNode->parentNode(); + m_typeStack.removeLast(); + } break; } case WebVTTTokenTypes::TimestampTag: { diff --git a/Source/WebCore/page/CaptionUserPreferencesMediaAF.cpp b/Source/WebCore/page/CaptionUserPreferencesMediaAF.cpp index bdd10c068165..095c1b440de5 100644 --- a/Source/WebCore/page/CaptionUserPreferencesMediaAF.cpp +++ b/Source/WebCore/page/CaptionUserPreferencesMediaAF.cpp @@ -590,9 +590,10 @@ String CaptionUserPreferencesMediaAF::captionsStyleSheetOverride() const String edgeStyle = captionsTextEdgeCSS(); String fontName = captionsDefaultFontCSS(); String background = captionsBackgroundCSS(); - if (!background.isEmpty() || !captionsColor.isEmpty() || !edgeStyle.isEmpty() || !fontName.isEmpty()) + if (!background.isEmpty() || !captionsColor.isEmpty() || !edgeStyle.isEmpty() || !fontName.isEmpty()) { captionsOverrideStyleSheet.append(" ::", ShadowPseudoIds::cue(), '{', background, captionsColor, edgeStyle, fontName, '}'); - + captionsOverrideStyleSheet.append(" ::", ShadowPseudoIds::cue(), "(rt) {", background, captionsColor, edgeStyle, fontName, '}'); + } String windowColor = captionsWindowCSS(); String windowCornerRadius = windowRoundedCornerRadiusCSS(); if (!windowColor.isEmpty() || !windowCornerRadius.isEmpty()) diff --git a/Source/WebCore/style/ElementRuleCollector.cpp b/Source/WebCore/style/ElementRuleCollector.cpp index 1612c26444e5..b0ebe579d473 100644 --- a/Source/WebCore/style/ElementRuleCollector.cpp +++ b/Source/WebCore/style/ElementRuleCollector.cpp @@ -395,7 +395,7 @@ void ElementRuleCollector::collectMatchingShadowPseudoElementRules(const MatchRe auto& rules = matchRequest.ruleSet; #if ENABLE(VIDEO) // FXIME: WebVTT should not be done by styling UA shadow trees like this. - if (element().isWebVTTElement()) + if (element().isWebVTTElement() || element().isWebVTTRubyElement() || element().isWebVTTRubyTextElement()) collectMatchingRulesForList(&rules.cuePseudoRules(), matchRequest); #endif auto& pseudoId = element().shadowPseudoId();