| Index: Source/devtools/front_end/DOMPresentationUtils.js | 
| diff --git a/Source/devtools/front_end/DOMPresentationUtils.js b/Source/devtools/front_end/DOMPresentationUtils.js | 
| index 67f1392eb06d7ce309983879ec2b22ab16c2ab5f..7d25da830dd5accbce663387fea87d68a11f8620 100644 | 
| --- a/Source/devtools/front_end/DOMPresentationUtils.js | 
| +++ b/Source/devtools/front_end/DOMPresentationUtils.js | 
| @@ -167,21 +167,331 @@ WebInspector.DOMPresentationUtils.buildImagePreviewContents = function(imageURL, | 
| WebInspector.DOMPresentationUtils.appropriateSelectorFor = function(node, justSelector) | 
| { | 
| var lowerCaseName = node.localName() || node.nodeName().toLowerCase(); | 
| +    if (node.nodeType() !== Node.ELEMENT_NODE) | 
| +        return lowerCaseName; | 
| +    if (lowerCaseName === "input" && node.getAttribute("type") && !node.getAttribute("id") && !node.getAttribute("class")) | 
| +        return lowerCaseName + "[type=\"" + node.getAttribute("type") + "\"]"; | 
| + | 
| +    return WebInspector.DOMPresentationUtils.cssPath(node, justSelector); | 
| +} | 
| + | 
| +/** | 
| + * @param {!WebInspector.DOMNode} node | 
| + * @param {boolean=} optimized | 
| + * @return {string} | 
| + */ | 
| +WebInspector.DOMPresentationUtils.cssPath = function(node, optimized) | 
| +{ | 
| +    if (node.nodeType() !== Node.ELEMENT_NODE) | 
| +        return ""; | 
| + | 
| +    var steps = []; | 
| +    var contextNode = node; | 
| +    while (contextNode) { | 
| +        var step = WebInspector.DOMPresentationUtils._cssPathValue(contextNode, optimized); | 
| +        if (!step) | 
| +            break; // Error - bail out early. | 
| +        steps.push(step); | 
| +        if (step.optimized) | 
| +            break; | 
| +        contextNode = contextNode.parentNode; | 
| +    } | 
| + | 
| +    steps.reverse(); | 
| +    return steps.join(" > "); | 
| +} | 
| + | 
| +/** | 
| + * @param {!WebInspector.DOMNode} node | 
| + * @param {boolean=} optimized | 
| + * @return {WebInspector.DOMNodePathStep} | 
| + */ | 
| +WebInspector.DOMPresentationUtils._cssPathValue = function(node, optimized) | 
| +{ | 
| +    if (node.nodeType() !== Node.ELEMENT_NODE) | 
| +        return null; | 
|  | 
| var id = node.getAttribute("id"); | 
| -    if (id) { | 
| -        var selector = "#" + id; | 
| -        return (justSelector ? selector : lowerCaseName + selector); | 
| +    if (optimized) { | 
| +        if (id) | 
| +            return new WebInspector.DOMNodePathStep(idSelector(id), true); | 
| +        var nodeNameLower = node.nodeName().toLowerCase(); | 
| +        if (nodeNameLower === "body" || nodeNameLower === "head" || nodeNameLower === "html") | 
| +            return new WebInspector.DOMNodePathStep(node.nodeNameInCorrectCase(), true); | 
| } | 
| +    var nodeName = node.nodeNameInCorrectCase(); | 
|  | 
| -    var className = node.getAttribute("class"); | 
| -    if (className) { | 
| -        var selector = "." + className.trim().replace(/\s+/g, "."); | 
| -        return (justSelector ? selector : lowerCaseName + selector); | 
| +    if (id) | 
| +        return new WebInspector.DOMNodePathStep(nodeName + idSelector(id), true); | 
| +    var parent = node.parentNode; | 
| +    if (!parent || parent.nodeType() === Node.DOCUMENT_NODE) | 
| +        return new WebInspector.DOMNodePathStep(nodeName, true); | 
| + | 
| +    /** | 
| +     * @param {WebInspector.DOMNode} node | 
| +     * @return {Array.<string>} | 
| +     */ | 
| +    function prefixedElementClassNames(node) | 
| +    { | 
| +        var classAttribute = node.getAttribute("class"); | 
| +        if (!classAttribute) | 
| +            return []; | 
| + | 
| +        return classAttribute.split(/\s+/g).filter(Boolean).map(function(name) { | 
| +            // The prefix is required to store "__proto__" in a object-based map. | 
| +            return "$" + name; | 
| +        }); | 
| } | 
|  | 
| -    if (lowerCaseName === "input" && node.getAttribute("type")) | 
| -        return lowerCaseName + "[type=\"" + node.getAttribute("type") + "\"]"; | 
| +    /** | 
| +     * @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 prefixedOwnClassNamesArray = prefixedElementClassNames(node); | 
| +    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 === node) { | 
| +            ownIndex = i; | 
| +            continue; | 
| +        } | 
| +        if (needsNthChild) | 
| +            continue; | 
| +        if (sibling.nodeNameInCorrectCase() !== nodeName) | 
| +            continue; | 
| + | 
| +        needsClassNames = true; | 
| +        var ownClassNames = prefixedOwnClassNamesArray.keySet(); | 
| +        var ownClassNameCount = 0; | 
| +        for (var name in ownClassNames) | 
| +            ++ownClassNameCount; | 
| +        if (ownClassNameCount === 0) { | 
| +            needsNthChild = true; | 
| +            continue; | 
| +        } | 
| +        var siblingClassNamesArray = prefixedElementClassNames(sibling); | 
| +        for (var j = 0; j < siblingClassNamesArray.length; ++j) { | 
| +            var siblingClass = siblingClassNamesArray[j]; | 
| +            if (!ownClassNames.hasOwnProperty(siblingClass)) | 
| +                continue; | 
| +            delete ownClassNames[siblingClass]; | 
| +            if (!--ownClassNameCount) { | 
| +                needsNthChild = true; | 
| +                break; | 
| +            } | 
| +        } | 
| +    } | 
|  | 
| -    return lowerCaseName; | 
| +    var result = nodeName; | 
| +    if (needsNthChild) { | 
| +        result += ":nth-child(" + (ownIndex + 1) + ")"; | 
| +    } else if (needsClassNames) { | 
| +        for (var prefixedName in prefixedOwnClassNamesArray.keySet()) | 
| +            result += "." + escapeIdentifierIfNeeded(prefixedName.substr(1)); | 
| +    } | 
| + | 
| +    return new WebInspector.DOMNodePathStep(result, false); | 
| +} | 
| + | 
| +/** | 
| + * @param {!WebInspector.DOMNode} node | 
| + * @param {boolean=} optimized | 
| + * @return {string} | 
| + */ | 
| +WebInspector.DOMPresentationUtils.xPath = function(node, optimized) | 
| +{ | 
| +    if (node.nodeType() === Node.DOCUMENT_NODE) | 
| +        return "/"; | 
| + | 
| +    var steps = []; | 
| +    var contextNode = node; | 
| +    while (contextNode) { | 
| +        var step = WebInspector.DOMPresentationUtils._xPathValue(contextNode, 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 {!WebInspector.DOMNode} node | 
| + * @param {boolean=} optimized | 
| + * @return {WebInspector.DOMNodePathStep} | 
| + */ | 
| +WebInspector.DOMPresentationUtils._xPathValue = function(node, optimized) | 
| +{ | 
| +    var ownValue; | 
| +    var ownIndex = WebInspector.DOMPresentationUtils._xPathIndex(node); | 
| +    if (ownIndex === -1) | 
| +        return null; // Error. | 
| + | 
| +    switch (node.nodeType()) { | 
| +    case Node.ELEMENT_NODE: | 
| +        if (optimized && node.getAttribute("id")) | 
| +            return new WebInspector.DOMNodePathStep("//*[@id=\"" + node.getAttribute("id") + "\"]", true); | 
| +        ownValue = node.localName(); | 
| +        break; | 
| +    case Node.ATTRIBUTE_NODE: | 
| +        ownValue = "@" + node.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.DOMNodePathStep(ownValue, node.nodeType() === Node.DOCUMENT_NODE); | 
| +}, | 
| + | 
| +/** | 
| + * @param {!WebInspector.DOMNode} node | 
| + * @return {number} | 
| + */ | 
| +WebInspector.DOMPresentationUtils._xPathIndex = function(node) | 
| +{ | 
| +    // 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 = node.parentNode ? node.parentNode.children() : null; | 
| +    if (!siblings) | 
| +        return 0; // Root node - no siblings. | 
| +    var hasSameNamedElements; | 
| +    for (var i = 0; i < siblings.length; ++i) { | 
| +        if (areNodesSimilar(node, siblings[i]) && siblings[i] !== node) { | 
| +            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(node, siblings[i])) { | 
| +            if (siblings[i] === node) | 
| +                return ownIndex; | 
| +            ++ownIndex; | 
| +        } | 
| +    } | 
| +    return -1; // An error occurred: |node| not found in parent's children. | 
| +} | 
| + | 
| +/** | 
| + * @constructor | 
| + * @param {string} value | 
| + * @param {boolean} optimized | 
| + */ | 
| +WebInspector.DOMNodePathStep = function(value, optimized) | 
| +{ | 
| +    this.value = value; | 
| +    this.optimized = optimized || false; | 
| +} | 
| + | 
| +WebInspector.DOMNodePathStep.prototype = { | 
| +    /** | 
| +     * @return {string} | 
| +     */ | 
| +    toString: function() | 
| +    { | 
| +        return this.value; | 
| +    } | 
| } | 
|  |