<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head><meta http-equiv="content-type" content="text/html; charset=utf-8" />
<title>[237608] trunk</title>
</head>
<body>

<style type="text/css"><!--
#msg dl.meta { border: 1px #006 solid; background: #369; padding: 6px; color: #fff; }
#msg dl.meta dt { float: left; width: 6em; font-weight: bold; }
#msg dt:after { content:':';}
#msg dl, #msg dt, #msg ul, #msg li, #header, #footer, #logmsg { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt;  }
#msg dl a { font-weight: bold}
#msg dl a:link    { color:#fc3; }
#msg dl a:active  { color:#ff0; }
#msg dl a:visited { color:#cc6; }
h3 { font-family: verdana,arial,helvetica,sans-serif; font-size: 10pt; font-weight: bold; }
#msg pre { overflow: auto; background: #ffc; border: 1px #fa0 solid; padding: 6px; }
#logmsg { background: #ffc; border: 1px #fa0 solid; padding: 1em 1em 0 1em; }
#logmsg p, #logmsg pre, #logmsg blockquote { margin: 0 0 1em 0; }
#logmsg p, #logmsg li, #logmsg dt, #logmsg dd { line-height: 14pt; }
#logmsg h1, #logmsg h2, #logmsg h3, #logmsg h4, #logmsg h5, #logmsg h6 { margin: .5em 0; }
#logmsg h1:first-child, #logmsg h2:first-child, #logmsg h3:first-child, #logmsg h4:first-child, #logmsg h5:first-child, #logmsg h6:first-child { margin-top: 0; }
#logmsg ul, #logmsg ol { padding: 0; list-style-position: inside; margin: 0 0 0 1em; }
#logmsg ul { text-indent: -1em; padding-left: 1em; }#logmsg ol { text-indent: -1.5em; padding-left: 1.5em; }
#logmsg > ul, #logmsg > ol { margin: 0 0 1em 0; }
#logmsg pre { background: #eee; padding: 1em; }
#logmsg blockquote { border: 1px solid #fa0; border-left-width: 10px; padding: 1em 1em 0 1em; background: white;}
#logmsg dl { margin: 0; }
#logmsg dt { font-weight: bold; }
#logmsg dd { margin: 0; padding: 0 0 0.5em 0; }
#logmsg dd:before { content:'\00bb';}
#logmsg table { border-spacing: 0px; border-collapse: collapse; border-top: 4px solid #fa0; border-bottom: 1px solid #fa0; background: #fff; }
#logmsg table th { text-align: left; font-weight: normal; padding: 0.2em 0.5em; border-top: 1px dotted #fa0; }
#logmsg table td { text-align: right; border-top: 1px dotted #fa0; padding: 0.2em 0.5em; }
#logmsg table thead th { text-align: center; border-bottom: 1px solid #fa0; }
#logmsg table th.Corner { text-align: left; }
#logmsg hr { border: none 0; border-top: 2px dashed #fa0; height: 1px; }
#header, #footer { color: #fff; background: #636; border: 1px #300 solid; padding: 6px; }
#patch { width: 100%; }
#patch h4 {font-family: verdana,arial,helvetica,sans-serif;font-size:10pt;padding:8px;background:#369;color:#fff;margin:0;}
#patch .propset h4, #patch .binary h4 {margin:0;}
#patch pre {padding:0;line-height:1.2em;margin:0;}
#patch .diff {width:100%;background:#eee;padding: 0 0 10px 0;overflow:auto;}
#patch .propset .diff, #patch .binary .diff  {padding:10px 0;}
#patch span {display:block;padding:0 10px;}
#patch .modfile, #patch .addfile, #patch .delfile, #patch .propset, #patch .binary, #patch .copfile {border:1px solid #ccc;margin:10px 0;}
#patch ins {background:#dfd;text-decoration:none;display:block;padding:0 10px;}
#patch del {background:#fdd;text-decoration:none;display:block;padding:0 10px;}
#patch .lines, .info {color:#888;background:#fff;}
--></style>
<div id="msg">
<dl class="meta">
<dt>Revision</dt> <dd><a href="http://trac.webkit.org/projects/webkit/changeset/237608">237608</a></dd>
<dt>Author</dt> <dd>drousso@apple.com</dd>
<dt>Date</dt> <dd>2018-10-30 16:06:23 -0700 (Tue, 30 Oct 2018)</dd>
</dl>

