Commit cd99b22b authored by apavlov@chromium.org's avatar apavlov@chromium.org

Web Inspector: Introduce "Copy XPath" popup menu item for DOM elements

https://bugs.webkit.org/show_bug.cgi?id=77619

Reviewed by Vsevolod Vlasov.

Source/WebCore:

XPath is optimized whenever an element has the "id" attribute.

Test: inspector/elements/node-xpath.xhtml

* English.lproj/localizedStrings.js:
* inspector/front-end/DOMAgent.js:
(WebInspector.DOMNode.XPathStep):
(WebInspector.DOMNode.XPathStep.prototype.toString):
(WebInspector.DOMNode.prototype.copyXPath):
(WebInspector.DOMNode.prototype.isXMLNode):
(WebInspector.DOMNode.prototype.xPath):
(WebInspector.DOMNode.prototype._xPathValue):
(WebInspector.DOMNode.prototype._xPathIndex):
* inspector/front-end/ElementsTreeOutline.js:
(WebInspector.ElementsTreeElement.prototype._populateNodeContextMenu):

LayoutTests:

* inspector/elements/node-xpath-expected.txt: Added.
* inspector/elements/node-xpath.xhtml: Added.

git-svn-id: http://svn.webkit.org/repository/webkit/trunk@106664 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent af938000
2012-02-02 Alexander Pavlov <apavlov@chromium.org>
Web Inspector: Introduce "Copy XPath" popup menu item for DOM elements
https://bugs.webkit.org/show_bug.cgi?id=77619
Reviewed by Vsevolod Vlasov.
* inspector/elements/node-xpath-expected.txt: Added.
* inspector/elements/node-xpath.xhtml: Added.
2012-02-03 Yury Semikhatsky <yurys@chromium.org>
inspector/debugger/pause-in-inline-script.html asserts in chromium debug
Tests DOMNode.xPath()
3 Prefix Suffix
4
5
6
'#document':'' - '/'
'':' Pre-comment ' - '/comment()[1]'
'html':'' - '/html'
'head':'' - '/html/head'
'script':'' - '//*[@id="script-id"]'
'script':'' - '/html/head/script[2]'
'script':'' - '/html/head/script[4]'
'':'\n// Comment\n//' - '/html/head/script[4]/text()[1]'
'':'\nfunction f()\n{\n document.write("<");\n}\n//' - '/html/head/script[4]/text()[2]'
'body':'' - '/html/body'
'p':'' - '/html/body/p'
'':'Tests DOMNode.xPath()' - '/html/body/p/text()'
'div':'' - '//*[@id="id1"]'
'div':'' - '//*[@id="id2"]'
'div':'' - '//*[@id="container"]'
'div':'' - '//*[@id="id3"]'
'':'3 Prefix ' - '//*[@id="id3"]/text()[1]'
'':'<greeting>Hello, world!</greeting>' - '//*[@id="id3"]/text()[2]'
'':' Suffix' - '//*[@id="id3"]/text()[3]'
'div':'' - '//*[@id="id4"]'
'':'4' - '//*[@id="id4"]/text()'
'div':'' - '//*[@id="id5"]'
'':'5' - '//*[@id="id5"]/text()'
'div':'' - '//*[@id="id6"]'
'':'6' - '//*[@id="id6"]/text()'
'':' Post-comment ' - '/comment()[2]'
<!-- Pre-comment -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<script src="../../http/tests/inspector/inspector-test.js" id="script-id"></script>
<script src="../../http/tests/inspector/elements-test.js"></script>
<script id="test-script">
//<![CDATA[
function test()
{
InspectorTest.expandElementsTree(dumpNodes.bind(null, ""));
var doc;
function dumpNodes(prefix, node)
{
if (!doc) {
doc = getDocumentElement();
node = doc;
}
if (node.getAttribute("id") === "test-script")
return;
dumpNodeData(node, prefix);
var children = node.children;
for (var i = 0; children && i < children.length; ++i)
dumpNodes(prefix + " ", children[i]);
if (node === doc)
InspectorTest.completeTest();
}
function getDocumentElement()
{
var map = WebInspector.domAgent._idToDOMNode;
for (var id in map) {
if (map[id].nodeName() === "#document")
return map[id];
}
return null;
}
function dumpNodeData(node, prefix)
{
var result = prefix + "'" + node.nodeName() + "':'" + node.nodeValue() + "' - '" + node.xPath(true) + "'";
InspectorTest.addResult(result.replace(/\n/g, "\\n"));
}
}
//]]>
</script>
<script>
// Comment
//<![CDATA[
function f()
{
document.write("<");
}
//]]>
</script>
</head>
<body onload="runTest()">
<p>Tests DOMNode.xPath()</p>
<div id="id1" class="foo"></div>
<div id="id2" class="foo"></div>
<div id="container">
<div id="id3" class="foo">3 Prefix <![CDATA[<greeting>Hello, world!</greeting>]]> Suffix</div>
<div id="id4" class="foo">4</div>
<div id="id5" class="foo">5</div>
<div id="id6" class="foo">6</div>
</div>
</body>
</html>
<!-- Post-comment -->
2012-02-02 Alexander Pavlov <apavlov@chromium.org>
Web Inspector: Introduce "Copy XPath" popup menu item for DOM elements
https://bugs.webkit.org/show_bug.cgi?id=77619
Reviewed by Vsevolod Vlasov.
XPath is optimized whenever an element has the "id" attribute.
Test: inspector/elements/node-xpath.xhtml
* English.lproj/localizedStrings.js:
* inspector/front-end/DOMAgent.js:
(WebInspector.DOMNode.XPathStep):
(WebInspector.DOMNode.XPathStep.prototype.toString):
(WebInspector.DOMNode.prototype.copyXPath):
(WebInspector.DOMNode.prototype.isXMLNode):
(WebInspector.DOMNode.prototype.xPath):
(WebInspector.DOMNode.prototype._xPathValue):
(WebInspector.DOMNode.prototype._xPathIndex):
* inspector/front-end/ElementsTreeOutline.js:
(WebInspector.ElementsTreeElement.prototype._populateNodeContextMenu):
2012-02-03 Dana Jansens <danakj@chromium.org>
[Chromium] Use the current clip when marking paints as opaque
......@@ -84,6 +84,24 @@ WebInspector.DOMNode = function(domAgent, doc, payload) {
}
}
/**
* @constructor
* @param {string} value
* @param {boolean} optimized
*/
WebInspector.DOMNode.XPathStep = function(value, optimized)
{
this.value = value;
this.optimized = optimized;
}
WebInspector.DOMNode.XPathStep.prototype = {
toString: function()
{
return this.value;
}
}
WebInspector.DOMNode.prototype = {
/**
* @return {boolean}
......@@ -279,6 +297,14 @@ WebInspector.DOMNode.prototype = {
DOMAgent.getOuterHTML(this.id, copy);
},
/**
* @param {boolean} optimized
*/
copyXPath: function(optimized)
{
InspectorFrontendHost.copyText(this.xPath(optimized));
},
/**
* @param {function(?Protocol.Error)=} callback
*/
......@@ -489,6 +515,121 @@ WebInspector.DOMNode.prototype = {
isXMLNode: function()
{
return !!this.ownerDocument && !!this.ownerDocument.xmlVersion;
},
/**
* @param {boolean} optimized
* @return {string}
*/
xPath: function(optimized)
{
if (this._nodeType === Node.DOCUMENT_NODE)
return "/";
var steps = [];
var contextNode = this;
while (contextNode) {
var step = contextNode._xPathValue(optimized);
if (!step)
break; // Error - bail out early.
steps.push(step);
if (step.optimized)
break;
contextNode = contextNode.parentNode;
}
steps.reverse();
return (steps.length && steps[0].optimized ? "" : "/") + steps.join("/");
},
/**
* @param {boolean} optimized
* @return {WebInspector.DOMNode.XPathStep}
*/
_xPathValue: function(optimized)
{
var ownValue;
var ownIndex = this._xPathIndex();
if (ownIndex === -1)
return null; // Error.
switch (this._nodeType) {
case Node.ELEMENT_NODE:
if (optimized && this.getAttribute("id"))
return new WebInspector.DOMNode.XPathStep("//*[@id=\"" + this.getAttribute("id") + "\"]", true);
ownValue = this._localName;
break;
case Node.ATTRIBUTE_NODE:
ownValue = "@" + this._nodeName;
break;
case Node.TEXT_NODE:
case Node.CDATA_SECTION_NODE:
ownValue = "text()";
break;
case Node.PROCESSING_INSTRUCTION_NODE:
ownValue = "processing-instruction()";
break;
case Node.COMMENT_NODE:
ownValue = "comment()";
break;
case Node.DOCUMENT_NODE:
ownValue = "";
break;
default:
ownValue = "";
break;
}
if (ownIndex > 0)
ownValue += "[" + ownIndex + "]";
return new WebInspector.DOMNode.XPathStep(ownValue, this._nodeType === Node.DOCUMENT_NODE);
},
/**
* @return {number}
*/
_xPathIndex: function()
{
// Returns -1 in case of error, 0 if no siblings matching the same expression, <XPath index among the same expression-matching sibling nodes> otherwise.
function areNodesSimilar(left, right)
{
if (left === right)
return true;
if (left._nodeType === Node.ELEMENT_NODE && right._nodeType === Node.ELEMENT_NODE)
return left._localName === right._localName;
if (left._nodeType === right._nodeType)
return true;
// XPath treats CDATA as text nodes.
var leftType = left._nodeType === Node.CDATA_SECTION_NODE ? Node.TEXT_NODE : left._nodeType;
var rightType = right._nodeType === Node.CDATA_SECTION_NODE ? Node.TEXT_NODE : right._nodeType;
return leftType === rightType;
}
var siblings = this.parentNode ? this.parentNode.children : null;
if (!siblings)
return 0; // Root node - no siblings.
var hasSameNamedElements;
for (var i = 0; i < siblings.length; ++i) {
if (areNodesSimilar(this, siblings[i]) && siblings[i] !== this) {
hasSameNamedElements = true;
break;
}
}
if (!hasSameNamedElements)
return 0;
var ownIndex = 1; // XPath indices start with 1.
for (var i = 0; i < siblings.length; ++i) {
if (areNodesSimilar(this, siblings[i])) {
if (siblings[i] === this)
return ownIndex;
++ownIndex;
}
}
return -1; // An error occurred: |this| not found in parent's children.
}
}
......
......@@ -1087,6 +1087,7 @@ WebInspector.ElementsTreeElement.prototype = {
// Add free-form node-related actions.
contextMenu.appendItem(WebInspector.UIString("Edit as HTML"), this._editAsHTML.bind(this));
contextMenu.appendItem(WebInspector.UIString("Copy as HTML"), this._copyHTML.bind(this));
contextMenu.appendItem(WebInspector.UIString("Copy XPath"), this._copyXPath.bind(this));
contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Delete node" : "Delete Node"), this.remove.bind(this));
},
......@@ -1729,6 +1730,11 @@ WebInspector.ElementsTreeElement.prototype = {
this.representedObject.copyNode();
},
_copyXPath: function()
{
this.representedObject.copyXPath(true);
},
_highlightSearchResults: function()
{
if (!this._searchQuery || !this._searchHighlightsVisible)
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment