/* * Copyright (C) 2011 Google Inc. All rights reserved. * Copyright (C) 2007, 2008 Apple Inc. All rights reserved. * Copyright (C) 2008 Matt Lilek * 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("")); 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.} */ 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, 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; } }