<h3>Log Message</h3>
<pre>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:</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsChangeLog">trunk/LayoutTests/ChangeLog</a></li>
<li><a href="#trunkLayoutTestsinspectordomdomutilitiescsspathhtml">trunk/LayoutTests/inspector/dom/domutilities-csspath.html</a></li>
<li><a href="#trunkSourceWebInspectorUIChangeLog">trunk/Source/WebInspectorUI/ChangeLog</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceBaseDOMUtilitiesjs">trunk/Source/WebInspectorUI/UserInterface/Base/DOMUtilities.js</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkLayoutTestsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/ChangeLog (237607 => 237608)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/ChangeLog      2018-10-30 22:45:28 UTC (rev 237607)
+++ trunk/LayoutTests/ChangeLog 2018-10-30 23:06:23 UTC (rev 237608)
</span><span class="lines">@@ -1,3 +1,12 @@
</span><ins>+2018-10-30  Devin Rousso  <drousso@apple.com>
+
+        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:
+
</ins><span class="cx"> 2018-10-30  Ali Juma  <ajuma@chromium.org>
</span><span class="cx"> 
</span><span class="cx">         Calling window.open("", "_self") allows working around restrictions on window.close()
</span></span></pre></div>
<a id="trunkLayoutTestsinspectordomdomutilitiescsspathhtml"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/inspector/dom/domutilities-csspath.html (237607 => 237608)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -7,27 +7,41 @@
</span><span class="cx"> {
</span><span class="cx">     let documentNode;
</span><span class="cx"> 
</span><del>-    function nodeForSelector(selector, callback) {
-        WI.domManager.querySelector(documentNode.id, selector, (nodeId) => {
-            callback(WI.domManager.nodeForId(nodeId));
-        });
</del><ins>+    async function nodeForSelector(selector) {
+        let nodeId = await WI.domManager.querySelector(documentNode.id, selector);
+        return WI.domManager.nodeForId(nodeId);
</ins><span class="cx">     }
</span><span class="cx"> 
</span><ins>+    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}.`);
+        }
+    }
+
</ins><span class="cx">     let suite = InspectorTest.createAsyncSuite("WI.cssPath");
</span><span class="cx"> 
</span><span class="cx">     suite.addTestCase({
</span><span class="cx">         name: "WI.cssPath.TopLevelNode",
</span><span class="cx">         description: "Top level nodes like html, body, and head are unique.",
</span><del>-        test(resolve, reject) {
-            nodeForSelector("html", (node) => {
-                InspectorTest.expectEqual(WI.cssPath(node), "html", "HTML element should have simple selector 'html'.");
</del><ins>+        async test() {
+            let html = await nodeForSelector("html");
+            testNodeMatchesPath(html, "HTML element should have simple selector 'html'.", {
+                regular: `html`,
+                full: `html`,
</ins><span class="cx">             });
</span><del>-            nodeForSelector("html > body", (node) => {
-                InspectorTest.expectEqual(WI.cssPath(node), "body", "BODY element should have simple selector 'body'.");
</del><ins>+
+            let body = await nodeForSelector("html > body");
+            testNodeMatchesPath(body, "BODY element should have simple selector 'body'.", {
+                regular: `body`,
+                full: `html > body[onload="runTest()"]`,
</ins><span class="cx">             });
</span><del>-            nodeForSelector("html > head", (node) => {
-                InspectorTest.expectEqual(WI.cssPath(node), "head", "HEAD element should have simple selector 'head'.");
-                resolve();
</del><ins>+
+            let head = await nodeForSelector("html > head");
+            testNodeMatchesPath(head, "HEAD element should have simple selector 'head'.", {
+                regular: `head`,
+                full: `html > head`,
</ins><span class="cx">             });
</span><span class="cx">         }
</span><span class="cx">     });
</span><span class="lines">@@ -35,13 +49,17 @@
</span><span class="cx">     suite.addTestCase({
</span><span class="cx">         name: "WI.cssPath.ElementWithID",
</span><span class="cx">         description: "Element with ID is unique (#id). Path does not need to go past it.",
</span><del>-        test(resolve, reject) {
-            nodeForSelector("#id-test", (node) => {
-                InspectorTest.expectEqual(WI.cssPath(node), "#id-test", "Element with id should have simple selector '#id-test'.");
</del><ins>+        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`,
</ins><span class="cx">             });
</span><del>-            nodeForSelector("#id-test > div", (node) => {
-                InspectorTest.expectEqual(WI.cssPath(node), "#id-test > div", "Element inside element with id should have path from id.");
-                resolve();
</del><ins>+
+            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`,
</ins><span class="cx">             });
</span><span class="cx">         }
</span><span class="cx">     });
</span><span class="lines">@@ -49,10 +67,11 @@
</span><span class="cx">     suite.addTestCase({
</span><span class="cx">         name: "WI.cssPath.InputElementFlair",
</span><span class="cx">         description: "Input elements include their type.",
</span><del>-        test(resolve, reject) {
-            nodeForSelector("#input-test input", (node) => {
-                InspectorTest.expectEqual(WI.cssPath(node), "#input-test > input[type=\"password\"]", "Input element should include type.");
-                resolve();
</del><ins>+        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"]`,
</ins><span class="cx">             });
</span><span class="cx">         }
</span><span class="cx">     });
</span><span class="lines">@@ -60,10 +79,11 @@
</span><span class="cx">     suite.addTestCase({
</span><span class="cx">         name: "WI.cssPath.UniqueTagName",
</span><span class="cx">         description: "Elements with unique tag name do not need nth-child.",
</span><del>-        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();
</del><ins>+        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`,
</ins><span class="cx">             });
</span><span class="cx">         }
</span><span class="cx">     });
</span><span class="lines">@@ -71,10 +91,11 @@
</span><span class="cx">     suite.addTestCase({
</span><span class="cx">         name: "WI.cssPath.NonUniqueTagName",
</span><span class="cx">         description: "Elements with non-unique tag name need nth-child.",
</span><del>-        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();
</del><ins>+        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)`,
</ins><span class="cx">             });
</span><span class="cx">         }
</span><span class="cx">     });
</span><span class="lines">@@ -82,10 +103,11 @@
</span><span class="cx">     suite.addTestCase({
</span><span class="cx">         name: "WI.cssPath.UniqueClassName",
</span><span class="cx">         description: "Elements with unique class names should include their class names.",
</span><del>-        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();
</del><ins>+        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`,
</ins><span class="cx">             });
</span><span class="cx">         }
</span><span class="cx">     });
</span><span class="lines">@@ -93,10 +115,11 @@
</span><span class="cx">     suite.addTestCase({
</span><span class="cx">         name: "WI.cssPath.NonUniqueClassName",
</span><span class="cx">         description: "Elements with non-unique class names should not include their class names.",
</span><del>-        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();
</del><ins>+        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)`,
</ins><span class="cx">             });
</span><span class="cx">         }
</span><span class="cx">     });
</span><span class="lines">@@ -104,10 +127,11 @@
</span><span class="cx">     suite.addTestCase({
</span><span class="cx">         name: "WI.cssPath.UniqueTagAndClassName",
</span><span class="cx">         description: "Elements with unique tag and class name just use tag for simplicity.",
</span><del>-        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();
</del><ins>+        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`,
</ins><span class="cx">             });
</span><span class="cx">         }
</span><span class="cx">     });
</span><span class="lines">@@ -115,10 +139,11 @@
</span><span class="cx">     suite.addTestCase({
</span><span class="cx">         name: "WI.cssPath.DeepPath",
</span><span class="cx">         description: "Tests for element with complex path.",
</span><del>-        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();
</del><ins>+        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`,
</ins><span class="cx">             });
</span><span class="cx">         }
</span><span class="cx">     });
</span><span class="lines">@@ -126,16 +151,22 @@
</span><span class="cx">     suite.addTestCase({
</span><span class="cx">         name: "WI.cssPath.PseudoElement",
</span><span class="cx">         description: "For a pseudo element we should get the path of the parent and append the pseudo element selector.",
</span><del>-        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();
</del><ins>+        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`,
</ins><span class="cx">             });
</span><ins>+
+            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`,
+            });
</ins><span class="cx">         }
</span><span class="cx">     });
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/ChangeLog (237607 => 237608)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -1,5 +1,19 @@
</span><span class="cx"> 2018-10-30  Devin Rousso  <drousso@apple.com>
</span><span class="cx"> 
</span><ins>+        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  <drousso@apple.com>
+
</ins><span class="cx">         Web Inspector: change WI.ColorWheel to use conic-gradient()
</span><span class="cx">         https://bugs.webkit.org/show_bug.cgi?id=189485
</span><span class="cx"> 
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceBaseDOMUtilitiesjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Base/DOMUtilities.js (237607 => 237608)</h4>
<pre class="diff"><span>
<span class="info">--- 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)
</span><span class="lines">@@ -93,7 +93,7 @@
</span><span class="cx">     return document.createElementNS("http://www.w3.org/2000/svg", tagName);
</span><span class="cx"> }
</span><span class="cx"> 
</span><del>-WI.cssPath = function(node)
</del><ins>+WI.cssPath = function(node, options = {})
</ins><span class="cx"> {
</span><span class="cx">     console.assert(node instanceof WI.DOMNode, "Expected a DOMNode.");
</span><span class="cx">     if (node.nodeType() !== Node.ELEMENT_NODE)
</span><span class="lines">@@ -107,7 +107,7 @@
</span><span class="cx"> 
</span><span class="cx">     let components = [];
</span><span class="cx">     while (node) {
</span><del>-        let component = WI.cssPathComponent(node);
</del><ins>+        let component = WI.cssPathComponent(node, options);
</ins><span class="cx">         if (!component)
</span><span class="cx">             break;
</span><span class="cx">         components.push(component);
</span><span class="lines">@@ -120,7 +120,7 @@
</span><span class="cx">     return components.map((x) => x.value).join(" > ") + suffix;
</span><span class="cx"> };
</span><span class="cx"> 
</span><del>-WI.cssPathComponent = function(node)
</del><ins>+WI.cssPathComponent = function(node, options = {})
</ins><span class="cx"> {
</span><span class="cx">     console.assert(node instanceof WI.DOMNode, "Expected a DOMNode.");
</span><span class="cx">     console.assert(!node.isPseudoElement());
</span><span class="lines">@@ -128,6 +128,75 @@
</span><span class="cx">         return null;
</span><span class="cx"> 
</span><span class="cx">     let nodeName = node.nodeNameInCorrectCase();
</span><ins>+
+    // 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};
+    }
+
</ins><span class="cx">     let lowerNodeName = node.nodeName().toLowerCase();
</span><span class="cx"> 
</span><span class="cx">     // html, head, and body are unique nodes.
</span><span class="lines">@@ -139,10 +208,6 @@
</span><span class="cx">     if (id)
</span><span class="cx">         return {value: node.escapedIdSelector, done: true};
</span><span class="cx"> 
</span><del>-    // Root node does not have siblings.
-    if (!node.parentNode || node.parentNode.nodeType() === Node.DOCUMENT_NODE)
-        return {value: nodeName, done: true};
-
</del><span class="cx">     // Find uniqueness among siblings.
</span><span class="cx">     //   - look for a unique className
</span><span class="cx">     //   - look for a unique tagName
</span></span></pre>
</div>
</div>

</body>
</html>