Upstream version 7.35.144.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Source / devtools / front_end / DOMPresentationUtils.js
1 /*
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
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  *
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.
19  *
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.
30  */
31
32 WebInspector.DOMPresentationUtils = {}
33
34 WebInspector.DOMPresentationUtils.decorateNodeLabel = function(node, parentElement)
35 {
36     var title = node.nodeNameInCorrectCase();
37
38     var nameElement = document.createElement("span");
39     nameElement.textContent = title;
40     parentElement.appendChild(nameElement);
41
42     var idAttribute = node.getAttribute("id");
43     if (idAttribute) {
44         var idElement = document.createElement("span");
45         parentElement.appendChild(idElement);
46
47         var part = "#" + idAttribute;
48         title += part;
49         idElement.appendChild(document.createTextNode(part));
50
51         // Mark the name as extra, since the ID is more important.
52         nameElement.className = "extra";
53     }
54
55     var classAttribute = node.getAttribute("class");
56     if (classAttribute) {
57         var classes = classAttribute.split(/\s+/);
58         var foundClasses = {};
59
60         if (classes.length) {
61             var classesElement = document.createElement("span");
62             classesElement.className = "extra";
63             parentElement.appendChild(classesElement);
64
65             for (var i = 0; i < classes.length; ++i) {
66                 var className = classes[i];
67                 if (className && !(className in foundClasses)) {
68                     var part = "." + className;
69                     title += part;
70                     classesElement.appendChild(document.createTextNode(part));
71                     foundClasses[className] = true;
72                 }
73             }
74         }
75     }
76     parentElement.title = title;
77 }
78
79 /**
80  * @param {!Element} container
81  * @param {string} nodeTitle
82  */
83 WebInspector.DOMPresentationUtils.createSpansForNodeTitle = function(container, nodeTitle)
84 {
85     var match = nodeTitle.match(/([^#.]+)(#[^.]+)?(\..*)?/);
86     container.createChild("span", "webkit-html-tag-name").textContent = match[1];
87     if (match[2])
88         container.createChild("span", "webkit-html-attribute-value").textContent = match[2];
89     if (match[3])
90         container.createChild("span", "webkit-html-attribute-name").textContent = match[3];
91 }
92
93 WebInspector.DOMPresentationUtils.linkifyNodeReference = function(node)
94 {
95     var link = document.createElement("span");
96     link.className = "node-link";
97     WebInspector.DOMPresentationUtils.decorateNodeLabel(node, link);
98
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);
102
103     return link;
104 }
105
106 WebInspector.DOMPresentationUtils.linkifyNodeById = function(nodeId)
107 {
108     var node = WebInspector.domModel.nodeForId(nodeId);
109     if (!node)
110         return document.createTextNode(WebInspector.UIString("<node>"));
111     return WebInspector.DOMPresentationUtils.linkifyNodeReference(node);
112 }
113
114 /**
115  * @param {string} imageURL
116  * @param {boolean} showDimensions
117  * @param {function(!Element=)} userCallback
118  * @param {!Object=} precomputedDimensions
119  */
120 WebInspector.DOMPresentationUtils.buildImagePreviewContents = function(imageURL, showDimensions, userCallback, precomputedDimensions)
121 {
122     var resource = WebInspector.resourceTreeModel.resourceForURL(imageURL);
123     if (!resource) {
124         userCallback();
125         return;
126     }
127
128     var imageElement = document.createElement("img");
129     imageElement.addEventListener("load", buildContent, false);
130     imageElement.addEventListener("error", errorCallback, false);
131     resource.populateImageSource(imageElement);
132
133     function errorCallback()
134     {
135         // Drop the event parameter when invoking userCallback.
136         userCallback();
137     }
138
139     function buildContent()
140     {
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;
147         var description;
148         if (showDimensions) {
149             if (offsetHeight === naturalHeight && offsetWidth === naturalWidth)
150                 description = WebInspector.UIString("%d \xd7 %d pixels", offsetWidth, offsetHeight);
151             else
152                 description = WebInspector.UIString("%d \xd7 %d pixels (Natural: %d \xd7 %d pixels)", offsetWidth, offsetHeight, naturalWidth, naturalHeight);
153         }
154
155         container.createChild("tr").createChild("td", "image-container").appendChild(imageElement);
156         if (description)
157             container.createChild("tr").createChild("td").createChild("span", "description").textContent = description;
158         userCallback(container);
159     }
160 }
161
162 /**
163  * @param {!WebInspector.DOMNode} node
164  * @param {boolean=} justSelector
165  * @return {string}
166  */
167 WebInspector.DOMPresentationUtils.fullQualifiedSelector = function(node, justSelector)
168 {
169     if (node.nodeType() !== Node.ELEMENT_NODE)
170         return node.localName() || node.nodeName().toLowerCase();
171     return WebInspector.DOMPresentationUtils.cssPath(node, justSelector);
172 }
173
174 /**
175  * @param {!WebInspector.DOMNode} node
176  * @return {string}
177  */
178 WebInspector.DOMPresentationUtils.simpleSelector = function(node)
179 {
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;
190 }
191
192 /**
193  * @param {!WebInspector.DOMNode} node
194  * @param {boolean=} optimized
195  * @return {string}
196  */
197 WebInspector.DOMPresentationUtils.cssPath = function(node, optimized)
198 {
199     if (node.nodeType() !== Node.ELEMENT_NODE)
200         return "";
201
202     var steps = [];
203     var contextNode = node;
204     while (contextNode) {
205         var step = WebInspector.DOMPresentationUtils._cssPathStep(contextNode, !!optimized, contextNode === node);
206         if (!step)
207             break; // Error - bail out early.
208         steps.push(step);
209         if (step.optimized)
210             break;
211         contextNode = contextNode.parentNode;
212     }
213
214     steps.reverse();
215     return steps.join(" > ");
216 }
217
218 /**
219  * @param {!WebInspector.DOMNode} node
220  * @param {boolean} optimized
221  * @param {boolean} isTargetNode
222  * @return {?WebInspector.DOMNodePathStep}
223  */
224 WebInspector.DOMPresentationUtils._cssPathStep = function(node, optimized, isTargetNode)
225 {
226     if (node.nodeType() !== Node.ELEMENT_NODE)
227         return null;
228
229     var id = node.getAttribute("id");
230     if (optimized) {
231         if (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);
236     }
237     var nodeName = node.nodeNameInCorrectCase();
238
239     if (id)
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);
244
245     /**
246      * @param {!WebInspector.DOMNode} node
247      * @return {!Array.<string>}
248      */
249     function prefixedElementClassNames(node)
250     {
251         var classAttribute = node.getAttribute("class");
252         if (!classAttribute)
253             return [];
254
255         return classAttribute.split(/\s+/g).filter(Boolean).map(function(name) {
256             // The prefix is required to store "__proto__" in a object-based map.
257             return "$" + name;
258         });
259     }
260
261     /**
262      * @param {string} id
263      * @return {string}
264      */
265     function idSelector(id)
266     {
267         return "#" + escapeIdentifierIfNeeded(id);
268     }
269
270     /**
271      * @param {string} ident
272      * @return {string}
273      */
274     function escapeIdentifierIfNeeded(ident)
275     {
276         if (isCSSIdentifier(ident))
277             return 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;
282         });
283     }
284
285     /**
286      * @param {string} c
287      * @param {boolean} isLast
288      * @return {string}
289      */
290     function escapeAsciiChar(c, isLast)
291     {
292         return "\\" + toHexByte(c) + (isLast ? "" : " ");
293     }
294
295     /**
296      * @param {string} c
297      */
298     function toHexByte(c)
299     {
300         var hexByte = c.charCodeAt(0).toString(16);
301         if (hexByte.length === 1)
302           hexByte = "0" + hexByte;
303         return hexByte;
304     }
305
306     /**
307      * @param {string} c
308      * @return {boolean}
309      */
310     function isCSSIdentChar(c)
311     {
312         if (/[a-zA-Z0-9_-]/.test(c))
313             return true;
314         return c.charCodeAt(0) >= 0xA0;
315     }
316
317     /**
318      * @param {string} value
319      * @return {boolean}
320      */
321     function isCSSIdentifier(value)
322     {
323         return /^-?[a-zA-Z_][a-zA-Z0-9_-]*$/.test(value);
324     }
325
326     var prefixedOwnClassNamesArray = prefixedElementClassNames(node);
327     var needsClassNames = false;
328     var needsNthChild = false;
329     var ownIndex = -1;
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)
335             continue;
336         elementIndex += 1;
337         if (sibling === node) {
338             ownIndex = elementIndex;
339             continue;
340         }
341         if (needsNthChild)
342             continue;
343         if (sibling.nodeNameInCorrectCase() !== nodeName)
344             continue;
345
346         needsClassNames = true;
347         var ownClassNames = prefixedOwnClassNamesArray.keySet();
348         var ownClassNameCount = 0;
349         for (var name in ownClassNames)
350             ++ownClassNameCount;
351         if (ownClassNameCount === 0) {
352             needsNthChild = true;
353             continue;
354         }
355         var siblingClassNamesArray = prefixedElementClassNames(sibling);
356         for (var j = 0; j < siblingClassNamesArray.length; ++j) {
357             var siblingClass = siblingClassNamesArray[j];
358             if (!ownClassNames.hasOwnProperty(siblingClass))
359                 continue;
360             delete ownClassNames[siblingClass];
361             if (!--ownClassNameCount) {
362                 needsNthChild = true;
363                 break;
364             }
365         }
366     }
367
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") + "\"]";
371     if (needsNthChild) {
372         result += ":nth-child(" + (ownIndex + 1) + ")";
373     } else if (needsClassNames) {
374         for (var prefixedName in prefixedOwnClassNamesArray.keySet())
375             result += "." + escapeIdentifierIfNeeded(prefixedName.substr(1));
376     }
377
378     return new WebInspector.DOMNodePathStep(result, false);
379 }
380
381 /**
382  * @param {!WebInspector.DOMNode} node
383  * @param {boolean=} optimized
384  * @return {string}
385  */
386 WebInspector.DOMPresentationUtils.xPath = function(node, optimized)
387 {
388     if (node.nodeType() === Node.DOCUMENT_NODE)
389         return "/";
390
391     var steps = [];
392     var contextNode = node;
393     while (contextNode) {
394         var step = WebInspector.DOMPresentationUtils._xPathValue(contextNode, optimized);
395         if (!step)
396             break; // Error - bail out early.
397         steps.push(step);
398         if (step.optimized)
399             break;
400         contextNode = contextNode.parentNode;
401     }
402
403     steps.reverse();
404     return (steps.length && steps[0].optimized ? "" : "/") + steps.join("/");
405 }
406
407 /**
408  * @param {!WebInspector.DOMNode} node
409  * @param {boolean=} optimized
410  * @return {?WebInspector.DOMNodePathStep}
411  */
412 WebInspector.DOMPresentationUtils._xPathValue = function(node, optimized)
413 {
414     var ownValue;
415     var ownIndex = WebInspector.DOMPresentationUtils._xPathIndex(node);
416     if (ownIndex === -1)
417         return null; // Error.
418
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();
424         break;
425     case Node.ATTRIBUTE_NODE:
426         ownValue = "@" + node.nodeName();
427         break;
428     case Node.TEXT_NODE:
429     case Node.CDATA_SECTION_NODE:
430         ownValue = "text()";
431         break;
432     case Node.PROCESSING_INSTRUCTION_NODE:
433         ownValue = "processing-instruction()";
434         break;
435     case Node.COMMENT_NODE:
436         ownValue = "comment()";
437         break;
438     case Node.DOCUMENT_NODE:
439         ownValue = "";
440         break;
441     default:
442         ownValue = "";
443         break;
444     }
445
446     if (ownIndex > 0)
447         ownValue += "[" + ownIndex + "]";
448
449     return new WebInspector.DOMNodePathStep(ownValue, node.nodeType() === Node.DOCUMENT_NODE);
450 },
451
452 /**
453  * @param {!WebInspector.DOMNode} node
454  * @return {number}
455  */
456 WebInspector.DOMPresentationUtils._xPathIndex = function(node)
457 {
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)
460     {
461         if (left === right)
462             return true;
463
464         if (left.nodeType() === Node.ELEMENT_NODE && right.nodeType() === Node.ELEMENT_NODE)
465             return left.localName() === right.localName();
466
467         if (left.nodeType() === right.nodeType())
468             return true;
469
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;
474     }
475
476     var siblings = node.parentNode ? node.parentNode.children() : null;
477     if (!siblings)
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;
483             break;
484         }
485     }
486     if (!hasSameNamedElements)
487         return 0;
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)
492                 return ownIndex;
493             ++ownIndex;
494         }
495     }
496     return -1; // An error occurred: |node| not found in parent's children.
497 }
498
499 /**
500  * @constructor
501  * @param {string} value
502  * @param {boolean} optimized
503  */
504 WebInspector.DOMNodePathStep = function(value, optimized)
505 {
506     this.value = value;
507     this.optimized = optimized || false;
508 }
509
510 WebInspector.DOMNodePathStep.prototype = {
511     /**
512      * @return {string}
513      */
514     toString: function()
515     {
516         return this.value;
517     }
518 }