<!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>[206059] 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/206059">206059</a></dd>
<dt>Author</dt> <dd>joepeck@webkit.org</dd>
<dt>Date</dt> <dd>2016-09-16 19:14:53 -0700 (Fri, 16 Sep 2016)</dd>
</dl>

<h3>Log Message</h3>
<pre>Web Inspector: Implement Copy CSS Selector and Copy Xpath Selector context menus
https://bugs.webkit.org/show_bug.cgi?id=158881
&lt;rdar://problem/8181156&gt;

Reviewed by Matt Baker.

Source/WebInspectorUI:

This is based off of the Blink implementation (DOMPresentationUtils)
with some minor modifications and using our own utility methods.

* Localizations/en.lproj/localizedStrings.js:
New context menu strings.

* UserInterface/Base/DOMUtilities.js:
(WebInspector.cssPath):
(WebInspector.cssPathComponent.classNames):
(WebInspector.cssPathComponent):
(WebInspector.xpath):
(WebInspector.xpathIndex.isSimiliarNode):
(WebInspector.xpathIndex):
Build strings for a CSS selector path or XPath path to a node.

* UserInterface/Views/DOMTreeElement.js:
(WebInspector.DOMTreeElement.prototype._populateNodeContextMenu):
* UserInterface/Views/DOMTreeOutline.js:
(WebInspector.DOMTreeOutline.prototype.populateContextMenu):
Include copy path context menu items on nodes.
Pseudo elements do not get Copy XPath.
Non-node elements do not get Copy Selector Path.

LayoutTests:

* inspector/dom/domutilities-csspath-expected.txt: Added.
* inspector/dom/domutilities-csspath.html: Added.
* inspector/dom/domutilities-path-dump-expected.txt: Added.
* inspector/dom/domutilities-path-dump.html: Added.
* inspector/dom/domutilities-xpath-expected.txt: Added.
* inspector/dom/domutilities-xpath.html: Added.</pre>

<h3>Modified Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsChangeLog">trunk/LayoutTests/ChangeLog</a></li>
<li><a href="#trunkSourceWebInspectorUIChangeLog">trunk/Source/WebInspectorUI/ChangeLog</a></li>
<li><a href="#trunkSourceWebInspectorUILocalizationsenlprojlocalizedStringsjs">trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceBaseDOMUtilitiesjs">trunk/Source/WebInspectorUI/UserInterface/Base/DOMUtilities.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsDOMTreeElementjs">trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js</a></li>
<li><a href="#trunkSourceWebInspectorUIUserInterfaceViewsDOMTreeOutlinejs">trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.js</a></li>
</ul>

<h3>Added Paths</h3>
<ul>
<li><a href="#trunkLayoutTestsinspectordomdomutilitiescsspathexpectedtxt">trunk/LayoutTests/inspector/dom/domutilities-csspath-expected.txt</a></li>
<li><a href="#trunkLayoutTestsinspectordomdomutilitiescsspathhtml">trunk/LayoutTests/inspector/dom/domutilities-csspath.html</a></li>
<li><a href="#trunkLayoutTestsinspectordomdomutilitiespathdumpexpectedtxt">trunk/LayoutTests/inspector/dom/domutilities-path-dump-expected.txt</a></li>
<li><a href="#trunkLayoutTestsinspectordomdomutilitiespathdumphtml">trunk/LayoutTests/inspector/dom/domutilities-path-dump.html</a></li>
<li><a href="#trunkLayoutTestsinspectordomdomutilitiesxpathexpectedtxt">trunk/LayoutTests/inspector/dom/domutilities-xpath-expected.txt</a></li>
<li><a href="#trunkLayoutTestsinspectordomdomutilitiesxpathhtml">trunk/LayoutTests/inspector/dom/domutilities-xpath.html</a></li>
</ul>

