2 * Copyright (C) 2011 Google Inc. All rights reserved.
3 * Copyright (C) 2007, 2008 Apple Inc. All rights reserved.
4 * Copyright (C) 2008 Matt Lilek <webkit@mattlilek.com>
5 * Copyright (C) 2009 Joseph Pecoraro
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
11 * 1. Redistributions of source code must retain the above copyright
12 * notice, this list of conditions and the following disclaimer.
13 * 2. Redistributions in binary form must reproduce the above copyright
14 * notice, this list of conditions and the following disclaimer in the
15 * documentation and/or other materials provided with the distribution.
16 * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of
17 * its contributors may be used to endorse or promote products derived
18 * from this software without specific prior written permission.
20 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
21 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
22 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
23 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
24 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
25 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
26 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
27 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
29 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 WebInspector.DOMPresentationUtils = {}
34 WebInspector.DOMPresentationUtils.decorateNodeLabel = function(node, parentElement)
36 var title = node.nodeNameInCorrectCase();
38 var nameElement = document.createElement("span");
39 nameElement.textContent = title;
40 parentElement.appendChild(nameElement);
42 var idAttribute = node.getAttribute("id");
44 var idElement = document.createElement("span");
45 parentElement.appendChild(idElement);
47 var part = "#" + idAttribute;
49 idElement.appendChild(document.createTextNode(part));
51 // Mark the name as extra, since the ID is more important.
52 nameElement.className = "extra";
55 var classAttribute = node.getAttribute("class");
57 var classes = classAttribute.split(/\s+/);
58 var foundClasses = {};
61 var classesElement = document.createElement("span");
62 classesElement.className = "extra";
63 parentElement.appendChild(classesElement);
65 for (var i = 0; i < classes.length; ++i) {
66 var className = classes[i];
67 if (className && !(className in foundClasses)) {
68 var part = "." + className;
70 classesElement.appendChild(document.createTextNode(part));
71 foundClasses[className] = true;
76 parentElement.title = title;
80 * @param {!Element} container
81 * @param {string} nodeTitle
83 WebInspector.DOMPresentationUtils.createSpansForNodeTitle = function(container, nodeTitle)
85 var match = nodeTitle.match(/([^#.]+)(#[^.]+)?(\..*)?/);
86 container.createChild("span", "webkit-html-tag-name").textContent = match[1];
88 container.createChild("span", "webkit-html-attribute-value").textContent = match[2];
90 container.createChild("span", "webkit-html-attribute-name").textContent = match[3];
93 WebInspector.DOMPresentationUtils.linkifyNodeReference = function(node)
95 var link = document.createElement("span");
96 link.className = "node-link";
97 WebInspector.DOMPresentationUtils.decorateNodeLabel(node, link);
99 link.addEventListener("click", WebInspector.domModel.inspectElement.bind(WebInspector.domModel, node.id), false);
100 link.addEventListener("mouseover", WebInspector.domModel.highlightDOMNode.bind(WebInspector.domModel, node.id, "", undefined), false);
101 link.addEventListener("mouseout", WebInspector.domModel.hideDOMNodeHighlight.bind(WebInspector.domModel), false);
106 WebInspector.DOMPresentationUtils.linkifyNodeById = function(nodeId)
108 var node = WebInspector.domModel.nodeForId(nodeId);
110 return document.createTextNode(WebInspector.UIString("<node>"));
111 return WebInspector.DOMPresentationUtils.linkifyNodeReference(node);
115 * @param {string} imageURL
116 * @param {boolean} showDimensions
117 * @param {function(!Element=)} userCallback
118 * @param {!Object=} precomputedDimensions
120 WebInspector.DOMPresentationUtils.buildImagePreviewContents = function(imageURL, showDimensions, userCallback, precomputedDimensions)
122 var resource = WebInspector.resourceTreeModel.resourceForURL(imageURL);
128 var imageElement = document.createElement("img");
129 imageElement.addEventListener("load", buildContent, false);
130 imageElement.addEventListener("error", errorCallback, false);
131 resource.populateImageSource(imageElement);
133 function errorCallback()
135 // Drop the event parameter when invoking userCallback.
139 function buildContent()
141 var container = document.createElement("table");
142 container.className = "image-preview-container";
143 var naturalWidth = precomputedDimensions ? precomputedDimensions.naturalWidth : imageElement.naturalWidth;
144 var naturalHeight = precomputedDimensions ? precomputedDimensions.naturalHeight : imageElement.naturalHeight;
145 var offsetWidth = precomputedDimensions ? precomputedDimensions.offsetWidth : naturalWidth;
146 var offsetHeight = precomputedDimensions ? precomputedDimensions.offsetHeight : naturalHeight;
148 if (showDimensions) {
149 if (offsetHeight === naturalHeight && offsetWidth === naturalWidth)
150 description = WebInspector.UIString("%d \xd7 %d pixels", offsetWidth, offsetHeight);
152 description = WebInspector.UIString("%d \xd7 %d pixels (Natural: %d \xd7 %d pixels)", offsetWidth, offsetHeight, naturalWidth, naturalHeight);
155 container.createChild("tr").createChild("td", "image-container").appendChild(imageElement);
157 container.createChild("tr").createChild("td").createChild("span", "description").textContent = description;
158 userCallback(container);
163 * @param {!WebInspector.DOMNode} node
164 * @param {boolean=} justSelector
167 WebInspector.DOMPresentationUtils.fullQualifiedSelector = function(node, justSelector)
169 if (node.nodeType() !== Node.ELEMENT_NODE)
170 return node.localName() || node.nodeName().toLowerCase();
171 return WebInspector.DOMPresentationUtils.cssPath(node, justSelector);
175 * @param {!WebInspector.DOMNode} node
178 WebInspector.DOMPresentationUtils.simpleSelector = function(node)
180 var lowerCaseName = node.localName() || node.nodeName().toLowerCase();
181 if (node.nodeType() !== Node.ELEMENT_NODE)
182 return lowerCaseName;
183 if (lowerCaseName === "input" && node.getAttribute("type") && !node.getAttribute("id") && !node.getAttribute("class"))
184 return lowerCaseName + "[type=\"" + node.getAttribute("type") + "\"]";
185 if (node.getAttribute("id"))
186 return lowerCaseName + "#" + node.getAttribute("id");
187 if (node.getAttribute("class"))
188 return lowerCaseName + "." + node.getAttribute("class").trim().replace(/\s+/g, ".");
189 return lowerCaseName;
193 * @param {!WebInspector.DOMNode} node
194 * @param {boolean=} optimized
197 WebInspector.DOMPresentationUtils.cssPath = function(node, optimized)
199 if (node.nodeType() !== Node.ELEMENT_NODE)
203 var contextNode = node;
204 while (contextNode) {
205 var step = WebInspector.DOMPresentationUtils._cssPathStep(contextNode, !!optimized, contextNode === node);
207 break; // Error - bail out early.
211 contextNode = contextNode.parentNode;
215 return steps.join(" > ");
219 * @param {!WebInspector.DOMNode} node
220 * @param {boolean} optimized
221 * @param {boolean} isTargetNode
222 * @return {?WebInspector.DOMNodePathStep}
224 WebInspector.DOMPresentationUtils._cssPathStep = function(node, optimized, isTargetNode)
226 if (node.nodeType() !== Node.ELEMENT_NODE)
229 var id = node.getAttribute("id");
232 return new WebInspector.DOMNodePathStep(idSelector(id), true);
233 var nodeNameLower = node.nodeName().toLowerCase();
234 if (nodeNameLower === "body" || nodeNameLower === "head" || nodeNameLower === "html")
235 return new WebInspector.DOMNodePathStep(node.nodeNameInCorrectCase(), true);
237 var nodeName = node.nodeNameInCorrectCase();
240 return new WebInspector.DOMNodePathStep(nodeName + idSelector(id), true);
241 var parent = node.parentNode;
242 if (!parent || parent.nodeType() === Node.DOCUMENT_NODE)
243 return new WebInspector.DOMNodePathStep(nodeName, true);
246 * @param {!WebInspector.DOMNode} node
247 * @return {!Array.<string>}
249 function prefixedElementClassNames(node)
251 var classAttribute = node.getAttribute("class");
255 return classAttribute.split(/\s+/g).filter(Boolean).map(function(name) {
256 // The prefix is required to store "__proto__" in a object-based map.
265 function idSelector(id)
267 return "#" + escapeIdentifierIfNeeded(id);
271 * @param {string} ident
274 function escapeIdentifierIfNeeded(ident)
276 if (isCSSIdentifier(ident))
278 var shouldEscapeFirst = /^(?:[0-9]|-[0-9-]?)/.test(ident);
279 var lastIndex = ident.length - 1;
280 return ident.replace(/./g, function(c, i) {
281 return ((shouldEscapeFirst && i === 0) || !isCSSIdentChar(c)) ? escapeAsciiChar(c, i === lastIndex) : c;
287 * @param {boolean} isLast
290 function escapeAsciiChar(c, isLast)
292 return "\\" + toHexByte(c) + (isLast ? "" : " ");
298 function toHexByte(c)
300 var hexByte = c.charCodeAt(0).toString(16);
301 if (hexByte.length === 1)
302 hexByte = "0" + hexByte;
310 function isCSSIdentChar(c)
312 if (/[a-zA-Z0-9_-]/.test(c))
314 return c.charCodeAt(0) >= 0xA0;
318 * @param {string} value
321 function isCSSIdentifier(value)
323 return /^-?[a-zA-Z_][a-zA-Z0-9_-]*$/.test(value);
326 var prefixedOwnClassNamesArray = prefixedElementClassNames(node);
327 var needsClassNames = false;
328 var needsNthChild = false;
330 var elementIndex = -1;
331 var siblings = parent.children();
332 for (var i = 0; (ownIndex === -1 || !needsNthChild) && i < siblings.length; ++i) {
333 var sibling = siblings[i];
334 if (sibling.nodeType() !== Node.ELEMENT_NODE)
337 if (sibling === node) {
338 ownIndex = elementIndex;
343 if (sibling.nodeNameInCorrectCase() !== nodeName)
346 needsClassNames = true;
347 var ownClassNames = prefixedOwnClassNamesArray.keySet();
348 var ownClassNameCount = 0;
349 for (var name in ownClassNames)
351 if (ownClassNameCount === 0) {
352 needsNthChild = true;
355 var siblingClassNamesArray = prefixedElementClassNames(sibling);
356 for (var j = 0; j < siblingClassNamesArray.length; ++j) {
357 var siblingClass = siblingClassNamesArray[j];
358 if (!ownClassNames.hasOwnProperty(siblingClass))
360 delete ownClassNames[siblingClass];
361 if (!--ownClassNameCount) {
362 needsNthChild = true;
368 var result = nodeName;
369 if (isTargetNode && nodeName.toLowerCase() === "input" && node.getAttribute("type") && !node.getAttribute("id") && !node.getAttribute("class"))
370 result += "[type=\"" + node.getAttribute("type") + "\"]";
372 result += ":nth-child(" + (ownIndex + 1) + ")";
373 } else if (needsClassNames) {
374 for (var prefixedName in prefixedOwnClassNamesArray.keySet())
375 result += "." + escapeIdentifierIfNeeded(prefixedName.substr(1));
378 return new WebInspector.DOMNodePathStep(result, false);
382 * @param {!WebInspector.DOMNode} node
383 * @param {boolean=} optimized
386 WebInspector.DOMPresentationUtils.xPath = function(node, optimized)
388 if (node.nodeType() === Node.DOCUMENT_NODE)
392 var contextNode = node;
393 while (contextNode) {
394 var step = WebInspector.DOMPresentationUtils._xPathValue(contextNode, optimized);
396 break; // Error - bail out early.
400 contextNode = contextNode.parentNode;
404 return (steps.length && steps[0].optimized ? "" : "/") + steps.join("/");
408 * @param {!WebInspector.DOMNode} node
409 * @param {boolean=} optimized
410 * @return {?WebInspector.DOMNodePathStep}
412 WebInspector.DOMPresentationUtils._xPathValue = function(node, optimized)
415 var ownIndex = WebInspector.DOMPresentationUtils._xPathIndex(node);
417 return null; // Error.
419 switch (node.nodeType()) {
420 case Node.ELEMENT_NODE:
421 if (optimized && node.getAttribute("id"))
422 return new WebInspector.DOMNodePathStep("//*[@id=\"" + node.getAttribute("id") + "\"]", true);
423 ownValue = node.localName();
425 case Node.ATTRIBUTE_NODE:
426 ownValue = "@" + node.nodeName();
429 case Node.CDATA_SECTION_NODE:
432 case Node.PROCESSING_INSTRUCTION_NODE:
433 ownValue = "processing-instruction()";
435 case Node.COMMENT_NODE:
436 ownValue = "comment()";
438 case Node.DOCUMENT_NODE:
447 ownValue += "[" + ownIndex + "]";
449 return new WebInspector.DOMNodePathStep(ownValue, node.nodeType() === Node.DOCUMENT_NODE);
453 * @param {!WebInspector.DOMNode} node
456 WebInspector.DOMPresentationUtils._xPathIndex = function(node)
458 // Returns -1 in case of error, 0 if no siblings matching the same expression, <XPath index among the same expression-matching sibling nodes> otherwise.
459 function areNodesSimilar(left, right)
464 if (left.nodeType() === Node.ELEMENT_NODE && right.nodeType() === Node.ELEMENT_NODE)
465 return left.localName() === right.localName();
467 if (left.nodeType() === right.nodeType())
470 // XPath treats CDATA as text nodes.
471 var leftType = left.nodeType() === Node.CDATA_SECTION_NODE ? Node.TEXT_NODE : left.nodeType();
472 var rightType = right.nodeType() === Node.CDATA_SECTION_NODE ? Node.TEXT_NODE : right.nodeType();
473 return leftType === rightType;
476 var siblings = node.parentNode ? node.parentNode.children() : null;
478 return 0; // Root node - no siblings.
479 var hasSameNamedElements;
480 for (var i = 0; i < siblings.length; ++i) {
481 if (areNodesSimilar(node, siblings[i]) && siblings[i] !== node) {
482 hasSameNamedElements = true;
486 if (!hasSameNamedElements)
488 var ownIndex = 1; // XPath indices start with 1.
489 for (var i = 0; i < siblings.length; ++i) {
490 if (areNodesSimilar(node, siblings[i])) {
491 if (siblings[i] === node)
496 return -1; // An error occurred: |node| not found in parent's children.
501 * @param {string} value
502 * @param {boolean} optimized
504 WebInspector.DOMNodePathStep = function(value, optimized)
507 this.optimized = optimized || false;
510 WebInspector.DOMNodePathStep.prototype = {