Title: [237608] trunk
Revision
237608
Author
[email protected]
Date
2018-10-30 16:06:23 -0700 (Tue, 30 Oct 2018)

Log Message

Web Inspector: provide options to WI.cssPath for more verbosity
https://bugs.webkit.org/show_bug.cgi?id=190987

Reviewed by Brian Burg.

Source/WebInspectorUI:

* UserInterface/Base/DOMUtilities.js:
(WI.cssPath):
(WI.cssPathComponent):
When the option `full` is true, print every attribute along with every node in the hierarchy
until the root is reached. This partially duplicates the effect of an XPath, but instead
uses CSS selectors, making it much more human readable and recognizable.

LayoutTests:

* inspector/dom/domutilities-csspath.html:

Modified Paths

Diff

Modified: trunk/LayoutTests/ChangeLog (237607 => 237608)


--- trunk/LayoutTests/ChangeLog	2018-10-30 22:45:28 UTC (rev 237607)
+++ trunk/LayoutTests/ChangeLog	2018-10-30 23:06:23 UTC (rev 237608)
@@ -1,3 +1,12 @@
+2018-10-30  Devin Rousso  <[email protected]>
+
+        Web Inspector: provide options to WI.cssPath for more verbosity
+        https://bugs.webkit.org/show_bug.cgi?id=190987
+
+        Reviewed by Brian Burg.
+
+        * inspector/dom/domutilities-csspath.html:
+
 2018-10-30  Ali Juma  <[email protected]>
 
         Calling window.open("", "_self") allows working around restrictions on window.close()

Modified: trunk/LayoutTests/inspector/dom/domutilities-csspath.html (237607 => 237608)


