Web Inspector: Some CSS rules for Cocoa media controls defined in inline styles inside user agent shadow root are not correctly parsed
https://bugs.webkit.org/show_bug.cgi?id=241815
rdar://85053718
Reviewed by Devin Rousso.
New test case: inspector/css/getMatchedStylesForNodeUserAgentShadowRoot.html
CSSSelectorParser has an exception that allows it to parse the following rules when the parser is in UASheetMode:
video[controls]::-webkit-media-text-track-container.visible-controls-bar
video::-webkit-media-text-track-container b
video::-webkit-media-text-track-container u
video::-webkit-media-text-track-container i
video::-webkit-media-text-track-container .hidden
These are technically invalid CSS selectors because only user action pseudo classes should be allowed after pseudo
element selectors, however exceptions exist in CSSSelectorParser to explicitly allow these otherwise invalid selectors
when the pseudo element is a WebKit-specific selector and the parsing mode is UASheetMode.
Web Inspector's InspectorStyleSheet did not previously set any special parsing mode because in a majority of cases we
never need to do this parsing of UA styles text because we do not need to resolve the actual locations of rules in a
source file since they are uneditable. These rules, being declared in the shadow root, are actually editable. To support
these rules, we now check if the owner node of the style sheet is part of a user agent shadow root, and set the parsing
mode to UASheetMode if it is.
* Source/WebCore/inspector/InspectorStyleSheet.cpp:
(ParsedStyleSheet::isInUserAgentShadowTree const):
(ParsedStyleSheet::ParsedStyleSheet):
(WebCore::InspectorStyleSheet::InspectorStyleSheet):
(WebCore::InspectorStyleSheet::ensureSourceData):
* LayoutTests/inspector/css/getMatchedStylesForNodeUserAgentShadowRoot-expected.txt: Added.
* LayoutTests/inspector/css/getMatchedStylesForNodeUserAgentShadowRoot.html: Added.
Canonical link: https://commits.webkit.org/251738@main
git-svn-id: http://svn.webkit.org/repository/webkit/trunk@295733 268f45cc-cd09-0410-ab3c-d52691b4dbfc
diff --git a/LayoutTests/inspector/css/getMatchedStylesForNodeUserAgentShadowRoot-expected.txt b/LayoutTests/inspector/css/getMatchedStylesForNodeUserAgentShadowRoot-expected.txt
new file mode 100644
index 0000000..79d5265
--- /dev/null
+++ b/LayoutTests/inspector/css/getMatchedStylesForNodeUserAgentShadowRoot-expected.txt
@@ -0,0 +1,12 @@
+Tests for the CSS.getMatchedStylesForNode command styles inside user-agent shadow roots.
+
+
+
+== Running test suite: CSS.getMatchedStylesForNode.UserAgentShadowRoot
+-- Running test case: CSS.getMatchedStylesForNode.UserAgentShadowRoot.StyleFollowingNormallyInvalidSelector
+PASS: Should have matched rule for selector '.media-controls .time-label, .media-controls .status-label'
+PASS: Should have matched rule for selector '.time-label'
+PASS: Should have matched rule for selector '.media-controls-container, .media-controls-container *'
+PASS: Should have matched rule for selector ':host(audio), :host(video.media-document.audio), *'
+PASS: Should have matched rule for selector '*'
+
diff --git a/LayoutTests/inspector/css/getMatchedStylesForNodeUserAgentShadowRoot.html b/LayoutTests/inspector/css/getMatchedStylesForNodeUserAgentShadowRoot.html
new file mode 100644
index 0000000..398164f
--- /dev/null
+++ b/LayoutTests/inspector/css/getMatchedStylesForNodeUserAgentShadowRoot.html
@@ -0,0 +1,73 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script>
+function test()
+{
+ let suite = InspectorTest.createAsyncSuite("CSS.getMatchedStylesForNode.UserAgentShadowRoot");
+
+ function nodeForId(id) {
+ return WI.domManager.nodeForId(id)
+ }
+
+ function addTestCase({name, description, resolveTargetDOMNode, expectedSelectors})
+ {
+ suite.addTestCase({
+ name,
+ description,
+ async test() {
+ let documentNode = await WI.domManager.requestDocument();
+ let domNode = await resolveTargetDOMNode(documentNode);
+ InspectorTest.assert(domNode, `Should find DOM Node.`);
+
+ let domNodeStyles = WI.cssManager.stylesForNode(domNode);
+ await domNodeStyles.refreshIfNeeded();
+
+ let matchedSelectorTexts = [];
+ for (let rule of domNodeStyles.matchedRules) {
+ let selectorText = rule.selectorText
+ matchedSelectorTexts.push(selectorText)
+
+ // A sign of bad parsing from the backend is a disagreement between the individual selectors and the combined selector text.
+ for (let selector of rule.selectors)
+ InspectorTest.assert(selectorText.includes(selector.text), `Should find selector '${selector.text}' in whole selector text '${selectorText}'.`);
+ }
+
+ let matchedSelectors = domNodeStyles.matchedRules.map((rule) => rule.selectorText);
+ for (let expectedSelector of expectedSelectors)
+ InspectorTest.expectThat(matchedSelectorTexts.includes(expectedSelector), `Should have matched rule for selector '${expectedSelector}'`);
+ },
+ });
+ }
+
+ addTestCase({
+ name: "CSS.getMatchedStylesForNode.UserAgentShadowRoot.StyleFollowingNormallyInvalidSelector",
+ description: "Ensure that rules declared after a normaly invalid selector (that is exempted due to being in a UA Shadow Root) are correctly resolved.",
+ async resolveTargetDOMNode(documentNode) {
+ let videoElement = nodeForId(await documentNode.querySelector("#target"));
+ let shadowRoot = videoElement.shadowRoots()[0];
+ return nodeForId(await shadowRoot.querySelector(".time-label"));
+ },
+ expectedSelectors: [
+ ".media-controls .time-label, .media-controls .status-label",
+ ".time-label",
+ ".media-controls-container, .media-controls-container *",
+ ":host(audio), :host(video.media-document.audio), *",
+ "*",
+ ],
+ });
+
+ suite.runTestCasesAndFinish();
+}
+</script>
+<style>
+</style>
+</head>
+<body onload="runTest()">
+ <p>Tests for the CSS.getMatchedStylesForNode command styles inside user-agent shadow roots.</p>
+ <video id="target" loop muted autoplay controls>
+ <source src="../../http/tests/resources/test.mp4" />
+ </video>
+</body>
+</html>
diff --git a/Source/WebCore/inspector/InspectorStyleSheet.cpp b/Source/WebCore/inspector/InspectorStyleSheet.cpp
index 8aff9be..2b4b73f 100644
--- a/Source/WebCore/inspector/InspectorStyleSheet.cpp
+++ b/Source/WebCore/inspector/InspectorStyleSheet.cpp
@@ -1436,6 +1436,12 @@
auto ruleSourceDataResult = makeUnique<RuleSourceDataList>();
CSSParserContext context(parserContextForDocument(m_pageStyleSheet->ownerDocument()));
+
+ // FIXME: <webkit.org/b/161747> Media control CSS uses out-of-spec selectors in inline user agent shadow root style
+ // element. See corresponding workaround in `CSSSelectorParser::extractCompoundFlags`.
+ if (auto* ownerNode = m_pageStyleSheet->ownerNode(); ownerNode && ownerNode->isInUserAgentShadowTree())
+ context.mode = UASheetMode;
+
StyleSheetHandler handler(m_parsedStyleSheet->text(), m_pageStyleSheet->ownerDocument(), ruleSourceDataResult.get());
CSSParser::parseSheetForInspector(context, newStyleSheet.ptr(), m_parsedStyleSheet->text(), handler);
m_parsedStyleSheet->setSourceData(WTFMove(ruleSourceDataResult));