 Chromium Code Reviews
 Chromium Code Reviews Issue 75253002:
  DevTools: [Elements] Implement "Copy CSS Path" context menu item for elements  (Closed) 
  Base URL: svn://svn.chromium.org/blink/trunk
    
  
    Issue 75253002:
  DevTools: [Elements] Implement "Copy CSS Path" context menu item for elements  (Closed) 
  Base URL: svn://svn.chromium.org/blink/trunk| Index: Source/devtools/front_end/DOMAgent.js | 
| diff --git a/Source/devtools/front_end/DOMAgent.js b/Source/devtools/front_end/DOMAgent.js | 
| index 320e88c01c7322119317b9cb53ee4c0e334ee025..f68cf3356da91f0a4065d6e9543cef795177e312 100644 | 
| --- a/Source/devtools/front_end/DOMAgent.js | 
| +++ b/Source/devtools/front_end/DOMAgent.js | 
| @@ -120,24 +120,6 @@ WebInspector.DOMNode.ShadowRootTypes = { | 
| Author: "author" | 
| } | 
| -/** | 
| - * @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 {Array.<WebInspector.DOMNode>} | 
| @@ -430,6 +412,14 @@ WebInspector.DOMNode.prototype = { | 
| /** | 
| * @param {boolean} optimized | 
| */ | 
| + copyCSSPath: function(optimized) | 
| + { | 
| + InspectorFrontendHost.copyText(this.cssPath(optimized)); | 
| + }, | 
| + | 
| + /** | 
| + * @param {boolean} optimized | 
| + */ | 
| copyXPath: function(optimized) | 
| { | 
| InspectorFrontendHost.copyText(this.xPath(optimized)); | 
| @@ -466,23 +456,12 @@ WebInspector.DOMNode.prototype = { | 
| appropriateSelectorFor: function(justSelector) | 
| { | 
| var lowerCaseName = this.localName() || this.nodeName().toLowerCase(); | 
| - | 
| - var id = this.getAttribute("id"); | 
| - if (id) { | 
| - var selector = "#" + id; | 
| - return (justSelector ? selector : lowerCaseName + selector); | 
| - } | 
| - | 
| - var className = this.getAttribute("class"); | 
| - if (className) { | 
| - var selector = "." + className.trim().replace(/\s+/g, "."); | 
| - return (justSelector ? selector : lowerCaseName + selector); | 
| - } | 
| - | 
| + if (this._nodeType !== Node.ELEMENT_NODE) | 
| + return lowerCaseName; | 
| if (lowerCaseName === "input" && this.getAttribute("type")) | 
| return lowerCaseName + "[type=\"" + this.getAttribute("type") + "\"]"; | 
| - return lowerCaseName; | 
| + return this.cssPath(justSelector); | 
| }, | 
| /** | 
| @@ -685,6 +664,177 @@ WebInspector.DOMNode.prototype = { | 
| * @param {boolean} optimized | 
| * @return {string} | 
| */ | 
| + cssPath: function(optimized) | 
| + { | 
| + if (this._nodeType !== Node.ELEMENT_NODE) | 
| + return ""; | 
| + | 
| + var steps = []; | 
| + var contextNode = this; | 
| + while (contextNode) { | 
| + var step = WebInspector.DOMPresentationUtils.cssPathValue(contextNode, optimized); | 
| 
aandrey
2013/11/21 10:17:34
there is a dependency problem: "sdk" module (conta
 | 
| + if (!step) | 
| + break; // Error - bail out early. | 
| + steps.push(step); | 
| + if (step.optimized) | 
| + break; | 
| + contextNode = contextNode.parentNode; | 
| + } | 
| + | 
| + steps.reverse(); | 
| + return steps.join(" > "); | 
| + }, | 
| + | 
| + /** | 
| + * @param {boolean} optimized | 
| + * @return {WebInspector.DOMNodePathStep} | 
| + */ | 
| + _cssPathValue: function(optimized) | 
| 
aandrey
2013/11/21 10:17:34
remove this method
 | 
| + { | 
| + if (this._nodeType !== Node.ELEMENT_NODE) | 
| + return null; | 
| + if (optimized) { | 
| + var id = this.getAttribute("id"); | 
| + if (id) | 
| + return new WebInspector.DOMNodePathStep(idSelector(id), true); | 
| + var nodeNameLower = this._nodeName.toLowerCase(); | 
| + if (nodeNameLower === "body" || nodeNameLower === "head" || nodeNameLower === "html") | 
| + return new WebInspector.DOMNodePathStep(this.nodeNameInCorrectCase(), true); | 
| + } | 
| + var nodeName = this.nodeNameInCorrectCase(); | 
| + var parent = this.parentNode; | 
| + if (!parent || parent._nodeType === Node.DOCUMENT_NODE) | 
| + return new WebInspector.DOMNodePathStep(nodeName, true); | 
| + | 
| + /** | 
| + * @param {WebInspector.DOMNode} node | 
| + * @return {Array.<string>} | 
| + */ | 
| + function elementClassNames(node) | 
| + { | 
| + var classAttribute = node.getAttribute("class"); | 
| + if (!classAttribute) | 
| + return []; | 
| + | 
| + return classAttribute.split(/\s+/g).filter(Boolean); | 
| + } | 
| + | 
| + /** | 
| + * @param {string} id | 
| + * @return {string} | 
| + */ | 
| + function idSelector(id) | 
| + { | 
| + return "#" + escapeIdentifierIfNeeded(id); | 
| + } | 
| + | 
| + /** | 
| + * @param {string} ident | 
| + * @return {string} | 
| + */ | 
| + function escapeIdentifierIfNeeded(ident) | 
| + { | 
| + if (isCSSIdentifier(ident)) | 
| + return ident; | 
| + var shouldEscapeFirst = /^(?:[0-9]|-[0-9-]?)/.test(ident); | 
| + var lastIndex = ident.length - 1; | 
| + return ident.replace(/./g, function(c, i) { | 
| + return ((shouldEscapeFirst && i === 0) || !isCSSIdentChar(c)) ? escapeAsciiChar(c, i === lastIndex) : c; | 
| + }); | 
| + } | 
| + | 
| + /** | 
| + * @param {string} c | 
| + * @param {boolean} isLast | 
| + * @return {string} | 
| + */ | 
| + function escapeAsciiChar(c, isLast) | 
| + { | 
| + return "\\" + toHexByte(c) + (isLast ? "" : " "); | 
| + } | 
| + | 
| + /** | 
| + * @param {string} c | 
| + */ | 
| + function toHexByte(c) | 
| + { | 
| + var hexByte = c.charCodeAt(0).toString(16); | 
| + if (hexByte.length === 1) | 
| + hexByte = "0" + hexByte; | 
| + return hexByte; | 
| + } | 
| + | 
| + /** | 
| + * @param {string} c | 
| + * @return {boolean} | 
| + */ | 
| + function isCSSIdentChar(c) | 
| + { | 
| + if (/[a-zA-Z0-9_-]/.test(c)) | 
| + return true; | 
| + return c.charCodeAt(0) >= 0xA0; | 
| + } | 
| + | 
| + /** | 
| + * @param {string} value | 
| + * @return {boolean} | 
| + */ | 
| + function isCSSIdentifier(value) | 
| + { | 
| + return /^-?[a-zA-Z_][a-zA-Z0-9_-]*$/.test(value); | 
| + } | 
| + | 
| + var uniqueClassNamesArray = elementClassNames(this); | 
| + var needsClassNames = false; | 
| + var needsNthChild = false; | 
| + var ownIndex = -1; | 
| + var siblings = parent.children(); | 
| + for (var i = 0; (ownIndex === -1 || !needsNthChild) && i < siblings.length; ++i) { | 
| + var sibling = siblings[i]; | 
| + if (sibling === this) { | 
| + ownIndex = i; | 
| + continue; | 
| + } | 
| + if (needsNthChild) | 
| + continue; | 
| + if (sibling.nodeNameInCorrectCase() !== nodeName) | 
| + continue; | 
| + | 
| + needsClassNames = true; | 
| + var ownClassNames = uniqueClassNamesArray.keySet(); | 
| + var ownClassNameCount = 0; | 
| + for (var name in ownClassNames) | 
| + ++ownClassNameCount; | 
| + if (ownClassNameCount === 0) { | 
| + needsNthChild = true; | 
| + continue; | 
| + } | 
| + var siblingClassNames = elementClassNames(sibling).keySet(); | 
| + for (var siblingClass in siblingClassNames) { | 
| + if (!ownClassNames.hasOwnProperty(siblingClass)) | 
| + continue; | 
| + delete ownClassNames[siblingClass]; | 
| + if (!--ownClassNameCount) { | 
| + needsNthChild = true; | 
| + break; | 
| + } | 
| + } | 
| + } | 
| + | 
| + var result = nodeName; | 
| + if (needsNthChild) { | 
| + result += ":nth-child(" + (ownIndex + 1) + ")"; | 
| + } else if (needsClassNames) { | 
| + for (var name in uniqueClassNamesArray.keySet()) | 
| + result += "." + escapeIdentifierIfNeeded(name); | 
| + } | 
| + return new WebInspector.DOMNodePathStep(result, false); | 
| + }, | 
| + | 
| + /** | 
| + * @param {boolean} optimized | 
| + * @return {string} | 
| + */ | 
| xPath: function(optimized) | 
| { | 
| if (this._nodeType === Node.DOCUMENT_NODE) | 
| @@ -708,7 +858,7 @@ WebInspector.DOMNode.prototype = { | 
| /** | 
| * @param {boolean} optimized | 
| - * @return {WebInspector.DOMNode.XPathStep} | 
| + * @return {WebInspector.DOMNodePathStep} | 
| */ | 
| _xPathValue: function(optimized) | 
| { | 
| @@ -720,7 +870,7 @@ WebInspector.DOMNode.prototype = { | 
| switch (this._nodeType) { | 
| case Node.ELEMENT_NODE: | 
| if (optimized && this.getAttribute("id")) | 
| - return new WebInspector.DOMNode.XPathStep("//*[@id=\"" + this.getAttribute("id") + "\"]", true); | 
| + return new WebInspector.DOMNodePathStep("//*[@id=\"" + this.getAttribute("id") + "\"]", true); | 
| ownValue = this._localName; | 
| break; | 
| case Node.ATTRIBUTE_NODE: | 
| @@ -747,7 +897,7 @@ WebInspector.DOMNode.prototype = { | 
| if (ownIndex > 0) | 
| ownValue += "[" + ownIndex + "]"; | 
| - return new WebInspector.DOMNode.XPathStep(ownValue, this._nodeType === Node.DOCUMENT_NODE); | 
| + return new WebInspector.DOMNodePathStep(ownValue, this._nodeType === Node.DOCUMENT_NODE); | 
| }, | 
| /** |