| /* |
| * Copyright (C) 2011 Google Inc. All rights reserved. |
| * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. |
| * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com> |
| * Copyright (C) 2009 Joseph Pecoraro |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of |
| * its contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| WebInspector.DOMPresentationUtils = {} |
| |
| WebInspector.DOMPresentationUtils.decorateNodeLabel = function(node, parentElement) |
| { |
| var title = node.nodeNameInCorrectCase(); |
| |
| var nameElement = document.createElement("span"); |
| nameElement.textContent = title; |
| parentElement.appendChild(nameElement); |
| |
| var idAttribute = node.getAttribute("id"); |
| if (idAttribute) { |
| var idElement = document.createElement("span"); |
| parentElement.appendChild(idElement); |
| |
| var part = "#" + idAttribute; |
| title += part; |
| idElement.appendChild(document.createTextNode(part)); |
| |
| // Mark the name as extra, since the ID is more important. |
| nameElement.className = "extra"; |
| } |
| |
| var classAttribute = node.getAttribute("class"); |
| if (classAttribute) { |
| var classes = classAttribute.split(/\s+/); |
| var foundClasses = {}; |
| |
| if (classes.length) { |
| var classesElement = document.createElement("span"); |
| classesElement.className = "extra"; |
| parentElement.appendChild(classesElement); |
| |
| for (var i = 0; i < classes.length; ++i) { |
| var className = classes[i]; |
| if (className && !(className in foundClasses)) { |
| var part = "." + className; |
| title += part; |
| classesElement.appendChild(document.createTextNode(part)); |
| foundClasses[className] = true; |
| } |
| } |
| } |
| } |
| parentElement.title = title; |
| } |
| |
| /** |
| * @param {!Element} container |
| * @param {string} nodeTitle |
| */ |
| WebInspector.DOMPresentationUtils.createSpansForNodeTitle = function(container, nodeTitle) |
| { |
| var match = nodeTitle.match(/([^#.]+)(#[^.]+)?(\..*)?/); |
| container.createChild("span", "webkit-html-tag-name").textContent = match[1]; |
| if (match[2]) |
| container.createChild("span", "webkit-html-attribute-value").textContent = match[2]; |
| if (match[3]) |
| container.createChild("span", "webkit-html-attribute-name").textContent = match[3]; |
| } |
| |
| WebInspector.DOMPresentationUtils.linkifyNodeReference = function(node) |
| { |
| var link = document.createElement("span"); |
| link.className = "node-link"; |
| WebInspector.DOMPresentationUtils.decorateNodeLabel(node, link); |
| |
| link.addEventListener("click", WebInspector.domAgent.inspectElement.bind(WebInspector.domAgent, node.id), false); |
| link.addEventListener("mouseover", WebInspector.domAgent.highlightDOMNode.bind(WebInspector.domAgent, node.id, "", undefined), false); |
| link.addEventListener("mouseout", WebInspector.domAgent.hideDOMNodeHighlight.bind(WebInspector.domAgent), false); |
| |
| return link; |
| } |
| |
| WebInspector.DOMPresentationUtils.linkifyNodeById = function(nodeId) |
| { |
| var node = WebInspector.domAgent.nodeForId(nodeId); |
| if (!node) |
| return document.createTextNode(WebInspector.UIString("<node>")); |
| return WebInspector.DOMPresentationUtils.linkifyNodeReference(node); |
| } |
| |
| /** |
| * @param {string} imageURL |
| * @param {boolean} showDimensions |
| * @param {function(Element=)} userCallback |
| * @param {Object=} precomputedDimensions |
| */ |
| WebInspector.DOMPresentationUtils.buildImagePreviewContents = function(imageURL, showDimensions, userCallback, precomputedDimensions) |
| { |
| var resource = WebInspector.resourceTreeModel.resourceForURL(imageURL); |
| if (!resource) { |
| userCallback(); |
| return; |
| } |
| |
| var imageElement = document.createElement("img"); |
| imageElement.addEventListener("load", buildContent, false); |
| imageElement.addEventListener("error", errorCallback, false); |
| resource.populateImageSource(imageElement); |
| |
| function errorCallback() |
| { |
| // Drop the event parameter when invoking userCallback. |
| userCallback(); |
| } |
| |
| function buildContent() |
| { |
| var container = document.createElement("table"); |
| container.className = "image-preview-container"; |
| var naturalWidth = precomputedDimensions ? precomputedDimensions.naturalWidth : imageElement.naturalWidth; |
| var naturalHeight = precomputedDimensions ? precomputedDimensions.naturalHeight : imageElement.naturalHeight; |
| var offsetWidth = precomputedDimensions ? precomputedDimensions.offsetWidth : naturalWidth; |
| var offsetHeight = precomputedDimensions ? precomputedDimensions.offsetHeight : naturalHeight; |
| var description; |
| if (showDimensions) { |
| if (offsetHeight === naturalHeight && offsetWidth === naturalWidth) |
| description = WebInspector.UIString("%d \xd7 %d pixels", offsetWidth, offsetHeight); |
| else |
| description = WebInspector.UIString("%d \xd7 %d pixels (Natural: %d \xd7 %d pixels)", offsetWidth, offsetHeight, naturalWidth, naturalHeight); |
| } |
| |
| container.createChild("tr").createChild("td", "image-container").appendChild(imageElement); |
| if (description) |
| container.createChild("tr").createChild("td").createChild("span", "description").textContent = description; |
| userCallback(container); |
| } |
| } |
| |
| /** |
| * @param {!WebInspector.DOMNode} node |
| * @param {boolean=} justSelector |
| * @return {string} |
| */ |
| 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 (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(); |
| |
| 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; |
| }); |
| } |
| |
| /** |
| * @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; |
| } |
| } |
| } |
| |
| 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; |
| } |
| } |