<!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
<rdar://problem/8181156>
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 <pecoraro@apple.com>
+
+ Web Inspector: Implement Copy CSS Selector and Copy Xpath Selector context menus
+ https://bugs.webkit.org/show_bug.cgi?id=158881
+ <rdar://problem/8181156>
+
+ 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 <jer.noble@apple.com>
</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>+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script>
+function test()
+{
+ let documentNode;
+
+ function nodeForSelector(selector, callback) {
+ WebInspector.domTreeManager.querySelector(documentNode.id, selector, (nodeId) => {
+ callback(WebInspector.domTreeManager.nodeForId(nodeId));
+ });
+ }
+
+ let suite = InspectorTest.createAsyncSuite("WebInspector.cssPath");
+
+ suite.addTestCase({
+ name: "WebInspector.cssPath.TopLevelNode",
+ description: "Top level nodes like html, body, and head are unique.",
+ test(resolve, reject) {
+ nodeForSelector("html", (node) => {
+ InspectorTest.expectEqual(WebInspector.cssPath(node), "html", "HTML element should have simple selector 'html'.");
+ });
+ nodeForSelector("html > body", (node) => {
+ InspectorTest.expectEqual(WebInspector.cssPath(node), "body", "BODY element should have simple selector 'body'.");
+ });
+ nodeForSelector("html > head", (node) => {
+ InspectorTest.expectEqual(WebInspector.cssPath(node), "head", "HEAD element should have simple selector 'head'.");
+ resolve();
+ });
+ }
+ });
+
+ suite.addTestCase({
+ name: "WebInspector.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(WebInspector.cssPath(node), "#id-test", "Element with id should have simple selector '#id-test'.");
+ });
+ nodeForSelector("#id-test > div", (node) => {
+ InspectorTest.expectEqual(WebInspector.cssPath(node), "#id-test > div", "Element inside element with id should have path from id.");
+ resolve();
+ });
+ }
+ });
+
+ suite.addTestCase({
+ name: "WebInspector.cssPath.InputElementFlair",
+ description: "Input elements include their type.",
+ test(resolve, reject) {
+ nodeForSelector("#input-test input", (node) => {
+ InspectorTest.expectEqual(WebInspector.cssPath(node), "#input-test > input[type=\"password\"]", "Input element should include type.");
+ resolve();
+ });
+ }
+ });
+
+ suite.addTestCase({
+ name: "WebInspector.cssPath.UniqueTagName",
+ description: "Elements with unique tag name do not need nth-child.",
+ test(resolve, reject) {
+ nodeForSelector("#unique-tag-test > span", (node) => {
+ InspectorTest.expectEqual(WebInspector.cssPath(node), "#unique-tag-test > span", "Elements with unique tag name should not need nth-child().");
+ resolve();
+ });
+ }
+ });
+
+ suite.addTestCase({
+ name: "WebInspector.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(WebInspector.cssPath(node), "#non-unique-tag-test > span:nth-child(3)", "Elements with non-unique tag name should need nth-child().");
+ resolve();
+ });
+ }
+ });
+
+ suite.addTestCase({
+ name: "WebInspector.cssPath.UniqueClassName",
+ description: "Elements with unique class names should include their class names.",
+ test(resolve, reject) {
+ nodeForSelector("#unique-class-test > .beta", (node) => {
+ InspectorTest.expectEqual(WebInspector.cssPath(node), "#unique-class-test > div.alpha.beta", "Elements with unique class names should include their class names.");
+ resolve();
+ });
+ }
+ });
+
+ suite.addTestCase({
+ name: "WebInspector.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(WebInspector.cssPath(node), "#non-unique-class-test > div:nth-child(2)", "Elements with non-unique class names should not include their class names.");
+ resolve();
+ });
+ }
+ });
+
+ suite.addTestCase({
+ name: "WebInspector.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(WebInspector.cssPath(node), "#unique-tag-and-class-test > div", "Elements with unique tag and class names should just have simple tag.");
+ resolve();
+ });
+ }
+ });
+
+ suite.addTestCase({
+ name: "WebInspector.cssPath.DeepPath",
+ description: "Tests for element with complex path.",
+ test(resolve, reject) {
+ nodeForSelector("small", (node) => {
+ InspectorTest.expectEqual(WebInspector.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();
+ });
+ }
+ });
+
+ suite.addTestCase({
+ name: "WebInspector.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(WebInspector.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(WebInspector.cssPath(pseudoElementAfter), "#pseudo-element-test > div:nth-child(3)::after", "Should be able to create path for ::after pseudo elements.");
+ resolve();
+ });
+ }
+ });
+
+ // FIXME: Write tests for nodes inside a Shadow DOM Tree.
+
+ WebInspector.domTreeManager.requestDocument((node) => {
+ documentNode = node;
+ suite.runTestCasesAndFinish();
+ });
+}
+</script>
+</head>
+<body onload="runTest()">
+<p>Test for WebInspector.cssPath.</p>
+<!-- If display:none pseudo elements are not created. -->
+<div style="visibility:hidden">
+ <div id="id-test">
+ <div></div>
+ </div>
+ <div id="input-test">
+ <input type="password">
+ </div>
+ <div id="unique-tag-test">
+ <div></div>
+ <span></span>
+ <div></div>
+ </div>
+ <div id="non-unique-tag-test">
+ <div></div>
+ <span></span>
+ <span></span>
+ <div></div>
+ </div>
+ <div id="unique-class-test">
+ <div class="alpha"></div>
+ <div class="alpha beta"></div>
+ <div class="alpha"></div>
+ </div>
+ <div id="non-unique-class-test">
+ <div class="alpha"></div>
+ <div class="alpha"></div>
+ <div class="alpha"></div>
+ </div>
+ <div id="unique-tag-and-class-test">
+ <div class="alpha"></div>
+ </div>
+ <div class="deep-path-test">
+ <ul>
+ <li>
+ <h1></h1>
+ <div></div>
+ <div></div>
+ <div>
+ <ul class="list">
+ <li></li>
+ <li class="active"><a href="#"><small></small></a></li>
+ <li></li>
+ </ul>
+ </div>
+ </li>
+ </ul>
+ </div>
+ <div id="pseudo-element-test">
+ <style>
+ #pseudo-element-test > div~div::before { content: "before"; }
+ #pseudo-element-test > div~div::after { content: "after"; }
+ </style>
+ <div></div>
+ <div></div>
+ </div>
+</div>
+</body>
+</html>
</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 > meta
+ #script-id
+ #test-script
+ body
+ body > p
+ body > article:nth-child(2)
+ body > article:nth-child(3)
+ #ids
+ #ids > div:nth-child(1)
+ #ids > div:nth-child(2)
+ #inner-id
+ #__proto__
+ [id="\#\"ridiculous\"\.id"]
+ [id="\'quoted\.value\'"]
+ #\.foo\.bar
+ #\-
+ #-a
+ [id="-\30 "]
+ [id="\37 "]
+ #ид
+ #ids > p
+ #classes
+ #classes > div:nth-child(1)
+ #classes > div:nth-child(2)
+ #classes > div.\.foo
+ #classes > div.\.foo\.bar
+ #classes > div.\-
+ #classes > div.-a
+ #classes > div.-\30
+ #classes > div.\37
+ #classes > div.класс
+ #classes > div:nth-child(10)
+ #classes > div:nth-child(11)
+ #classes > span
+ #id-with-class
+ #non-unique-classes
+ #non-unique-classes > span:nth-child(1)
+ #non-unique-classes > span:nth-child(2)
+ #non-unique-classes > span:nth-child(3)
+ #non-unique-classes > span:nth-child(4)
+ #non-unique-classes > span:nth-child(5)
+ #non-unique-classes > div:nth-child(6)
+ #non-unique-classes > div:nth-child(7)
+ #non-unique-classes > div:nth-child(8)
+ #non-unique-classes > div:nth-child(9)
+ #non-unique-classes > div:nth-child(10)
+ #non-unique-classes > div:nth-child(11)
+
+-- XPaths --
+/html
+ /html/head
+ /html/head/meta
+ //*[@id="script-id"]
+ //*[@id="test-script"]
+ //*[@id="test-script"]/text()
+ /html/body
+ /html/body/p
+ /html/body/p/text()
+ /html/body/article[1]
+ /html/body/article[2]
+ //*[@id="ids"]
+ //*[@id="ids"]/div[1]
+ //*[@id="ids"]/div[2]
+ //*[@id="inner-id"]
+ //*[@id="__proto__"]
+ //*[@id="#"ridiculous".id"]
+ //*[@id="'quoted.value'"]
+ //*[@id=".foo.bar"]
+ //*[@id="-"]
+ //*[@id="-a"]
+ //*[@id="-0"]
+ //*[@id="7"]
+ //*[@id="ид"]
+ //*[@id="ид"]/text()
+ //*[@id="ids"]/p
+ //*[@id="classes"]
+ //*[@id="classes"]/div[1]
+ //*[@id="classes"]/div[2]
+ //*[@id="classes"]/div[3]
+ //*[@id="classes"]/div[4]
+ //*[@id="classes"]/div[5]
+ //*[@id="classes"]/div[6]
+ //*[@id="classes"]/div[7]
+ //*[@id="classes"]/div[8]
+ //*[@id="classes"]/div[9]
+ //*[@id="classes"]/div[9]/text()
+ //*[@id="classes"]/div[10]
+ //*[@id="classes"]/div[11]
+ //*[@id="classes"]/span
+ //*[@id="id-with-class"]
+ //*[@id="non-unique-classes"]
+ //*[@id="non-unique-classes"]/span[1]
+ //*[@id="non-unique-classes"]/span[2]
+ //*[@id="non-unique-classes"]/span[3]
+ //*[@id="non-unique-classes"]/span[4]
+ //*[@id="non-unique-classes"]/span[5]
+ //*[@id="non-unique-classes"]/div[1]
+ //*[@id="non-unique-classes"]/div[2]
+ //*[@id="non-unique-classes"]/div[3]
+ //*[@id="non-unique-classes"]/div[4]
+ //*[@id="non-unique-classes"]/div[5]
+ //*[@id="non-unique-classes"]/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>+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<script src="../../http/tests/inspector/resources/inspector-test.js" id="script-id"></script>
+<script id="test-script">
+function verifySelector(selector) {
+ let nodes = document.querySelectorAll(selector);
+ if (nodes.length !== 1)
+ console.log("Selector was not unique: " + selector);
+}
+
+function verifyXPath(xpath) {
+ let nodes = document.evaluate(xpath, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
+ if (nodes.snapshotLength !== 1)
+ console.log("XPath was not unique: " + 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 = " ".repeat(depth * 2);
+ let path = func(node);
+ if (path) {
+ InspectorTest.log(prefix + path);
+ verifier(path);
+ }
+ }
+ }
+
+ WebInspector.domTreeManager.requestDocument((documentNode) => {
+ // Push all the nodes to the frontend.
+ WebInspector.domTreeManager.querySelector(documentNode.id, "html", (nodeId) => {
+ let htmlNode = WebInspector.domTreeManager.nodeForId(nodeId);
+ htmlNode.getSubtree(10, () => {
+ buildNodeList(htmlNode, 0);
+
+ InspectorTest.log("");
+ InspectorTest.log("-- CSS Selector Paths --");
+ processList(WebInspector.cssPath, (selector) => {
+ InspectorTest.evaluateInPage("verifySelector(" + JSON.stringify(selector) + ")");
+ });
+
+ InspectorTest.log("");
+ InspectorTest.log("-- XPaths --");
+ processList(WebInspector.xpath, (xpath) => {
+ InspectorTest.evaluateInPage("verifyXPath(" + JSON.stringify(xpath) + ")");
+ });
+
+ InspectorBackend.runAfterPendingDispatches(() => {
+ InspectorTest.completeTest();
+ })
+ });
+ });
+ });
+}
+</script>
+</head>
+<body onload="runTest()">
+<p>Test for WebInspector.cssPath.</p>
+
+<article></article>
+<article></article>
+
+<div id="ids">
+ <div></div>
+ <div></div>
+ <div id="inner-id"></div>
+ <div id="__proto__"></div>
+ <div id='#"ridiculous".id'></div>
+ <div id="'quoted.value'"></div>
+ <div id=".foo.bar"></div>
+ <div id="-"></div>
+ <div id="-a"></div>
+ <div id="-0"></div>
+ <div id="7"></div>
+ <div id="ид">ид</div>
+ <p></p>
+</div>
+
+<div id="classes">
+ <div class="foo bar"></div>
+ <div class=" foo foo "></div>
+ <div class=".foo"></div>
+ <div class=".foo.bar"></div>
+ <div class="-"></div>
+ <div class="-a"></div>
+ <div class="-0"></div>
+ <div class="7"></div>
+ <div class="класс">класс</div>
+ <div class="__proto__"></div>
+ <div class="__proto__ foo"></div>
+ <span class="bar"></span>
+ <div id="id-with-class" class="moo"></div>
+</div>
+
+<div id="non-unique-classes">
+ <span class="c1"></span>
+ <span class="c1"></span>
+ <span class="c1 c2"></span>
+ <span class="c1 c2 c3"></span>
+ <span></span>
+ <div class="c1"></div>
+ <div class="c1 c2"></div>
+ <div class="c3 c2"></div>
+ <div class="c3 c4"></div>
+ <div class="c1 c4"></div>
+ <div></div>
+</div>
+</body>
+</html>
</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="id-test"]'.
+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>+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../http/tests/inspector/resources/inspector-test.js"></script>
+<script>
+function test()
+{
+ let documentNode;
+
+ function nodeForSelector(selector, callback) {
+ WebInspector.domTreeManager.querySelector(documentNode.id, selector, (nodeId) => {
+ callback(WebInspector.domTreeManager.nodeForId(nodeId));
+ });
+ }
+
+ let suite = InspectorTest.createAsyncSuite("WebInspector.xpath");
+
+ suite.addTestCase({
+ name: "WebInspector.xpath.TopLevelNode",
+ description: "Top level nodes like html, body, and head are unique.",
+ test(resolve, reject) {
+ nodeForSelector("html", (node) => {
+ InspectorTest.expectEqual(WebInspector.xpath(node), "/html", "HTML element should have simple XPath '/html'.");
+ });
+ nodeForSelector("html > body", (node) => {
+ InspectorTest.expectEqual(WebInspector.xpath(node), "/html/body", "BODY element should have simple XPath '/html/body'.");
+ });
+ nodeForSelector("html > head", (node) => {
+ InspectorTest.expectEqual(WebInspector.xpath(node), "/html/head", "HEAD element should have simple XPath '/html/head'.");
+ resolve();
+ });
+ }
+ });
+
+ suite.addTestCase({
+ name: "WebInspector.xpath.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(WebInspector.xpath(node), "//*[@id=\"id-test\"]", "Element with id should have a single path component '//*[@id=\"id-test\"]'.");
+ });
+ nodeForSelector("#id-test > div", (node) => {
+ InspectorTest.expectEqual(WebInspector.xpath(node), "//*[@id=\"id-test\"]/div", "Element inside element with id should have path from id.");
+ resolve();
+ });
+ }
+ });
+
+ suite.addTestCase({
+ name: "WebInspector.xpath.UniqueTagName",
+ description: "Elements with unique tag name do not need nth-child.",
+ test(resolve, reject) {
+ nodeForSelector("#unique-tag-test > span", (node) => {
+ InspectorTest.expectEqual(WebInspector.xpath(node), "//*[@id=\"unique-tag-test\"]/span", "Elements with unique tag name should not need XPath index.");
+ resolve();
+ });
+ }
+ });
+
+ suite.addTestCase({
+ name: "WebInspector.xpath.NonUniqueTagName",
+ description: "Elements with non-unique tag name need index.",
+ test(resolve, reject) {
+ nodeForSelector("#non-unique-tag-test > span ~ span", (node) => {
+ InspectorTest.expectEqual(WebInspector.xpath(node), "//*[@id=\"non-unique-tag-test\"]/span[2]", "Elements with non-unique tag name should need XPath index.");
+ resolve();
+ });
+ }
+ });
+
+ suite.addTestCase({
+ name: "WebInspector.xpath.DeepPath",
+ description: "Tests for element with complex path.",
+ test(resolve, reject) {
+ nodeForSelector("small", (node) => {
+ InspectorTest.log(WebInspector.xpath(node));
+ InspectorTest.expectEqual(WebInspector.xpath(node), "/html/body/div/div[7]/ul/li/div[3]/ul/li[2]/a/small", "Should be able to get XPath for deep elements.");
+ resolve();
+ });
+ }
+ });
+
+ suite.addTestCase({
+ name: "WebInspector.xpath.TextAndCommentNode",
+ description: "Tests for non-Element nodes.",
+ test(resolve, reject) {
+ nodeForSelector("#non-element-test > p > br", (node) => {
+ let paragraphChildren = node.parentNode.children;
+ let lastTextChild = paragraphChildren[paragraphChildren.length - 1];
+ let lastCommentChild = paragraphChildren[paragraphChildren.length - 2];
+ InspectorTest.expectEqual(WebInspector.xpath(lastTextChild), "//*[@id=\"non-element-test\"]/p/text()[3]", "Should be able to get XPath for TEXT_NODE.");
+ InspectorTest.expectEqual(WebInspector.xpath(lastCommentChild), "//*[@id=\"non-element-test\"]/p/comment()", "Should be able to get XPath for COMMENT_NODE.");
+ resolve();
+ });
+ }
+ });
+
+ // FIXME: Write tests for nodes inside a Shadow DOM Tree.
+ // FIXME: Write test for CDATA.
+
+ WebInspector.domTreeManager.requestDocument((node) => {
+ documentNode = node;
+ suite.runTestCasesAndFinish();
+ });
+}
+</script>
+</head>
+<body onload="runTest()">
+<p>Test for WebInspector.xpath.</p>
+<!-- If display:none pseudo elements are not created. -->
+<div style="visibility:hidden">
+ <div id="id-test">
+ <div></div>
+ </div>
+ <div id="unique-tag-test">
+ <div></div>
+ <span></span>
+ <div></div>
+ </div>
+ <div id="non-unique-tag-test">
+ <div></div>
+ <span></span>
+ <span></span>
+ <div></div>
+ </div>
+ <div id="unique-class-test">
+ <div class="alpha"></div>
+ <div class="alpha beta"></div>
+ <div class="alpha"></div>
+ </div>
+ <div id="non-unique-class-test">
+ <div class="alpha"></div>
+ <div class="alpha"></div>
+ <div class="alpha"></div>
+ </div>
+ <div id="unique-tag-and-class-test">
+ <div class="alpha"></div>
+ </div>
+ <div class="deep-path-test">
+ <ul>
+ <li>
+ <h1></h1>
+ <div></div>
+ <div></div>
+ <div>
+ <ul class="list">
+ <li></li>
+ <li class="active"><a href="#"><small></small></a></li>
+ <li></li>
+ </ul>
+ </div>
+ </li>
+ </ul>
+ </div>
+ <div id="non-element-test">
+ <p>
+ Some leading text
+ <br>
+ Some trailing text
+ <!-- Comment -->
+ Some final text
+ </p>
+ </div>
+</div>
+</body>
+</html>
</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 <pecoraro@apple.com>
+
+ Web Inspector: Implement Copy CSS Selector and Copy Xpath Selector context menus
+ https://bugs.webkit.org/show_bug.cgi?id=158881
+ <rdar://problem/8181156>
+
+ 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 <nvasilyev@apple.com>
</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["Copy Row"] = "Copy Row";
</span><span class="cx"> localizedStrings["Copy Rule"] = "Copy Rule";
</span><span class="cx"> localizedStrings["Copy Selected"] = "Copy Selected";
</span><ins>+localizedStrings["Copy Selector Path"] = "Copy Selector Path";
</ins><span class="cx"> localizedStrings["Copy Table"] = "Copy Table";
</span><ins>+localizedStrings["Copy XPath"] = "Copy XPath";
</ins><span class="cx"> localizedStrings["Copy as HTML"] = "Copy as HTML";
</span><span class="cx"> localizedStrings["Copy as cURL"] = "Copy as cURL";
</span><span class="cx"> localizedStrings["Could not fetch properties. Object may no longer exist."] = "Could not fetch properties. Object may no longer exist.";
</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("http://www.w3.org/2000/svg", tagName);
</span><span class="cx"> }
</span><ins>+
+WebInspector.cssPath = function(node)
+{
+ console.assert(node instanceof WebInspector.DOMNode, "Expected a DOMNode.");
+ if (node.nodeType() !== Node.ELEMENT_NODE)
+ return "";
+
+ let suffix = "";
+ if (node.isPseudoElement()) {
+ suffix = "::" + 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) => x.value).join(" > ") + suffix;
+};
+
+WebInspector.cssPathComponent = function(node)
+{
+ console.assert(node instanceof WebInspector.DOMNode, "Expected a DOMNode.");
+ 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 === "body" || lowerNodeName === "head" || lowerNodeName === "html")
+ return {value: nodeName, done: true};
+
+ // #id is unique.
+ let id = node.getAttribute("id");
+ 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("class");
+ 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 === "input" && node.getAttribute("type") && !uniqueClasses.size)
+ selector += `[type="${node.getAttribute("type")}"]`;
+ 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, "Expected a DOMNode.");
+
+ if (node.nodeType() === Node.DOCUMENT_NODE)
+ return "/";
+
+ 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 && components[0].done ? "" : "/";
+ return prefix + components.map((x) => x.value).join("/");
+};
+
+WebInspector.xpathComponent = function(node)
+{
+ console.assert(node instanceof WebInspector.DOMNode, "Expected a DOMNode.");
+
+ let index = WebInspector.xpathIndex(node);
+ if (index === -1)
+ return null;
+
+ let value;
+
+ switch (node.nodeType()) {
+ case Node.DOCUMENT_NODE:
+ return {value: "", done: true};
+ case Node.ELEMENT_NODE:
+ var id = node.getAttribute("id");
+ if (id)
+ return {value: `//*[@id="${id}"]`, done: true};
+ value = node.localName();
+ break;
+ case Node.ATTRIBUTE_NODE:
+ value = `@${node.nodeName()}`;
+ break;
+ case Node.TEXT_NODE:
+ case Node.CDATA_SECTION_NODE:
+ value = "text()";
+ break;
+ case Node.COMMENT_NODE:
+ value = "comment()";
+ break;
+ case Node.PROCESSING_INSTRUCTION_NODE:
+ value = "processing-instruction()";
+ break
+ default:
+ value = "";
+ break;
+ }
+
+ if (index > 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 <= 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 && 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 > 0, "Should have found the node.");
+ 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("Edit as HTML"), this._editAsHTML.bind(this));
</span><del>- contextMenu.appendItem(WebInspector.UIString("Copy as HTML"), this._copyHTML.bind(this));
</del><ins>+ if (!node.isPseudoElement())
+ contextMenu.appendItem(WebInspector.UIString("Copy as HTML"), this._copyHTML.bind(this));
</ins><span class="cx"> if (this.editable)
</span><span class="cx"> contextMenu.appendItem(WebInspector.UIString("Delete Node"), 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("Scroll Into View"), this._scrollIntoView.bind(this));
</span><ins>+
+ contextMenu.appendSeparator();
+
+ if (node.nodeType() === Node.ELEMENT_NODE) {
+ contextMenu.appendItem(WebInspector.UIString("Copy Selector Path"), () => {
+ let cssPath = WebInspector.cssPath(this.representedObject);
+ InspectorFrontendHost.copyText(cssPath);
+ });
+ }
+
+ if (!node.isPseudoElement()) {
+ contextMenu.appendItem(WebInspector.UIString("Copy XPath"), () => {
+ 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("html-tag");
- var textNode = event.target.enclosingNodeOrSelfWithClass("html-text-node");
- var commentNode = event.target.enclosingNodeOrSelfWithClass("html-comment");
</del><ins>+ let tag = event.target.enclosingNodeOrSelfWithClass("html-tag");
+ let textNode = event.target.enclosingNodeOrSelfWithClass("html-text-node");
+ let commentNode = event.target.enclosingNodeOrSelfWithClass("html-comment");
+ let pseudoElement = event.target.enclosingNodeOrSelfWithClass("html-pseudo-element");
</ins><span class="cx">
</span><del>- var populated = false;
</del><ins>+ let populated = false;
</ins><span class="cx"> if (tag && 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 && treeElement._populateNodeContextMenu) {
</del><ins>+ } else if ((commentNode || pseudoElement) && 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>