</div>
<div id="patch">
<h3>Diff</h3>
<a id="trunkLayoutTestsChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/LayoutTests/ChangeLog (206058 => 206059)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/ChangeLog        2016-09-17 00:43:55 UTC (rev 206058)
+++ trunk/LayoutTests/ChangeLog        2016-09-17 02:14:53 UTC (rev 206059)
</span><span class="lines">@@ -1,3 +1,18 @@
</span><ins>+2016-09-16  Joseph Pecoraro  &lt;pecoraro@apple.com&gt;
+
+        Web Inspector: Implement Copy CSS Selector and Copy Xpath Selector context menus
+        https://bugs.webkit.org/show_bug.cgi?id=158881
+        &lt;rdar://problem/8181156&gt;
+
+        Reviewed by Matt Baker.
+
+        * inspector/dom/domutilities-csspath-expected.txt: Added.
+        * inspector/dom/domutilities-csspath.html: Added.
+        * inspector/dom/domutilities-path-dump-expected.txt: Added.
+        * inspector/dom/domutilities-path-dump.html: Added.
+        * inspector/dom/domutilities-xpath-expected.txt: Added.
+        * inspector/dom/domutilities-xpath.html: Added.
+
</ins><span class="cx"> 2016-09-16  Jer Noble  &lt;jer.noble@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         Unreviewed gardening; enable newly passing media/media-source/ tests.
</span></span></pre></div>
<a id="trunkLayoutTestsinspectordomdomutilitiescsspathexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/inspector/dom/domutilities-csspath-expected.txt (0 => 206059)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/dom/domutilities-csspath-expected.txt                                (rev 0)
+++ trunk/LayoutTests/inspector/dom/domutilities-csspath-expected.txt        2016-09-17 02:14:53 UTC (rev 206059)
</span><span class="lines">@@ -0,0 +1,38 @@
</span><ins>+Test for WebInspector.cssPath.
+
+
+== Running test suite: WebInspector.cssPath
+-- Running test case: WebInspector.cssPath.TopLevelNode
+PASS: HTML element should have simple selector 'html'.
+PASS: BODY element should have simple selector 'body'.
+PASS: HEAD element should have simple selector 'head'.
+
+-- Running test case: WebInspector.cssPath.ElementWithID
+PASS: Element with id should have simple selector '#id-test'.
+PASS: Element inside element with id should have path from id.
+
+-- Running test case: WebInspector.cssPath.InputElementFlair
+PASS: Input element should include type.
+
+-- Running test case: WebInspector.cssPath.UniqueTagName
+PASS: Elements with unique tag name should not need nth-child().
+
+-- Running test case: WebInspector.cssPath.NonUniqueTagName
+PASS: Elements with non-unique tag name should need nth-child().
+
+-- Running test case: WebInspector.cssPath.UniqueClassName
+PASS: Elements with unique class names should include their class names.
+
+-- Running test case: WebInspector.cssPath.NonUniqueClassName
+PASS: Elements with non-unique class names should not include their class names.
+
+-- Running test case: WebInspector.cssPath.UniqueTagAndClassName
+PASS: Elements with unique tag and class names should just have simple tag.
+
+-- Running test case: WebInspector.cssPath.DeepPath
+PASS: Should be able to create path for deep elements.
+
+-- Running test case: WebInspector.cssPath.PseudoElement
+PASS: Should be able to create path for ::before pseudo elements.
+PASS: Should be able to create path for ::after pseudo elements.
+
</ins></span></pre></div>
<a id="trunkLayoutTestsinspectordomdomutilitiescsspathhtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/inspector/dom/domutilities-csspath.html (0 => 206059)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/dom/domutilities-csspath.html                                (rev 0)
+++ trunk/LayoutTests/inspector/dom/domutilities-csspath.html        2016-09-17 02:14:53 UTC (rev 206059)
</span><span class="lines">@@ -0,0 +1,211 @@
</span><ins>+&lt;!DOCTYPE html&gt;
+&lt;html&gt;
+&lt;head&gt;
+&lt;script src=&quot;../../http/tests/inspector/resources/inspector-test.js&quot;&gt;&lt;/script&gt;
+&lt;script&gt;
+function test()
+{
+    let documentNode;
+
+    function nodeForSelector(selector, callback) {
+        WebInspector.domTreeManager.querySelector(documentNode.id, selector, (nodeId) =&gt; {
+            callback(WebInspector.domTreeManager.nodeForId(nodeId));
+        });
+    }
+
+    let suite = InspectorTest.createAsyncSuite(&quot;WebInspector.cssPath&quot;);
+
+    suite.addTestCase({
+        name: &quot;WebInspector.cssPath.TopLevelNode&quot;,
+        description: &quot;Top level nodes like html, body, and head are unique.&quot;,
+        test(resolve, reject) {
+            nodeForSelector(&quot;html&quot;, (node) =&gt; {
+                InspectorTest.expectEqual(WebInspector.cssPath(node), &quot;html&quot;, &quot;HTML element should have simple selector 'html'.&quot;);
+            });
+            nodeForSelector(&quot;html &gt; body&quot;, (node) =&gt; {
+                InspectorTest.expectEqual(WebInspector.cssPath(node), &quot;body&quot;, &quot;BODY element should have simple selector 'body'.&quot;);
+            });
+            nodeForSelector(&quot;html &gt; head&quot;, (node) =&gt; {
+                InspectorTest.expectEqual(WebInspector.cssPath(node), &quot;head&quot;, &quot;HEAD element should have simple selector 'head'.&quot;);
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: &quot;WebInspector.cssPath.ElementWithID&quot;,
+        description: &quot;Element with ID is unique (#id). Path does not need to go past it.&quot;,
+        test(resolve, reject) {
+            nodeForSelector(&quot;#id-test&quot;, (node) =&gt; {
+                InspectorTest.expectEqual(WebInspector.cssPath(node), &quot;#id-test&quot;, &quot;Element with id should have simple selector '#id-test'.&quot;);
+            });
+            nodeForSelector(&quot;#id-test &gt; div&quot;, (node) =&gt; {
+                InspectorTest.expectEqual(WebInspector.cssPath(node), &quot;#id-test &gt; div&quot;, &quot;Element inside element with id should have path from id.&quot;);
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: &quot;WebInspector.cssPath.InputElementFlair&quot;,
+        description: &quot;Input elements include their type.&quot;,
+        test(resolve, reject) {
+            nodeForSelector(&quot;#input-test input&quot;, (node) =&gt; {
+                InspectorTest.expectEqual(WebInspector.cssPath(node), &quot;#input-test &gt; input[type=\&quot;password\&quot;]&quot;, &quot;Input element should include type.&quot;);
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: &quot;WebInspector.cssPath.UniqueTagName&quot;,
+        description: &quot;Elements with unique tag name do not need nth-child.&quot;,
+        test(resolve, reject) {
+            nodeForSelector(&quot;#unique-tag-test &gt; span&quot;, (node) =&gt; {
+                InspectorTest.expectEqual(WebInspector.cssPath(node), &quot;#unique-tag-test &gt; span&quot;, &quot;Elements with unique tag name should not need nth-child().&quot;);
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: &quot;WebInspector.cssPath.NonUniqueTagName&quot;,
+        description: &quot;Elements with non-unique tag name need nth-child.&quot;,
+        test(resolve, reject) {
+            nodeForSelector(&quot;#non-unique-tag-test &gt; span ~ span&quot;, (node) =&gt; {
+                InspectorTest.expectEqual(WebInspector.cssPath(node), &quot;#non-unique-tag-test &gt; span:nth-child(3)&quot;, &quot;Elements with non-unique tag name should need nth-child().&quot;);
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: &quot;WebInspector.cssPath.UniqueClassName&quot;,
+        description: &quot;Elements with unique class names should include their class names.&quot;,
+        test(resolve, reject) {
+            nodeForSelector(&quot;#unique-class-test &gt; .beta&quot;, (node) =&gt; {
+                InspectorTest.expectEqual(WebInspector.cssPath(node), &quot;#unique-class-test &gt; div.alpha.beta&quot;, &quot;Elements with unique class names should include their class names.&quot;);
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: &quot;WebInspector.cssPath.NonUniqueClassName&quot;,
+        description: &quot;Elements with non-unique class names should not include their class names.&quot;,
+        test(resolve, reject) {
+            nodeForSelector(&quot;#non-unique-class-test &gt; div ~ div&quot;, (node) =&gt; {
+                InspectorTest.expectEqual(WebInspector.cssPath(node), &quot;#non-unique-class-test &gt; div:nth-child(2)&quot;, &quot;Elements with non-unique class names should not include their class names.&quot;);
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: &quot;WebInspector.cssPath.UniqueTagAndClassName&quot;,
+        description: &quot;Elements with unique tag and class name just use tag for simplicity.&quot;,
+        test(resolve, reject) {
+            nodeForSelector(&quot;#unique-tag-and-class-test &gt; .alpha&quot;, (node) =&gt; {
+                InspectorTest.expectEqual(WebInspector.cssPath(node), &quot;#unique-tag-and-class-test &gt; div&quot;, &quot;Elements with unique tag and class names should just have simple tag.&quot;);
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: &quot;WebInspector.cssPath.DeepPath&quot;,
+        description: &quot;Tests for element with complex path.&quot;,
+        test(resolve, reject) {
+            nodeForSelector(&quot;small&quot;, (node) =&gt; {
+                InspectorTest.expectEqual(WebInspector.cssPath(node), &quot;body &gt; div &gt; div.deep-path-test &gt; ul &gt; li &gt; div:nth-child(4) &gt; ul &gt; li.active &gt; a &gt; small&quot;, &quot;Should be able to create path for deep elements.&quot;);
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: &quot;WebInspector.cssPath.PseudoElement&quot;,
+        description: &quot;For a pseudo element we should get the path of the parent and append the pseudo element selector.&quot;,
+        test(resolve, reject) {
+            nodeForSelector(&quot;#pseudo-element-test &gt; div ~ div&quot;, (node) =&gt; {
+                let pseudoElementBefore = node.beforePseudoElement();
+                InspectorTest.assert(pseudoElementBefore);
+                InspectorTest.expectEqual(WebInspector.cssPath(pseudoElementBefore), &quot;#pseudo-element-test &gt; div:nth-child(3)::before&quot;, &quot;Should be able to create path for ::before pseudo elements.&quot;);
+                let pseudoElementAfter = node.afterPseudoElement();
+                InspectorTest.assert(pseudoElementAfter);
+                InspectorTest.expectEqual(WebInspector.cssPath(pseudoElementAfter), &quot;#pseudo-element-test &gt; div:nth-child(3)::after&quot;, &quot;Should be able to create path for ::after pseudo elements.&quot;);
+                resolve();
+            });
+        }
+    });
+
+    // FIXME: Write tests for nodes inside a Shadow DOM Tree.
+
+    WebInspector.domTreeManager.requestDocument((node) =&gt; {
+        documentNode = node;
+        suite.runTestCasesAndFinish();
+    });
+}
+&lt;/script&gt;
+&lt;/head&gt;
+&lt;body onload=&quot;runTest()&quot;&gt;
+&lt;p&gt;Test for WebInspector.cssPath.&lt;/p&gt;
+&lt;!-- If display:none pseudo elements are not created. --&gt;
+&lt;div style=&quot;visibility:hidden&quot;&gt;
+    &lt;div id=&quot;id-test&quot;&gt;
+        &lt;div&gt;&lt;/div&gt;
+    &lt;/div&gt;
+    &lt;div id=&quot;input-test&quot;&gt;
+        &lt;input type=&quot;password&quot;&gt;
+    &lt;/div&gt;
+    &lt;div id=&quot;unique-tag-test&quot;&gt;
+        &lt;div&gt;&lt;/div&gt;
+        &lt;span&gt;&lt;/span&gt;
+        &lt;div&gt;&lt;/div&gt;
+    &lt;/div&gt;
+    &lt;div id=&quot;non-unique-tag-test&quot;&gt;
+        &lt;div&gt;&lt;/div&gt;
+        &lt;span&gt;&lt;/span&gt;
+        &lt;span&gt;&lt;/span&gt;
+        &lt;div&gt;&lt;/div&gt;
+    &lt;/div&gt;
+    &lt;div id=&quot;unique-class-test&quot;&gt;
+        &lt;div class=&quot;alpha&quot;&gt;&lt;/div&gt;
+        &lt;div class=&quot;alpha beta&quot;&gt;&lt;/div&gt;
+        &lt;div class=&quot;alpha&quot;&gt;&lt;/div&gt;
+    &lt;/div&gt;
+    &lt;div id=&quot;non-unique-class-test&quot;&gt;
+        &lt;div class=&quot;alpha&quot;&gt;&lt;/div&gt;
+        &lt;div class=&quot;alpha&quot;&gt;&lt;/div&gt;
+        &lt;div class=&quot;alpha&quot;&gt;&lt;/div&gt;
+    &lt;/div&gt;
+    &lt;div id=&quot;unique-tag-and-class-test&quot;&gt;
+        &lt;div class=&quot;alpha&quot;&gt;&lt;/div&gt;
+    &lt;/div&gt;
+    &lt;div class=&quot;deep-path-test&quot;&gt;
+        &lt;ul&gt;
+            &lt;li&gt;
+                &lt;h1&gt;&lt;/h1&gt;
+                &lt;div&gt;&lt;/div&gt;
+                &lt;div&gt;&lt;/div&gt;
+                &lt;div&gt;
+                    &lt;ul class=&quot;list&quot;&gt;
+                        &lt;li&gt;&lt;/li&gt;
+                        &lt;li class=&quot;active&quot;&gt;&lt;a href=&quot;#&quot;&gt;&lt;small&gt;&lt;/small&gt;&lt;/a&gt;&lt;/li&gt;
+                        &lt;li&gt;&lt;/li&gt;
+                    &lt;/ul&gt;
+                &lt;/div&gt;
+            &lt;/li&gt;
+        &lt;/ul&gt;
+    &lt;/div&gt;
+    &lt;div id=&quot;pseudo-element-test&quot;&gt;
+        &lt;style&gt;
+        #pseudo-element-test &gt; div~div::before { content: &quot;before&quot;; }
+        #pseudo-element-test &gt; div~div::after { content: &quot;after&quot;; }
+        &lt;/style&gt;
+        &lt;div&gt;&lt;/div&gt;
+        &lt;div&gt;&lt;/div&gt;
+    &lt;/div&gt;
+&lt;/div&gt;
+&lt;/body&gt;
+&lt;/html&gt;
</ins></span></pre></div>
<a id="trunkLayoutTestsinspectordomdomutilitiespathdumpexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/inspector/dom/domutilities-path-dump-expected.txt (0 => 206059)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/dom/domutilities-path-dump-expected.txt                                (rev 0)
+++ trunk/LayoutTests/inspector/dom/domutilities-path-dump-expected.txt        2016-09-17 02:14:53 UTC (rev 206059)
</span><span class="lines">@@ -0,0 +1,111 @@
</span><ins>+Test for WebInspector.cssPath.
+
+ид
+класс
+
+-- CSS Selector Paths --
+html
+  head
+    head &gt; meta
+    #script-id
+    #test-script
+  body
+    body &gt; p
+    body &gt; article:nth-child(2)
+    body &gt; article:nth-child(3)
+    #ids
+      #ids &gt; div:nth-child(1)
+      #ids &gt; div:nth-child(2)
+      #inner-id
+      #__proto__
+      [id=&quot;\#\&quot;ridiculous\&quot;\.id&quot;]
+      [id=&quot;\'quoted\.value\'&quot;]
+      #\.foo\.bar
+      #\-
+      #-a
+      [id=&quot;-\30 &quot;]
+      [id=&quot;\37 &quot;]
+      #ид
+      #ids &gt; p
+    #classes
+      #classes &gt; div:nth-child(1)
+      #classes &gt; div:nth-child(2)
+      #classes &gt; div.\.foo
+      #classes &gt; div.\.foo\.bar
+      #classes &gt; div.\-
+      #classes &gt; div.-a
+      #classes &gt; div.-\30 
+      #classes &gt; div.\37 
+      #classes &gt; div.класс
+      #classes &gt; div:nth-child(10)
+      #classes &gt; div:nth-child(11)
+      #classes &gt; span
+      #id-with-class
+    #non-unique-classes
+      #non-unique-classes &gt; span:nth-child(1)
+      #non-unique-classes &gt; span:nth-child(2)
+      #non-unique-classes &gt; span:nth-child(3)
+      #non-unique-classes &gt; span:nth-child(4)
+      #non-unique-classes &gt; span:nth-child(5)
+      #non-unique-classes &gt; div:nth-child(6)
+      #non-unique-classes &gt; div:nth-child(7)
+      #non-unique-classes &gt; div:nth-child(8)
+      #non-unique-classes &gt; div:nth-child(9)
+      #non-unique-classes &gt; div:nth-child(10)
+      #non-unique-classes &gt; div:nth-child(11)
+
+-- XPaths --
+/html
+  /html/head
+    /html/head/meta
+    //*[@id=&quot;script-id&quot;]
+    //*[@id=&quot;test-script&quot;]
+      //*[@id=&quot;test-script&quot;]/text()
+  /html/body
+    /html/body/p
+      /html/body/p/text()
+    /html/body/article[1]
+    /html/body/article[2]
+    //*[@id=&quot;ids&quot;]
+      //*[@id=&quot;ids&quot;]/div[1]
+      //*[@id=&quot;ids&quot;]/div[2]
+      //*[@id=&quot;inner-id&quot;]
+      //*[@id=&quot;__proto__&quot;]
+      //*[@id=&quot;#&quot;ridiculous&quot;.id&quot;]
+      //*[@id=&quot;'quoted.value'&quot;]
+      //*[@id=&quot;.foo.bar&quot;]
+      //*[@id=&quot;-&quot;]
+      //*[@id=&quot;-a&quot;]
+      //*[@id=&quot;-0&quot;]
+      //*[@id=&quot;7&quot;]
+      //*[@id=&quot;ид&quot;]
+        //*[@id=&quot;ид&quot;]/text()
+      //*[@id=&quot;ids&quot;]/p
+    //*[@id=&quot;classes&quot;]
+      //*[@id=&quot;classes&quot;]/div[1]
+      //*[@id=&quot;classes&quot;]/div[2]
+      //*[@id=&quot;classes&quot;]/div[3]
+      //*[@id=&quot;classes&quot;]/div[4]
+      //*[@id=&quot;classes&quot;]/div[5]
+      //*[@id=&quot;classes&quot;]/div[6]
+      //*[@id=&quot;classes&quot;]/div[7]
+      //*[@id=&quot;classes&quot;]/div[8]
+      //*[@id=&quot;classes&quot;]/div[9]
+        //*[@id=&quot;classes&quot;]/div[9]/text()
+      //*[@id=&quot;classes&quot;]/div[10]
+      //*[@id=&quot;classes&quot;]/div[11]
+      //*[@id=&quot;classes&quot;]/span
+      //*[@id=&quot;id-with-class&quot;]
+    //*[@id=&quot;non-unique-classes&quot;]
+      //*[@id=&quot;non-unique-classes&quot;]/span[1]
+      //*[@id=&quot;non-unique-classes&quot;]/span[2]
+      //*[@id=&quot;non-unique-classes&quot;]/span[3]
+      //*[@id=&quot;non-unique-classes&quot;]/span[4]
+      //*[@id=&quot;non-unique-classes&quot;]/span[5]
+      //*[@id=&quot;non-unique-classes&quot;]/div[1]
+      //*[@id=&quot;non-unique-classes&quot;]/div[2]
+      //*[@id=&quot;non-unique-classes&quot;]/div[3]
+      //*[@id=&quot;non-unique-classes&quot;]/div[4]
+      //*[@id=&quot;non-unique-classes&quot;]/div[5]
+      //*[@id=&quot;non-unique-classes&quot;]/div[6]
+
</ins></span></pre></div>
<a id="trunkLayoutTestsinspectordomdomutilitiespathdumphtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/inspector/dom/domutilities-path-dump.html (0 => 206059)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/dom/domutilities-path-dump.html                                (rev 0)
+++ trunk/LayoutTests/inspector/dom/domutilities-path-dump.html        2016-09-17 02:14:53 UTC (rev 206059)
</span><span class="lines">@@ -0,0 +1,122 @@
</span><ins>+&lt;!DOCTYPE html&gt;
+&lt;html&gt;
+&lt;head&gt;
+&lt;meta charset=&quot;utf-8&quot;&gt;
+&lt;script src=&quot;../../http/tests/inspector/resources/inspector-test.js&quot; id=&quot;script-id&quot;&gt;&lt;/script&gt;
+&lt;script id=&quot;test-script&quot;&gt;
+function verifySelector(selector) {
+    let nodes = document.querySelectorAll(selector);
+    if (nodes.length !== 1)
+        console.log(&quot;Selector was not unique: &quot; + selector);
+}
+
+function verifyXPath(xpath) {
+    let nodes = document.evaluate(xpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+    if (nodes.snapshotLength !== 1)
+        console.log(&quot;XPath was not unique: &quot; + xpath);
+}
+
+function test()
+{
+    let nodes = [];
+
+    function buildNodeList(node, depth) {
+        nodes.push({node, depth});
+        if (!node.children)
+            return;
+        for (let child of node.children)
+            buildNodeList(child, depth + 1);
+    }
+
+    function processList(func, verifier) {
+        for (let {node, depth} of nodes) {
+            let prefix = &quot; &quot;.repeat(depth * 2);
+            let path = func(node);
+            if (path) {
+                InspectorTest.log(prefix + path);
+                verifier(path);
+            }   
+        }
+    }
+
+    WebInspector.domTreeManager.requestDocument((documentNode) =&gt; {
+        // Push all the nodes to the frontend.
+        WebInspector.domTreeManager.querySelector(documentNode.id, &quot;html&quot;, (nodeId) =&gt; {
+            let htmlNode = WebInspector.domTreeManager.nodeForId(nodeId);
+            htmlNode.getSubtree(10, () =&gt; {
+                buildNodeList(htmlNode, 0);
+
+                InspectorTest.log(&quot;&quot;);
+                InspectorTest.log(&quot;-- CSS Selector Paths --&quot;);
+                processList(WebInspector.cssPath, (selector) =&gt; {
+                    InspectorTest.evaluateInPage(&quot;verifySelector(&quot; + JSON.stringify(selector) + &quot;)&quot;);
+                });
+
+                InspectorTest.log(&quot;&quot;);
+                InspectorTest.log(&quot;-- XPaths --&quot;);
+                processList(WebInspector.xpath, (xpath) =&gt; {
+                    InspectorTest.evaluateInPage(&quot;verifyXPath(&quot; + JSON.stringify(xpath) + &quot;)&quot;);
+                });
+
+                InspectorBackend.runAfterPendingDispatches(() =&gt; {
+                    InspectorTest.completeTest();
+                })
+            });
+        });
+    });
+}
+&lt;/script&gt;
+&lt;/head&gt;
+&lt;body onload=&quot;runTest()&quot;&gt;
+&lt;p&gt;Test for WebInspector.cssPath.&lt;/p&gt;
+
+&lt;article&gt;&lt;/article&gt;
+&lt;article&gt;&lt;/article&gt;
+
+&lt;div id=&quot;ids&quot;&gt;
+    &lt;div&gt;&lt;/div&gt;
+    &lt;div&gt;&lt;/div&gt;
+    &lt;div id=&quot;inner-id&quot;&gt;&lt;/div&gt;
+    &lt;div id=&quot;__proto__&quot;&gt;&lt;/div&gt;
+    &lt;div id='#&quot;ridiculous&quot;.id'&gt;&lt;/div&gt;
+    &lt;div id=&quot;'quoted.value'&quot;&gt;&lt;/div&gt;
+    &lt;div id=&quot;.foo.bar&quot;&gt;&lt;/div&gt;
+    &lt;div id=&quot;-&quot;&gt;&lt;/div&gt;
+    &lt;div id=&quot;-a&quot;&gt;&lt;/div&gt;
+    &lt;div id=&quot;-0&quot;&gt;&lt;/div&gt;
+    &lt;div id=&quot;7&quot;&gt;&lt;/div&gt;
+    &lt;div id=&quot;ид&quot;&gt;ид&lt;/div&gt;
+    &lt;p&gt;&lt;/p&gt;
+&lt;/div&gt;
+
+&lt;div id=&quot;classes&quot;&gt;
+    &lt;div class=&quot;foo bar&quot;&gt;&lt;/div&gt;
+    &lt;div class=&quot; foo foo &quot;&gt;&lt;/div&gt;
+    &lt;div class=&quot;.foo&quot;&gt;&lt;/div&gt;
+    &lt;div class=&quot;.foo.bar&quot;&gt;&lt;/div&gt;
+    &lt;div class=&quot;-&quot;&gt;&lt;/div&gt;
+    &lt;div class=&quot;-a&quot;&gt;&lt;/div&gt;
+    &lt;div class=&quot;-0&quot;&gt;&lt;/div&gt;
+    &lt;div class=&quot;7&quot;&gt;&lt;/div&gt;
+    &lt;div class=&quot;класс&quot;&gt;класс&lt;/div&gt;
+    &lt;div class=&quot;__proto__&quot;&gt;&lt;/div&gt;
+    &lt;div class=&quot;__proto__ foo&quot;&gt;&lt;/div&gt;
+    &lt;span class=&quot;bar&quot;&gt;&lt;/span&gt;
+    &lt;div id=&quot;id-with-class&quot; class=&quot;moo&quot;&gt;&lt;/div&gt;
+&lt;/div&gt;
+
+&lt;div id=&quot;non-unique-classes&quot;&gt;
+  &lt;span class=&quot;c1&quot;&gt;&lt;/span&gt;
+  &lt;span class=&quot;c1&quot;&gt;&lt;/span&gt;
+  &lt;span class=&quot;c1 c2&quot;&gt;&lt;/span&gt;
+  &lt;span class=&quot;c1 c2 c3&quot;&gt;&lt;/span&gt;
+  &lt;span&gt;&lt;/span&gt;
+  &lt;div class=&quot;c1&quot;&gt;&lt;/div&gt;
+  &lt;div class=&quot;c1 c2&quot;&gt;&lt;/div&gt;
+  &lt;div class=&quot;c3 c2&quot;&gt;&lt;/div&gt;
+  &lt;div class=&quot;c3 c4&quot;&gt;&lt;/div&gt;
+  &lt;div class=&quot;c1 c4&quot;&gt;&lt;/div&gt;
+  &lt;div&gt;&lt;/div&gt;
+&lt;/div&gt;
+&lt;/body&gt;
+&lt;/html&gt;
</ins></span></pre></div>
<a id="trunkLayoutTestsinspectordomdomutilitiesxpathexpectedtxt"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/inspector/dom/domutilities-xpath-expected.txt (0 => 206059)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/dom/domutilities-xpath-expected.txt                                (rev 0)
+++ trunk/LayoutTests/inspector/dom/domutilities-xpath-expected.txt        2016-09-17 02:14:53 UTC (rev 206059)
</span><span class="lines">@@ -0,0 +1,29 @@
</span><ins>+Test for WebInspector.xpath.
+
+
+
+
+== Running test suite: WebInspector.xpath
+-- Running test case: WebInspector.xpath.TopLevelNode
+PASS: HTML element should have simple XPath '/html'.
+PASS: BODY element should have simple XPath '/html/body'.
+PASS: HEAD element should have simple XPath '/html/head'.
+
+-- Running test case: WebInspector.xpath.ElementWithID
+PASS: Element with id should have a single path component '//*[@id=&quot;id-test&quot;]'.
+PASS: Element inside element with id should have path from id.
+
+-- Running test case: WebInspector.xpath.UniqueTagName
+PASS: Elements with unique tag name should not need XPath index.
+
+-- Running test case: WebInspector.xpath.NonUniqueTagName
+PASS: Elements with non-unique tag name should need XPath index.
+
+-- Running test case: WebInspector.xpath.DeepPath
+/html/body/div/div[7]/ul/li/div[3]/ul/li[2]/a/small
+PASS: Should be able to get XPath for deep elements.
+
+-- Running test case: WebInspector.xpath.TextAndCommentNode
+PASS: Should be able to get XPath for TEXT_NODE.
+PASS: Should be able to get XPath for COMMENT_NODE.
+
</ins></span></pre></div>
<a id="trunkLayoutTestsinspectordomdomutilitiesxpathhtml"></a>
<div class="addfile"><h4>Added: trunk/LayoutTests/inspector/dom/domutilities-xpath.html (0 => 206059)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/LayoutTests/inspector/dom/domutilities-xpath.html                                (rev 0)
+++ trunk/LayoutTests/inspector/dom/domutilities-xpath.html        2016-09-17 02:14:53 UTC (rev 206059)
</span><span class="lines">@@ -0,0 +1,166 @@
</span><ins>+&lt;!DOCTYPE html&gt;
+&lt;html&gt;
+&lt;head&gt;
+&lt;script src=&quot;../../http/tests/inspector/resources/inspector-test.js&quot;&gt;&lt;/script&gt;
+&lt;script&gt;
+function test()
+{
+    let documentNode;
+
+    function nodeForSelector(selector, callback) {
+        WebInspector.domTreeManager.querySelector(documentNode.id, selector, (nodeId) =&gt; {
+            callback(WebInspector.domTreeManager.nodeForId(nodeId));
+        });
+    }
+
+    let suite = InspectorTest.createAsyncSuite(&quot;WebInspector.xpath&quot;);
+
+    suite.addTestCase({
+        name: &quot;WebInspector.xpath.TopLevelNode&quot;,
+        description: &quot;Top level nodes like html, body, and head are unique.&quot;,
+        test(resolve, reject) {
+            nodeForSelector(&quot;html&quot;, (node) =&gt; {
+                InspectorTest.expectEqual(WebInspector.xpath(node), &quot;/html&quot;, &quot;HTML element should have simple XPath '/html'.&quot;);
+            });
+            nodeForSelector(&quot;html &gt; body&quot;, (node) =&gt; {
+                InspectorTest.expectEqual(WebInspector.xpath(node), &quot;/html/body&quot;, &quot;BODY element should have simple XPath '/html/body'.&quot;);
+            });
+            nodeForSelector(&quot;html &gt; head&quot;, (node) =&gt; {
+                InspectorTest.expectEqual(WebInspector.xpath(node), &quot;/html/head&quot;, &quot;HEAD element should have simple XPath '/html/head'.&quot;);
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: &quot;WebInspector.xpath.ElementWithID&quot;,
+        description: &quot;Element with ID is unique (#id). Path does not need to go past it.&quot;,
+        test(resolve, reject) {
+            nodeForSelector(&quot;#id-test&quot;, (node) =&gt; {
+                InspectorTest.expectEqual(WebInspector.xpath(node), &quot;//*[@id=\&quot;id-test\&quot;]&quot;, &quot;Element with id should have a single path component '//*[@id=\&quot;id-test\&quot;]'.&quot;);
+            });
+            nodeForSelector(&quot;#id-test &gt; div&quot;, (node) =&gt; {
+                InspectorTest.expectEqual(WebInspector.xpath(node), &quot;//*[@id=\&quot;id-test\&quot;]/div&quot;, &quot;Element inside element with id should have path from id.&quot;);
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: &quot;WebInspector.xpath.UniqueTagName&quot;,
+        description: &quot;Elements with unique tag name do not need nth-child.&quot;,
+        test(resolve, reject) {
+            nodeForSelector(&quot;#unique-tag-test &gt; span&quot;, (node) =&gt; {
+                InspectorTest.expectEqual(WebInspector.xpath(node), &quot;//*[@id=\&quot;unique-tag-test\&quot;]/span&quot;, &quot;Elements with unique tag name should not need XPath index.&quot;);
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: &quot;WebInspector.xpath.NonUniqueTagName&quot;,
+        description: &quot;Elements with non-unique tag name need index.&quot;,
+        test(resolve, reject) {
+            nodeForSelector(&quot;#non-unique-tag-test &gt; span ~ span&quot;, (node) =&gt; {
+                InspectorTest.expectEqual(WebInspector.xpath(node), &quot;//*[@id=\&quot;non-unique-tag-test\&quot;]/span[2]&quot;, &quot;Elements with non-unique tag name should need XPath index.&quot;);
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: &quot;WebInspector.xpath.DeepPath&quot;,
+        description: &quot;Tests for element with complex path.&quot;,
+        test(resolve, reject) {
+            nodeForSelector(&quot;small&quot;, (node) =&gt; {
+                InspectorTest.log(WebInspector.xpath(node));
+                InspectorTest.expectEqual(WebInspector.xpath(node), &quot;/html/body/div/div[7]/ul/li/div[3]/ul/li[2]/a/small&quot;, &quot;Should be able to get XPath for deep elements.&quot;);
+                resolve();
+            });
+        }
+    });
+
+    suite.addTestCase({
+        name: &quot;WebInspector.xpath.TextAndCommentNode&quot;,
+        description: &quot;Tests for non-Element nodes.&quot;,
+        test(resolve, reject) {
+            nodeForSelector(&quot;#non-element-test &gt; p &gt; br&quot;, (node) =&gt; {
+                let paragraphChildren = node.parentNode.children;
+                let lastTextChild = paragraphChildren[paragraphChildren.length - 1];
+                let lastCommentChild = paragraphChildren[paragraphChildren.length - 2];
+                InspectorTest.expectEqual(WebInspector.xpath(lastTextChild), &quot;//*[@id=\&quot;non-element-test\&quot;]/p/text()[3]&quot;, &quot;Should be able to get XPath for TEXT_NODE.&quot;);
+                InspectorTest.expectEqual(WebInspector.xpath(lastCommentChild), &quot;//*[@id=\&quot;non-element-test\&quot;]/p/comment()&quot;, &quot;Should be able to get XPath for COMMENT_NODE.&quot;);
+                resolve();
+            });
+        }
+    });
+
+    // FIXME: Write tests for nodes inside a Shadow DOM Tree.
+    // FIXME: Write test for CDATA.
+
+    WebInspector.domTreeManager.requestDocument((node) =&gt; {
+        documentNode = node;
+        suite.runTestCasesAndFinish();
+    });
+}
+&lt;/script&gt;
+&lt;/head&gt;
+&lt;body onload=&quot;runTest()&quot;&gt;
+&lt;p&gt;Test for WebInspector.xpath.&lt;/p&gt;
+&lt;!-- If display:none pseudo elements are not created. --&gt;
+&lt;div style=&quot;visibility:hidden&quot;&gt;
+    &lt;div id=&quot;id-test&quot;&gt;
+        &lt;div&gt;&lt;/div&gt;
+    &lt;/div&gt;
+    &lt;div id=&quot;unique-tag-test&quot;&gt;
+        &lt;div&gt;&lt;/div&gt;
+        &lt;span&gt;&lt;/span&gt;
+        &lt;div&gt;&lt;/div&gt;
+    &lt;/div&gt;
+    &lt;div id=&quot;non-unique-tag-test&quot;&gt;
+        &lt;div&gt;&lt;/div&gt;
+        &lt;span&gt;&lt;/span&gt;
+        &lt;span&gt;&lt;/span&gt;
+        &lt;div&gt;&lt;/div&gt;
+    &lt;/div&gt;
+    &lt;div id=&quot;unique-class-test&quot;&gt;
+        &lt;div class=&quot;alpha&quot;&gt;&lt;/div&gt;
+        &lt;div class=&quot;alpha beta&quot;&gt;&lt;/div&gt;
+        &lt;div class=&quot;alpha&quot;&gt;&lt;/div&gt;
+    &lt;/div&gt;
+    &lt;div id=&quot;non-unique-class-test&quot;&gt;
+        &lt;div class=&quot;alpha&quot;&gt;&lt;/div&gt;
+        &lt;div class=&quot;alpha&quot;&gt;&lt;/div&gt;
+        &lt;div class=&quot;alpha&quot;&gt;&lt;/div&gt;
+    &lt;/div&gt;
+    &lt;div id=&quot;unique-tag-and-class-test&quot;&gt;
+        &lt;div class=&quot;alpha&quot;&gt;&lt;/div&gt;
+    &lt;/div&gt;
+    &lt;div class=&quot;deep-path-test&quot;&gt;
+        &lt;ul&gt;
+            &lt;li&gt;
+                &lt;h1&gt;&lt;/h1&gt;
+                &lt;div&gt;&lt;/div&gt;
+                &lt;div&gt;&lt;/div&gt;
+                &lt;div&gt;
+                    &lt;ul class=&quot;list&quot;&gt;
+                        &lt;li&gt;&lt;/li&gt;
+                        &lt;li class=&quot;active&quot;&gt;&lt;a href=&quot;#&quot;&gt;&lt;small&gt;&lt;/small&gt;&lt;/a&gt;&lt;/li&gt;
+                        &lt;li&gt;&lt;/li&gt;
+                    &lt;/ul&gt;
+                &lt;/div&gt;
+            &lt;/li&gt;
+        &lt;/ul&gt;
+    &lt;/div&gt;
+    &lt;div id=&quot;non-element-test&quot;&gt;
+        &lt;p&gt;
+            Some leading text
+            &lt;br&gt;
+            Some trailing text
+            &lt;!-- Comment --&gt;
+            Some final text
+        &lt;/p&gt;
+    &lt;/div&gt;
+&lt;/div&gt;
+&lt;/body&gt;
+&lt;/html&gt;
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIChangeLog"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/ChangeLog (206058 => 206059)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/ChangeLog        2016-09-17 00:43:55 UTC (rev 206058)
+++ trunk/Source/WebInspectorUI/ChangeLog        2016-09-17 02:14:53 UTC (rev 206059)
</span><span class="lines">@@ -1,3 +1,34 @@
</span><ins>+2016-09-16  Joseph Pecoraro  &lt;pecoraro@apple.com&gt;
+
+        Web Inspector: Implement Copy CSS Selector and Copy Xpath Selector context menus
+        https://bugs.webkit.org/show_bug.cgi?id=158881
+        &lt;rdar://problem/8181156&gt;
+
+        Reviewed by Matt Baker.
+
+        This is based off of the Blink implementation (DOMPresentationUtils)
+        with some minor modifications and using our own utility methods.
+
+        * Localizations/en.lproj/localizedStrings.js:
+        New context menu strings.
+
+        * UserInterface/Base/DOMUtilities.js:
+        (WebInspector.cssPath):
+        (WebInspector.cssPathComponent.classNames):
+        (WebInspector.cssPathComponent):
+        (WebInspector.xpath):
+        (WebInspector.xpathIndex.isSimiliarNode):
+        (WebInspector.xpathIndex):
+        Build strings for a CSS selector path or XPath path to a node.
+
+        * UserInterface/Views/DOMTreeElement.js:
+        (WebInspector.DOMTreeElement.prototype._populateNodeContextMenu):
+        * UserInterface/Views/DOMTreeOutline.js:
+        (WebInspector.DOMTreeOutline.prototype.populateContextMenu):
+        Include copy path context menu items on nodes.
+        Pseudo elements do not get Copy XPath.
+        Non-node elements do not get Copy Selector Path.
+
</ins><span class="cx"> 2016-09-16  Nikita Vasilyev  &lt;nvasilyev@apple.com&gt;
</span><span class="cx"> 
</span><span class="cx">         Web Inspector: Make console session dividers more pronounced
</span></span></pre></div>
<a id="trunkSourceWebInspectorUILocalizationsenlprojlocalizedStringsjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js (206058 => 206059)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js        2016-09-17 00:43:55 UTC (rev 206058)
+++ trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js        2016-09-17 02:14:53 UTC (rev 206059)
</span><span class="lines">@@ -208,7 +208,9 @@
</span><span class="cx"> localizedStrings[&quot;Copy Row&quot;] = &quot;Copy Row&quot;;
</span><span class="cx"> localizedStrings[&quot;Copy Rule&quot;] = &quot;Copy Rule&quot;;
</span><span class="cx"> localizedStrings[&quot;Copy Selected&quot;] = &quot;Copy Selected&quot;;
</span><ins>+localizedStrings[&quot;Copy Selector Path&quot;] = &quot;Copy Selector Path&quot;;
</ins><span class="cx"> localizedStrings[&quot;Copy Table&quot;] = &quot;Copy Table&quot;;
</span><ins>+localizedStrings[&quot;Copy XPath&quot;] = &quot;Copy XPath&quot;;
</ins><span class="cx"> localizedStrings[&quot;Copy as HTML&quot;] = &quot;Copy as HTML&quot;;
</span><span class="cx"> localizedStrings[&quot;Copy as cURL&quot;] = &quot;Copy as cURL&quot;;
</span><span class="cx"> localizedStrings[&quot;Could not fetch properties. Object may no longer exist.&quot;] = &quot;Could not fetch properties. Object may no longer exist.&quot;;
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceBaseDOMUtilitiesjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Base/DOMUtilities.js (206058 => 206059)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Base/DOMUtilities.js        2016-09-17 00:43:55 UTC (rev 206058)
+++ trunk/Source/WebInspectorUI/UserInterface/Base/DOMUtilities.js        2016-09-17 02:14:53 UTC (rev 206059)
</span><span class="lines">@@ -80,3 +80,231 @@
</span><span class="cx"> {
</span><span class="cx">     return document.createElementNS(&quot;http://www.w3.org/2000/svg&quot;, tagName);
</span><span class="cx"> }
</span><ins>+
+WebInspector.cssPath = function(node)
+{
+    console.assert(node instanceof WebInspector.DOMNode, &quot;Expected a DOMNode.&quot;);
+    if (node.nodeType() !== Node.ELEMENT_NODE)
+        return &quot;&quot;;
+
+    let suffix = &quot;&quot;;
+    if (node.isPseudoElement()) {
+        suffix = &quot;::&quot; + node.pseudoType();
+        node = node.parentNode;
+    }
+
+    let components = [];
+    while (node) {
+        let component = WebInspector.cssPathComponent(node);
+        if (!component)
+            break;
+        components.push(component);
+        if (component.done)
+            break;
+        node = node.parentNode;
+    }
+
+    components.reverse();
+    return components.map((x) =&gt; x.value).join(&quot; &gt; &quot;) + suffix;
+};
+
+WebInspector.cssPathComponent = function(node)
+{
+    console.assert(node instanceof WebInspector.DOMNode, &quot;Expected a DOMNode.&quot;);
+    console.assert(!node.isPseudoElement());
+    if (node.nodeType() !== Node.ELEMENT_NODE)
+        return null;
+
+    let nodeName = node.nodeNameInCorrectCase();
+    let lowerNodeName = node.nodeName().toLowerCase();
+
+    // html, head, and body are unique nodes.
+    if (lowerNodeName === &quot;body&quot; || lowerNodeName === &quot;head&quot; || lowerNodeName === &quot;html&quot;)
+        return {value: nodeName, done: true};
+
+    // #id is unique.
+    let id = node.getAttribute(&quot;id&quot;);
+    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
+    //   - fallback to nth-child()
+
+    function classNames(node) {
+        let classAttribute = node.getAttribute(&quot;class&quot;);
+        return classAttribute ? classAttribute.trim().split(/\s+/) : [];
+    }
+
+    let nthChildIndex = -1;
+    let hasUniqueTagName = true;
+    let uniqueClasses = new Set(classNames(node));
+
+    let siblings = node.parentNode.children;
+    let elementIndex = 0;
+    for (let sibling of siblings) {
+        if (sibling.nodeType() !== Node.ELEMENT_NODE)
+            continue;
+
+        elementIndex++;
+        if (sibling === node) {
+            nthChildIndex = elementIndex;
+            continue;
+        }
+
+        if (sibling.nodeNameInCorrectCase() === nodeName)
+            hasUniqueTagName = false;
+
+        if (uniqueClasses.size) {
+            let siblingClassNames = classNames(sibling);
+            for (let className of siblingClassNames)
+                uniqueClasses.delete(className);
+        }
+    }
+
+    let selector = nodeName;
+    if (lowerNodeName === &quot;input&quot; &amp;&amp; node.getAttribute(&quot;type&quot;) &amp;&amp; !uniqueClasses.size)
+        selector += `[type=&quot;${node.getAttribute(&quot;type&quot;)}&quot;]`;
+    if (!hasUniqueTagName) {
+        if (uniqueClasses.size)
+            selector += node.escapedClassSelector;
+        else
+            selector += `:nth-child(${nthChildIndex})`;
+    }
+
+    return {value: selector, done: false};
+};
+
+WebInspector.xpath = function(node)
+{
+    console.assert(node instanceof WebInspector.DOMNode, &quot;Expected a DOMNode.&quot;);
+
+    if (node.nodeType() === Node.DOCUMENT_NODE)
+        return &quot;/&quot;;
+
+    let components = [];
+    while (node) {
+        let component = WebInspector.xpathComponent(node);
+        if (!component)
+            break;
+        components.push(component);
+        if (component.done)
+            break;
+        node = node.parentNode;
+    }
+
+    components.reverse();
+
+    let prefix = components.length &amp;&amp; components[0].done ? &quot;&quot; : &quot;/&quot;;
+    return prefix + components.map((x) =&gt; x.value).join(&quot;/&quot;);
+};
+
+WebInspector.xpathComponent = function(node)
+{
+    console.assert(node instanceof WebInspector.DOMNode, &quot;Expected a DOMNode.&quot;);
+
+    let index = WebInspector.xpathIndex(node);
+    if (index === -1)
+        return null;
+
+    let value;
+
+    switch (node.nodeType()) {
+    case Node.DOCUMENT_NODE:
+        return {value: &quot;&quot;, done: true};
+    case Node.ELEMENT_NODE:
+        var id = node.getAttribute(&quot;id&quot;);
+        if (id)
+            return {value: `//*[@id=&quot;${id}&quot;]`, done: true};
+        value = node.localName();
+        break;
+    case Node.ATTRIBUTE_NODE:
+        value = `@${node.nodeName()}`;
+        break;
+    case Node.TEXT_NODE:
+    case Node.CDATA_SECTION_NODE:
+        value = &quot;text()&quot;;
+        break;
+    case Node.COMMENT_NODE:
+        value = &quot;comment()&quot;;
+        break;
+    case Node.PROCESSING_INSTRUCTION_NODE:
+        value = &quot;processing-instruction()&quot;;
+        break
+    default:
+        value = &quot;&quot;;
+        break;
+    }
+
+    if (index &gt; 0)
+        value += `[${index}]`;
+
+    return {value, done: false};
+};
+
+WebInspector.xpathIndex = function(node)
+{
+    // Root node.
+    if (!node.parentNode)
+        return 0;
+
+    // No siblings.
+    let siblings = node.parentNode.children;
+    if (siblings.length &lt;= 1)
+        return 0;
+
+    // Find uniqueness among siblings.
+    //   - look for a unique localName
+    //   - fallback to index
+
+    function isSimiliarNode(a, b) {
+        if (a === b)
+            return true;
+
+        let aType = a.nodeType();
+        let bType = b.nodeType();
+
+        if (aType === Node.ELEMENT_NODE &amp;&amp; bType === Node.ELEMENT_NODE)
+            return a.localName() === b.localName();
+
+        // XPath CDATA and text() are the same.
+        if (aType === Node.CDATA_SECTION_NODE)
+            aType === Node.TEXT_NODE;
+        if (bType === Node.CDATA_SECTION_NODE)
+            bType === Node.TEXT_NODE;
+
+        return aType === bType;
+    }
+
+    let unique = true;
+    let xPathIndex = -1;
+
+    let xPathIndexCounter = 1; // XPath indices start at 1.
+    for (let sibling of siblings) {
+        if (!isSimiliarNode(node, sibling))
+            continue;
+
+        if (node === sibling) {
+            xPathIndex = xPathIndexCounter;
+            if (!unique)
+                return xPathIndex;
+        } else {
+            unique = false;
+            if (xPathIndex !== -1)
+                return xPathIndex;
+        }
+
+        xPathIndexCounter++;
+    }
+
+    if (unique)
+        return 0;
+
+    console.assert(xPathIndex &gt; 0, &quot;Should have found the node.&quot;);
+    return xPathIndex;
+};
</ins></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsDOMTreeElementjs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js (206058 => 206059)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js        2016-09-17 00:43:55 UTC (rev 206058)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js        2016-09-17 02:14:53 UTC (rev 206059)
</span><span class="lines">@@ -710,16 +710,33 @@
</span><span class="cx"> 
</span><span class="cx">     _populateNodeContextMenu(contextMenu)
</span><span class="cx">     {
</span><ins>+        let node = this.representedObject;
+
</ins><span class="cx">         // Add free-form node-related actions.
</span><span class="cx">         if (this.editable)
</span><span class="cx">             contextMenu.appendItem(WebInspector.UIString(&quot;Edit as HTML&quot;), this._editAsHTML.bind(this));
</span><del>-        contextMenu.appendItem(WebInspector.UIString(&quot;Copy as HTML&quot;), this._copyHTML.bind(this));
</del><ins>+        if (!node.isPseudoElement())
+            contextMenu.appendItem(WebInspector.UIString(&quot;Copy as HTML&quot;), this._copyHTML.bind(this));
</ins><span class="cx">         if (this.editable)
</span><span class="cx">             contextMenu.appendItem(WebInspector.UIString(&quot;Delete Node&quot;), this.remove.bind(this));
</span><del>-
-        let node = this.representedObject;
</del><span class="cx">         if (node.nodeType() === Node.ELEMENT_NODE)
</span><span class="cx">             contextMenu.appendItem(WebInspector.UIString(&quot;Scroll Into View&quot;), this._scrollIntoView.bind(this));
</span><ins>+
+        contextMenu.appendSeparator();
+
+        if (node.nodeType() === Node.ELEMENT_NODE) {
+            contextMenu.appendItem(WebInspector.UIString(&quot;Copy Selector Path&quot;), () =&gt; {
+                let cssPath = WebInspector.cssPath(this.representedObject);
+                InspectorFrontendHost.copyText(cssPath);
+            });
+        }
+
+        if (!node.isPseudoElement()) {
+            contextMenu.appendItem(WebInspector.UIString(&quot;Copy XPath&quot;), () =&gt; {
+                let xpath = WebInspector.xpath(this.representedObject);
+                InspectorFrontendHost.copyText(xpath);
+            });
+        }
</ins><span class="cx">     }
</span><span class="cx"> 
</span><span class="cx">     _startEditing()
</span></span></pre></div>
<a id="trunkSourceWebInspectorUIUserInterfaceViewsDOMTreeOutlinejs"></a>
<div class="modfile"><h4>Modified: trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.js (206058 => 206059)</h4>
<pre class="diff"><span>
<span class="info">--- trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.js        2016-09-17 00:43:55 UTC (rev 206058)
+++ trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.js        2016-09-17 02:14:53 UTC (rev 206059)
</span><span class="lines">@@ -240,11 +240,12 @@
</span><span class="cx"> 
</span><span class="cx">     populateContextMenu(contextMenu, event, treeElement)
</span><span class="cx">     {
</span><del>-        var tag = event.target.enclosingNodeOrSelfWithClass(&quot;html-tag&quot;);
-        var textNode = event.target.enclosingNodeOrSelfWithClass(&quot;html-text-node&quot;);
-        var commentNode = event.target.enclosingNodeOrSelfWithClass(&quot;html-comment&quot;);
</del><ins>+        let tag = event.target.enclosingNodeOrSelfWithClass(&quot;html-tag&quot;);
+        let textNode = event.target.enclosingNodeOrSelfWithClass(&quot;html-text-node&quot;);
+        let commentNode = event.target.enclosingNodeOrSelfWithClass(&quot;html-comment&quot;);
+        let pseudoElement = event.target.enclosingNodeOrSelfWithClass(&quot;html-pseudo-element&quot;);
</ins><span class="cx"> 
</span><del>-        var populated = false;
</del><ins>+        let populated = false;
</ins><span class="cx">         if (tag &amp;&amp; treeElement._populateTagContextMenu) {
</span><span class="cx">             if (populated)
</span><span class="cx">                 contextMenu.appendSeparator();
</span><span class="lines">@@ -255,7 +256,7 @@
</span><span class="cx">                 contextMenu.appendSeparator();
</span><span class="cx">             treeElement._populateTextContextMenu(contextMenu, textNode);
</span><span class="cx">             populated = true;
</span><del>-        } else if (commentNode &amp;&amp; treeElement._populateNodeContextMenu) {
</del><ins>+        } else if ((commentNode || pseudoElement) &amp;&amp; treeElement._populateNodeContextMenu) {
</ins><span class="cx">             if (populated)
</span><span class="cx">                 contextMenu.appendSeparator();
</span><span class="cx">             treeElement._populateNodeContextMenu(contextMenu);
</span></span></pre>
</div>
</div>

</body>
</html>