--- trunk/LayoutTests/inspector/dom/domutilities-csspath.html	2018-10-30 22:45:28 UTC (rev 237607)
+++ trunk/LayoutTests/inspector/dom/domutilities-csspath.html	2018-10-30 23:06:23 UTC (rev 237608)
@@ -7,27 +7,41 @@
 {
     let documentNode;
 
-    function nodeForSelector(selector, callback) {
-        WI.domManager.querySelector(documentNode.id, selector, (nodeId) => {
-            callback(WI.domManager.nodeForId(nodeId));
-        });
+    async function nodeForSelector(selector) {
+        let nodeId = await WI.domManager.querySelector(documentNode.id, selector);
+        return WI.domManager.nodeForId(nodeId);
     }
 
+    function testNodeMatchesPath(node, message, {regular, full}) {
+        InspectorTest.expectEqual(WI.cssPath(node), regular, message);
+        if (full) {
+            let actual = WI.cssPath(node, {full: true});
+            InspectorTest.assert(actual === full, `Full path ${actual} doesn't match expected ${full}.`);
+        }
+    }
+
     let suite = InspectorTest.createAsyncSuite("WI.cssPath");
 
     suite.addTestCase({
         name: "WI.cssPath.TopLevelNode",
         description: "Top level nodes like html, body, and head are unique.",
-        test(resolve, reject) {
-            nodeForSelector("html", (node) => {
-                InspectorTest.expectEqual(WI.cssPath(node), "html", "HTML element should have simple selector 'html'.");
+        async test() {
+            let html = await nodeForSelector("html");
+            testNodeMatchesPath(html, "HTML element should have simple selector 'html'.", {
+                regular: `html`,
+                full: `html`,
             });
-            nodeForSelector("html > body", (node) => {
-                InspectorTest.expectEqual(WI.cssPath(node), "body", "BODY element should have simple selector 'body'.");
+
+            let body = await nodeForSelector("html > body");
+            testNodeMatchesPath(body, "BODY element should have simple selector 'body'.", {
+                regular: `body`,
+                full: `html > body[_onload_="runTest()"]`,
             });
-            nodeForSelector("html > head", (node) => {
-                InspectorTest.expectEqual(WI.cssPath(node), "head", "HEAD element should have simple selector 'head'.");
-                resolve();
+
+            let head = await nodeForSelector("html > head");
+            testNodeMatchesPath(head, "HEAD element should have simple selector 'head'.", {
+                regular: `head`,
+                full: `html > head`,
             });
         }
     });
@@ -35,13 +49,17 @@
     suite.addTestCase({
         name: "WI.cssPath.ElementWithID",
         description: "Element with ID is unique (#id). Path does not need to go past it.",
-        test(resolve, reject) {
-            nodeForSelector("#id-test", (node) => {
-                InspectorTest.expectEqual(WI.cssPath(node), "#id-test", "Element with id should have simple selector '#id-test'.");
+        async test() {
+            let test = await nodeForSelector("#id-test");
+            testNodeMatchesPath(test, "Element with id should have simple selector '#id-test'.", {
+                regular: `#id-test`,
+                full: `html > body[_onload_="runTest()"] > div[style="visibility:hidden"] > div#id-test`,
             });
-            nodeForSelector("#id-test > div", (node) => {
-                InspectorTest.expectEqual(WI.cssPath(node), "#id-test > div", "Element inside element with id should have path from id.");
-                resolve();
+
+            let div = await nodeForSelector("#id-test > div");
+            testNodeMatchesPath(div, "Element inside element with id should have path from id.", {
+                regular: `#id-test > div`,
+                full: `html > body[_onload_="runTest()"] > div[style="visibility:hidden"] > div#id-test > div`,
             });
         }
     });
@@ -49,10 +67,11 @@
     suite.addTestCase({
         name: "WI.cssPath.InputElementFlair",
         description: "Input elements include their type.",
-        test(resolve, reject) {
-            nodeForSelector("#input-test input", (node) => {
-                InspectorTest.expectEqual(WI.cssPath(node), "#input-test > input[type=\"password\"]", "Input element should include type.");
-                resolve();
+        async test() {
+            let input = await nodeForSelector("#input-test input");
+            testNodeMatchesPath(input, "Input element should include type.", {
+                regular: `#input-test > input[type="password"]`,
+                full: `html > body[_onload_="runTest()"] > div[style="visibility:hidden"] > div#input-test > input[type="password"]`,
             });
         }
     });
@@ -60,10 +79,11 @@
     suite.addTestCase({
         name: "WI.cssPath.UniqueTagName",
         description: "Elements with unique tag name do not need nth-child.",
-        test(resolve, reject) {
-            nodeForSelector("#unique-tag-test > span", (node) => {
-                InspectorTest.expectEqual(WI.cssPath(node), "#unique-tag-test > span", "Elements with unique tag name should not need nth-child().");
-                resolve();
+        async test() {
+            let span = await nodeForSelector("#unique-tag-test > span");
+            testNodeMatchesPath(span, "Elements with unique tag name should not need nth-child().", {
+                regular: `#unique-tag-test > span`,
+                full: `html > body[_onload_="runTest()"] > div[style="visibility:hidden"] > div#unique-tag-test > span`,
             });
         }
     });
@@ -71,10 +91,11 @@
     suite.addTestCase({
         name: "WI.cssPath.NonUniqueTagName",
         description: "Elements with non-unique tag name need nth-child.",
-        test(resolve, reject) {
-            nodeForSelector("#non-unique-tag-test > span ~ span", (node) => {
-                InspectorTest.expectEqual(WI.cssPath(node), "#non-unique-tag-test > span:nth-child(3)", "Elements with non-unique tag name should need nth-child().");
-                resolve();
+        async test() {
+            let span = await nodeForSelector("#non-unique-tag-test > span ~ span");
+            testNodeMatchesPath(span, "Elements with non-unique tag name should need nth-child().", {
+                regular: `#non-unique-tag-test > span:nth-child(3)`,
+                full: `html > body[_onload_="runTest()"] > div[style="visibility:hidden"] > div#non-unique-tag-test > span:nth-child(3)`,
             });
         }
     });
@@ -82,10 +103,11 @@
     suite.addTestCase({
         name: "WI.cssPath.UniqueClassName",
         description: "Elements with unique class names should include their class names.",
-        test(resolve, reject) {
-            nodeForSelector("#unique-class-test > .beta", (node) => {
-                InspectorTest.expectEqual(WI.cssPath(node), "#unique-class-test > div.alpha.beta", "Elements with unique class names should include their class names.");
-                resolve();
+        async test() {
+            let beta = await nodeForSelector("#unique-class-test > .beta");
+            testNodeMatchesPath(beta, "Elements with unique class names should include their class names.", {
+                regular: `#unique-class-test > div.alpha.beta`,
+                full: `html > body[_onload_="runTest()"] > div[style="visibility:hidden"] > div#unique-class-test > div.alpha.beta`,
             });
         }
     });
@@ -93,10 +115,11 @@
     suite.addTestCase({
         name: "WI.cssPath.NonUniqueClassName",
         description: "Elements with non-unique class names should not include their class names.",
-        test(resolve, reject) {
-            nodeForSelector("#non-unique-class-test > div ~ div", (node) => {
-                InspectorTest.expectEqual(WI.cssPath(node), "#non-unique-class-test > div:nth-child(2)", "Elements with non-unique class names should not include their class names.");
-                resolve();
+        async test() {
+            let div = await nodeForSelector("#non-unique-class-test > div ~ div");
+            testNodeMatchesPath(div, "Elements with non-unique class names should not include their class names.", {
+                regular: `#non-unique-class-test > div:nth-child(2)`,
+                full: `html > body[_onload_="runTest()"] > div[style="visibility:hidden"] > div#non-unique-class-test > div.alpha:nth-child(2)`,
             });
         }
     });
@@ -104,10 +127,11 @@
     suite.addTestCase({
         name: "WI.cssPath.UniqueTagAndClassName",
         description: "Elements with unique tag and class name just use tag for simplicity.",
-        test(resolve, reject) {
-            nodeForSelector("#unique-tag-and-class-test > .alpha", (node) => {
-                InspectorTest.expectEqual(WI.cssPath(node), "#unique-tag-and-class-test > div", "Elements with unique tag and class names should just have simple tag.");
-                resolve();
+        async test() {
+            let alpha = await nodeForSelector("#unique-tag-and-class-test > .alpha");
+            testNodeMatchesPath(alpha, "Elements with unique tag and class names should just have simple tag.", {
+                regular: `#unique-tag-and-class-test > div`,
+                full: `html > body[_onload_="runTest()"] > div[style="visibility:hidden"] > div#unique-tag-and-class-test > div.alpha`,
             });
         }
     });
@@ -115,10 +139,11 @@
     suite.addTestCase({
         name: "WI.cssPath.DeepPath",
         description: "Tests for element with complex path.",
-        test(resolve, reject) {
-            nodeForSelector("small", (node) => {
-                InspectorTest.expectEqual(WI.cssPath(node), "body > div > div.deep-path-test > ul > li > div:nth-child(4) > ul > li.active > a > small", "Should be able to create path for deep elements.");
-                resolve();
+        async test() {
+            let small = await nodeForSelector("small");
+            testNodeMatchesPath(small, "Should be able to create path for deep elements.", {
+                regular: `body > div > div.deep-path-test > ul > li > div:nth-child(4) > ul > li.active > a > small`,
+                full: `html > body[_onload_="runTest()"] > div[style="visibility:hidden"] > div.deep-path-test > ul > li > div:nth-child(4) > ul.list > li.active > a[href="" > small`,
             });
         }
     });
@@ -126,16 +151,22 @@
     suite.addTestCase({
         name: "WI.cssPath.PseudoElement",
         description: "For a pseudo element we should get the path of the parent and append the pseudo element selector.",
-        test(resolve, reject) {
-            nodeForSelector("#pseudo-element-test > div ~ div", (node) => {
-                let pseudoElementBefore = node.beforePseudoElement();
-                InspectorTest.assert(pseudoElementBefore);
-                InspectorTest.expectEqual(WI.cssPath(pseudoElementBefore), "#pseudo-element-test > div:nth-child(3)::before", "Should be able to create path for ::before pseudo elements.");
-                let pseudoElementAfter = node.afterPseudoElement();
-                InspectorTest.assert(pseudoElementAfter);
-                InspectorTest.expectEqual(WI.cssPath(pseudoElementAfter), "#pseudo-element-test > div:nth-child(3)::after", "Should be able to create path for ::after pseudo elements.");
-                resolve();
+        async test() {
+            let div = await nodeForSelector("#pseudo-element-test > div ~ div");
+
+            let pseudoElementBefore = div.beforePseudoElement();
+            InspectorTest.assert(pseudoElementBefore);
+            testNodeMatchesPath(pseudoElementBefore, "Should be able to create path for ::before pseudo elements.", {
+                regular: `#pseudo-element-test > div:nth-child(3)::before`,
+                full: `html > body[_onload_="runTest()"] > div[style="visibility:hidden"] > div#pseudo-element-test > div:nth-child(3)::before`,
             });
+
+            let pseudoElementAfter = div.afterPseudoElement();
+            InspectorTest.assert(pseudoElementAfter);
+            testNodeMatchesPath(pseudoElementAfter, "Should be able to create path for ::after pseudo elements.", {
+                regular: `#pseudo-element-test > div:nth-child(3)::after`,
+                full: `html > body[_onload_="runTest()"] > div[style="visibility:hidden"] > div#pseudo-element-test > div:nth-child(3)::after`,
+            });
         }
     });
 

Modified: trunk/Source/WebInspectorUI/ChangeLog (237607 => 237608)


--- trunk/Source/WebInspectorUI/ChangeLog	2018-10-30 22:45:28 UTC (rev 237607)
+++ trunk/Source/WebInspectorUI/ChangeLog	2018-10-30 23:06:23 UTC (rev 237608)
@@ -1,5 +1,19 @@
 2018-10-30  Devin Rousso  <[email protected]>
 
+        Web Inspector: provide options to WI.cssPath for more verbosity
+        https://bugs.webkit.org/show_bug.cgi?id=190987
+
+        Reviewed by Brian Burg.
+
+        * UserInterface/Base/DOMUtilities.js:
+        (WI.cssPath):
+        (WI.cssPathComponent):
+        When the option `full` is true, print every attribute along with every node in the hierarchy
+        until the root is reached. This partially duplicates the effect of an XPath, but instead
+        uses CSS selectors, making it much more human readable and recognizable.
+
+2018-10-30  Devin Rousso  <[email protected]>
+
         Web Inspector: change WI.ColorWheel to use conic-gradient()
         https://bugs.webkit.org/show_bug.cgi?id=189485
 

Modified: trunk/Source/WebInspectorUI/UserInterface/Base/DOMUtilities.js (237607 => 237608)


--- trunk/Source/WebInspectorUI/UserInterface/Base/DOMUtilities.js	2018-10-30 22:45:28 UTC (rev 237607)
+++ trunk/Source/WebInspectorUI/UserInterface/Base/DOMUtilities.js	2018-10-30 23:06:23 UTC (rev 237608)
@@ -93,7 +93,7 @@
     return document.createElementNS("http://www.w3.org/2000/svg", tagName);
 }
 
-WI.cssPath = function(node)
+WI.cssPath = function(node, options = {})
 {
     console.assert(node instanceof WI.DOMNode, "Expected a DOMNode.");
     if (node.nodeType() !== Node.ELEMENT_NODE)
@@ -107,7 +107,7 @@
 
     let components = [];
     while (node) {
-        let component = WI.cssPathComponent(node);
+        let component = WI.cssPathComponent(node, options);
         if (!component)
             break;
         components.push(component);
@@ -120,7 +120,7 @@
     return components.map((x) => x.value).join(" > ") + suffix;
 };
 
-WI.cssPathComponent = function(node)
+WI.cssPathComponent = function(node, options = {})
 {
     console.assert(node instanceof WI.DOMNode, "Expected a DOMNode.");
     console.assert(!node.isPseudoElement());
@@ -128,6 +128,75 @@
         return null;
 
     let nodeName = node.nodeNameInCorrectCase();
+
+    // Root node does not have siblings.
+    if (!node.parentNode || node.parentNode.nodeType() === Node.DOCUMENT_NODE)
+        return {value: nodeName, done: true};
+
+    if (options.full) {
+        function getUniqueAttributes(domNode) {
+            let uniqueAttributes = new Map;
+            for (let attribute of domNode.attributes()) {
+                let values = [attribute.value];
+                if (attribute.name === "id" || attribute.name === "class")
+                    values = attribute.value.split(/\s+/);
+                uniqueAttributes.set(attribute.name, new Set(values));
+            }
+            return uniqueAttributes;
+        }
+
+        let nodeIndex = 0;
+        let needsNthChild = false;
+        let uniqueAttributes = getUniqueAttributes(node);
+        node.parentNode.children.forEach((child, i) => {
+            if (child.nodeType() !== Node.ELEMENT_NODE)
+                return;
+
+            if (child === node) {
+                nodeIndex = i;
+                return;
+            }
+
+            if (needsNthChild || child.nodeNameInCorrectCase() !== nodeName)
+                return;
+
+            let childUniqueAttributes = getUniqueAttributes(child);
+            let subsetCount = 0;
+            for (let [name, values] of uniqueAttributes) {
+                let childValues = childUniqueAttributes.get(name);
+                if (childValues && values.size <= childValues.size && values.isSubsetOf(childValues))
+                    ++subsetCount;
+            }
+
+            if (subsetCount === uniqueAttributes.size)
+                needsNthChild = true;
+        });
+
+        function selectorForAttribute(values, prefix = "", shouldCSSEscape = false) {
+            if (!values || !values.size)
+                return "";
+            values = Array.from(values);
+            values = values.filter((value) => value && value.length);
+            if (!values.length)
+                return "";
+            values = values.map((value) => shouldCSSEscape ? CSS.escape(value) : value.escapeCharacters("\""));
+            return prefix + values.join(prefix);
+        }
+
+        let selector = nodeName;
+        selector += selectorForAttribute(uniqueAttributes.get("id"), "#", true);
+        selector += selectorForAttribute(uniqueAttributes.get("class"), ".", true);
+        for (let [attribute, values] of uniqueAttributes) {
+            if (attribute !== "id" && attribute !== "class")
+                selector += `[${attribute}="${selectorForAttribute(values)}"]`;
+        }
+
+        if (needsNthChild)
+            selector += `:nth-child(${nodeIndex + 1})`;
+
+        return {value: selector, done: false};
+    }
+
     let lowerNodeName = node.nodeName().toLowerCase();
 
     // html, head, and body are unique nodes.
@@ -139,10 +208,6 @@
     if (id)
         return {value: node.escapedIdSelector, done: true};
 
-    // Root node does not have siblings.
-    if (!node.parentNode || node.parentNode.nodeType() === Node.DOCUMENT_NODE)
-        return {value: nodeName, done: true};
-
     // Find uniqueness among siblings.
     //   - look for a unique className
     //   - look for a unique tagName
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to