summaryrefslogtreecommitdiffstats
path: root/chromium/third_party/WebKit/Source/devtools/front_end/components/DOMPresentationUtils.js
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/third_party/WebKit/Source/devtools/front_end/components/DOMPresentationUtils.js')
-rw-r--r--chromium/third_party/WebKit/Source/devtools/front_end/components/DOMPresentationUtils.js518
1 files changed, 518 insertions, 0 deletions
diff --git a/chromium/third_party/WebKit/Source/devtools/front_end/components/DOMPresentationUtils.js b/chromium/third_party/WebKit/Source/devtools/front_end/components/DOMPresentationUtils.js
new file mode 100644
index 00000000000..ac9ad80af30
--- /dev/null
+++ b/chromium/third_party/WebKit/Source/devtools/front_end/components/DOMPresentationUtils.js
@@ -0,0 +1,518 @@
+/*
+ * 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];
+}
+
+/**
+ * @param {?WebInspector.DOMNode} node
+ * @return {!Node}
+ */
+WebInspector.DOMPresentationUtils.linkifyNodeReference = function(node)
+{
+ if (!node)
+ return document.createTextNode(WebInspector.UIString("<node>"));
+
+ var link = document.createElement("span");
+ link.className = "node-link";
+ WebInspector.DOMPresentationUtils.decorateNodeLabel(node, link);
+
+ link.addEventListener("click", node.reveal.bind(node), false);
+ link.addEventListener("mouseover", node.highlight.bind(node, undefined, undefined), false);
+ link.addEventListener("mouseout", node.domModel().hideDOMNodeHighlight.bind(node.domModel()), false);
+
+ return link;
+}
+
+/**
+ * @param {string} imageURL
+ * @param {!WebInspector.Target} target
+ * @param {boolean} showDimensions
+ * @param {function(!Element=)} userCallback
+ * @param {!Object=} precomputedDimensions
+ */
+WebInspector.DOMPresentationUtils.buildImagePreviewContents = function(target, imageURL, showDimensions, userCallback, precomputedDimensions)
+{
+ var resource = target.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.fullQualifiedSelector = function(node, justSelector)
+{
+ if (node.nodeType() !== Node.ELEMENT_NODE)
+ return node.localName() || node.nodeName().toLowerCase();
+ return WebInspector.DOMPresentationUtils.cssPath(node, justSelector);
+}
+
+/**
+ * @param {!WebInspector.DOMNode} node
+ * @return {string}
+ */
+WebInspector.DOMPresentationUtils.simpleSelector = function(node)
+{
+ 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") + "\"]";
+ if (node.getAttribute("id"))
+ return lowerCaseName + "#" + node.getAttribute("id");
+ if (node.getAttribute("class"))
+ return (lowerCaseName === "div" ? "" : lowerCaseName) + "." + node.getAttribute("class").trim().replace(/\s+/g, ".");
+ return lowerCaseName;
+}
+
+/**
+ * @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._cssPathStep(contextNode, !!optimized, contextNode === node);
+ 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
+ * @param {boolean} isTargetNode
+ * @return {?WebInspector.DOMNodePathStep}
+ */
+WebInspector.DOMPresentationUtils._cssPathStep = function(node, optimized, isTargetNode)
+{
+ 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 elementIndex = -1;
+ var siblings = parent.children();
+ for (var i = 0; (ownIndex === -1 || !needsNthChild) && i < siblings.length; ++i) {
+ var sibling = siblings[i];
+ if (sibling.nodeType() !== Node.ELEMENT_NODE)
+ continue;
+ elementIndex += 1;
+ if (sibling === node) {
+ ownIndex = elementIndex;
+ 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 (isTargetNode && nodeName.toLowerCase() === "input" && node.getAttribute("type") && !node.getAttribute("id") && !node.getAttribute("class"))
+ result += "[type=\"" + node.getAttribute("type") + "\"]";
+ 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;
+ }
+}