Diff
Modified: trunk/LayoutTests/ChangeLog (206058 => 206059)
--- trunk/LayoutTests/ChangeLog 2016-09-17 00:43:55 UTC (rev 206058)
+++ trunk/LayoutTests/ChangeLog 2016-09-17 02:14:53 UTC (rev 206059)
@@ -1,3 +1,18 @@
+2016-09-16 Joseph Pecoraro <pecor...@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.
+
2016-09-16 Jer Noble <jer.no...@apple.com>
Unreviewed gardening; enable newly passing media/media-source/ tests.
Added: trunk/LayoutTests/inspector/dom/domutilities-csspath-expected.txt (0 => 206059)
--- 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)
@@ -0,0 +1,38 @@
+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.
+
Added: trunk/LayoutTests/inspector/dom/domutilities-csspath.html (0 => 206059)
--- 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)
@@ -0,0 +1,211 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src=""
+<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=""
+ <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>
Added: trunk/LayoutTests/inspector/dom/domutilities-path-dump-expected.txt (0 => 206059)
--- 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)
@@ -0,0 +1,111 @@
+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]
+
Added: trunk/LayoutTests/inspector/dom/domutilities-path-dump.html (0 => 206059)
--- 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)
@@ -0,0 +1,122 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="utf-8">
+<script src="" 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>
Added: trunk/LayoutTests/inspector/dom/domutilities-xpath-expected.txt (0 => 206059)
--- 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)
@@ -0,0 +1,29 @@
+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.
+
Added: trunk/LayoutTests/inspector/dom/domutilities-xpath.html (0 => 206059)
--- 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)
@@ -0,0 +1,166 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src=""
+<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=""
+ <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>
Modified: trunk/Source/WebInspectorUI/ChangeLog (206058 => 206059)
--- 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)
@@ -1,3 +1,34 @@
+2016-09-16 Joseph Pecoraro <pecor...@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.
+
2016-09-16 Nikita Vasilyev <nvasil...@apple.com>
Web Inspector: Make console session dividers more pronounced
Modified: trunk/Source/WebInspectorUI/Localizations/en.lproj/localizedStrings.js (206058 => 206059)
--- 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)
@@ -208,7 +208,9 @@
localizedStrings["Copy Row"] = "Copy Row";
localizedStrings["Copy Rule"] = "Copy Rule";
localizedStrings["Copy Selected"] = "Copy Selected";
+localizedStrings["Copy Selector Path"] = "Copy Selector Path";
localizedStrings["Copy Table"] = "Copy Table";
+localizedStrings["Copy XPath"] = "Copy XPath";
localizedStrings["Copy as HTML"] = "Copy as HTML";
localizedStrings["Copy as cURL"] = "Copy as cURL";
localizedStrings["Could not fetch properties. Object may no longer exist."] = "Could not fetch properties. Object may no longer exist.";
Modified: trunk/Source/WebInspectorUI/UserInterface/Base/DOMUtilities.js (206058 => 206059)
--- 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)
@@ -80,3 +80,231 @@
{
return document.createElementNS("http://www.w3.org/2000/svg", tagName);
}
+
+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;
+};
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeElement.js (206058 => 206059)
--- 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)
@@ -710,16 +710,33 @@
_populateNodeContextMenu(contextMenu)
{
+ let node = this.representedObject;
+
// Add free-form node-related actions.
if (this.editable)
contextMenu.appendItem(WebInspector.UIString("Edit as HTML"), this._editAsHTML.bind(this));
- contextMenu.appendItem(WebInspector.UIString("Copy as HTML"), this._copyHTML.bind(this));
+ if (!node.isPseudoElement())
+ contextMenu.appendItem(WebInspector.UIString("Copy as HTML"), this._copyHTML.bind(this));
if (this.editable)
contextMenu.appendItem(WebInspector.UIString("Delete Node"), this.remove.bind(this));
-
- let node = this.representedObject;
if (node.nodeType() === Node.ELEMENT_NODE)
contextMenu.appendItem(WebInspector.UIString("Scroll Into View"), this._scrollIntoView.bind(this));
+
+ 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);
+ });
+ }
}
_startEditing()
Modified: trunk/Source/WebInspectorUI/UserInterface/Views/DOMTreeOutline.js (206058 => 206059)
--- 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)
@@ -240,11 +240,12 @@
populateContextMenu(contextMenu, event, treeElement)
{
- var tag = event.target.enclosingNodeOrSelfWithClass("html-tag");
- var textNode = event.target.enclosingNodeOrSelfWithClass("html-text-node");
- var commentNode = event.target.enclosingNodeOrSelfWithClass("html-comment");
+ 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");
- var populated = false;
+ let populated = false;
if (tag && treeElement._populateTagContextMenu) {
if (populated)
contextMenu.appendSeparator();
@@ -255,7 +256,7 @@
contextMenu.appendSeparator();
treeElement._populateTextContextMenu(contextMenu, textNode);
populated = true;
- } else if (commentNode && treeElement._populateNodeContextMenu) {
+ } else if ((commentNode || pseudoElement) && treeElement._populateNodeContextMenu) {
if (populated)
contextMenu.appendSeparator();
treeElement._populateNodeContextMenu(contextMenu);