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
= createElement("span");
39 nameElement
.textContent
= title
;
40 parentElement
.appendChild(nameElement
);
42 var idAttribute
= node
.getAttribute("id");
44 var idElement
= createElement("span");
45 parentElement
.appendChild(idElement
);
47 var part
= "#" + idAttribute
;
49 idElement
.createTextChild(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
= 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
.createTextChild(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];
94 * @param {?WebInspector.DOMNode} node
97 WebInspector
.DOMPresentationUtils
.linkifyNodeReference = function(node
)
100 return createTextNode(WebInspector
.UIString("<node>"));
102 var root
= createElement("span");
103 var shadowRoot
= WebInspector
.createShadowRootWithCoreStyles(root
);
104 shadowRoot
.appendChild(WebInspector
.Widget
.createStyleElement("components/domUtils.css"));
105 var link
= shadowRoot
.createChild("div", "node-link");
107 WebInspector
.DOMPresentationUtils
.decorateNodeLabel(node
, link
);
109 link
.addEventListener("click", WebInspector
.Revealer
.reveal
.bind(WebInspector
.Revealer
, node
, undefined), false);
110 link
.addEventListener("mouseover", node
.highlight
.bind(node
, undefined, undefined), false);
111 link
.addEventListener("mouseleave", WebInspector
.DOMModel
.hideDOMNodeHighlight
.bind(WebInspector
.DOMModel
), false);
117 * @param {!WebInspector.DeferredDOMNode} deferredNode
120 WebInspector
.DOMPresentationUtils
.linkifyDeferredNodeReference = function(deferredNode
)
122 var root
= createElement("div");
123 var shadowRoot
= WebInspector
.createShadowRootWithCoreStyles(root
);
124 shadowRoot
.appendChild(WebInspector
.Widget
.createStyleElement("components/domUtils.css"));
125 var link
= shadowRoot
.createChild("div", "node-link");
126 link
.createChild("content");
127 link
.addEventListener("click", deferredNode
.resolve
.bind(deferredNode
, onDeferredNodeResolved
), false);
128 link
.addEventListener("mousedown", consumeEvent
, false);
131 * @param {?WebInspector.DOMNode} node
133 function onDeferredNodeResolved(node
)
135 WebInspector
.Revealer
.reveal(node
);
142 * @param {!WebInspector.Target} target
143 * @param {string} originalImageURL
144 * @param {boolean} showDimensions
145 * @param {function(!Element=)} userCallback
146 * @param {!Object=} precomputedFeatures
148 WebInspector
.DOMPresentationUtils
.buildImagePreviewContents = function(target
, originalImageURL
, showDimensions
, userCallback
, precomputedFeatures
)
150 var resource
= target
.resourceTreeModel
.resourceForURL(originalImageURL
);
151 var imageURL
= originalImageURL
;
152 if (!isImageResource(resource
) && precomputedFeatures
&& precomputedFeatures
.currentSrc
) {
153 imageURL
= precomputedFeatures
.currentSrc
;
154 resource
= target
.resourceTreeModel
.resourceForURL(imageURL
);
156 if (!isImageResource(resource
)) {
161 var imageElement
= createElement("img");
162 imageElement
.addEventListener("load", buildContent
, false);
163 imageElement
.addEventListener("error", errorCallback
, false);
164 resource
.populateImageSource(imageElement
);
166 function errorCallback()
168 // Drop the event parameter when invoking userCallback.
173 * @param {?WebInspector.Resource} resource
176 function isImageResource(resource
)
178 return !!resource
&& resource
.resourceType() === WebInspector
.resourceTypes
.Image
;
181 function buildContent()
183 var container
= createElement("table");
184 container
.className
= "image-preview-container";
185 var naturalWidth
= precomputedFeatures
? precomputedFeatures
.naturalWidth
: imageElement
.naturalWidth
;
186 var naturalHeight
= precomputedFeatures
? precomputedFeatures
.naturalHeight
: imageElement
.naturalHeight
;
187 var offsetWidth
= precomputedFeatures
? precomputedFeatures
.offsetWidth
: naturalWidth
;
188 var offsetHeight
= precomputedFeatures
? precomputedFeatures
.offsetHeight
: naturalHeight
;
190 if (showDimensions
) {
191 if (offsetHeight
=== naturalHeight
&& offsetWidth
=== naturalWidth
)
192 description
= WebInspector
.UIString("%d \xd7 %d pixels", offsetWidth
, offsetHeight
);
194 description
= WebInspector
.UIString("%d \xd7 %d pixels (Natural: %d \xd7 %d pixels)", offsetWidth
, offsetHeight
, naturalWidth
, naturalHeight
);
197 container
.createChild("tr").createChild("td", "image-container").appendChild(imageElement
);
199 container
.createChild("tr").createChild("td").createChild("span", "description").textContent
= description
;
200 if (imageURL
!== originalImageURL
)
201 container
.createChild("tr").createChild("td").createChild("span", "description").textContent
= String
.sprintf("currentSrc: %s", imageURL
.trimMiddle(100));
202 userCallback(container
);
207 * @param {!WebInspector.Target} target
208 * @param {!WebInspector.Linkifier} linkifier
209 * @param {!Array.<!ConsoleAgent.CallFrame>=} stackTrace
210 * @param {!ConsoleAgent.AsyncStackTrace=} asyncStackTrace
213 WebInspector
.DOMPresentationUtils
.buildStackTracePreviewContents = function(target
, linkifier
, stackTrace
, asyncStackTrace
)
215 var element
= createElement("span");
216 element
.style
.display
= "inline-block";
217 var shadowRoot
= WebInspector
.createShadowRootWithCoreStyles(element
);
219 shadowRoot
.appendChild(WebInspector
.Widget
.createStyleElement("components/domUtils.css"));
220 var contentElement
= shadowRoot
.createChild("table", "stack-preview-container");
223 * @param {!Array.<!ConsoleAgent.CallFrame>} stackTrace
225 function appendStackTrace(stackTrace
)
227 for (var stackFrame
of stackTrace
) {
228 var row
= createElement("tr");
229 row
.createChild("td").textContent
= WebInspector
.beautifyFunctionName(stackFrame
.functionName
);
230 row
.createChild("td").textContent
= " @ ";
231 row
.createChild("td").appendChild(linkifier
.linkifyConsoleCallFrame(target
, stackFrame
));
232 contentElement
.appendChild(row
);
237 appendStackTrace(stackTrace
);
239 while (asyncStackTrace
) {
240 var callFrames
= asyncStackTrace
.callFrames
;
241 if (!callFrames
|| !callFrames
.length
)
243 var row
= contentElement
.createChild("tr");
244 row
.createChild("td", "stack-preview-async-description").textContent
= WebInspector
.asyncStackTraceLabel(asyncStackTrace
.description
);
245 row
.createChild("td");
246 row
.createChild("td");
247 appendStackTrace(callFrames
);
248 asyncStackTrace
= asyncStackTrace
.asyncStackTrace
;
255 * @param {!WebInspector.DOMNode} node
256 * @param {boolean=} justSelector
259 WebInspector
.DOMPresentationUtils
.fullQualifiedSelector = function(node
, justSelector
)
261 if (node
.nodeType() !== Node
.ELEMENT_NODE
)
262 return node
.localName() || node
.nodeName().toLowerCase();
263 return WebInspector
.DOMPresentationUtils
.cssPath(node
, justSelector
);
267 * @param {!WebInspector.DOMNode} node
270 WebInspector
.DOMPresentationUtils
.simpleSelector = function(node
)
272 var lowerCaseName
= node
.localName() || node
.nodeName().toLowerCase();
273 if (node
.nodeType() !== Node
.ELEMENT_NODE
)
274 return lowerCaseName
;
275 if (lowerCaseName
=== "input" && node
.getAttribute("type") && !node
.getAttribute("id") && !node
.getAttribute("class"))
276 return lowerCaseName
+ "[type=\"" + node
.getAttribute("type") + "\"]";
277 if (node
.getAttribute("id"))
278 return lowerCaseName
+ "#" + node
.getAttribute("id");
279 if (node
.getAttribute("class"))
280 return (lowerCaseName
=== "div" ? "" : lowerCaseName
) + "." + node
.getAttribute("class").trim().replace(/\s+/g, ".");
281 return lowerCaseName
;
285 * @param {!WebInspector.DOMNode} node
286 * @param {boolean=} optimized
289 WebInspector
.DOMPresentationUtils
.cssPath = function(node
, optimized
)
291 if (node
.nodeType() !== Node
.ELEMENT_NODE
)
295 var contextNode
= node
;
296 while (contextNode
) {
297 var step
= WebInspector
.DOMPresentationUtils
._cssPathStep(contextNode
, !!optimized
, contextNode
=== node
);
299 break; // Error - bail out early.
303 contextNode
= contextNode
.parentNode
;
307 return steps
.join(" > ");
311 * @param {!WebInspector.DOMNode} node
312 * @param {boolean} optimized
313 * @param {boolean} isTargetNode
314 * @return {?WebInspector.DOMNodePathStep}
316 WebInspector
.DOMPresentationUtils
._cssPathStep = function(node
, optimized
, isTargetNode
)
318 if (node
.nodeType() !== Node
.ELEMENT_NODE
)
321 var id
= node
.getAttribute("id");
324 return new WebInspector
.DOMNodePathStep(idSelector(id
), true);
325 var nodeNameLower
= node
.nodeName().toLowerCase();
326 if (nodeNameLower
=== "body" || nodeNameLower
=== "head" || nodeNameLower
=== "html")
327 return new WebInspector
.DOMNodePathStep(node
.nodeNameInCorrectCase(), true);
329 var nodeName
= node
.nodeNameInCorrectCase();
332 return new WebInspector
.DOMNodePathStep(nodeName
+ idSelector(id
), true);
333 var parent
= node
.parentNode
;
334 if (!parent
|| parent
.nodeType() === Node
.DOCUMENT_NODE
)
335 return new WebInspector
.DOMNodePathStep(nodeName
, true);
338 * @param {!WebInspector.DOMNode} node
339 * @return {!Array.<string>}
341 function prefixedElementClassNames(node
)
343 var classAttribute
= node
.getAttribute("class");
347 return classAttribute
.split(/\s+/g).filter(Boolean
).map(function(name
) {
348 // The prefix is required to store "__proto__" in a object-based map.
357 function idSelector(id
)
359 return "#" + escapeIdentifierIfNeeded(id
);
363 * @param {string} ident
366 function escapeIdentifierIfNeeded(ident
)
368 if (isCSSIdentifier(ident
))
370 var shouldEscapeFirst
= /^(?:[0-9]|-[0-9-]?)/.test(ident
);
371 var lastIndex
= ident
.length
- 1;
372 return ident
.replace(/./g
, function(c
, i
) {
373 return ((shouldEscapeFirst
&& i
=== 0) || !isCSSIdentChar(c
)) ? escapeAsciiChar(c
, i
=== lastIndex
) : c
;
379 * @param {boolean} isLast
382 function escapeAsciiChar(c
, isLast
)
384 return "\\" + toHexByte(c
) + (isLast
? "" : " ");
390 function toHexByte(c
)
392 var hexByte
= c
.charCodeAt(0).toString(16);
393 if (hexByte
.length
=== 1)
394 hexByte
= "0" + hexByte
;
402 function isCSSIdentChar(c
)
404 if (/[a-zA-Z0-9_-]/.test(c
))
406 return c
.charCodeAt(0) >= 0xA0;
410 * @param {string} value
413 function isCSSIdentifier(value
)
415 return /^-?[a-zA-Z_][a-zA-Z0-9_-]*$/.test(value
);
418 var prefixedOwnClassNamesArray
= prefixedElementClassNames(node
);
419 var needsClassNames
= false;
420 var needsNthChild
= false;
422 var elementIndex
= -1;
423 var siblings
= parent
.children();
424 for (var i
= 0; (ownIndex
=== -1 || !needsNthChild
) && i
< siblings
.length
; ++i
) {
425 var sibling
= siblings
[i
];
426 if (sibling
.nodeType() !== Node
.ELEMENT_NODE
)
429 if (sibling
=== node
) {
430 ownIndex
= elementIndex
;
435 if (sibling
.nodeNameInCorrectCase() !== nodeName
)
438 needsClassNames
= true;
439 var ownClassNames
= prefixedOwnClassNamesArray
.keySet();
440 var ownClassNameCount
= 0;
441 for (var name
in ownClassNames
)
443 if (ownClassNameCount
=== 0) {
444 needsNthChild
= true;
447 var siblingClassNamesArray
= prefixedElementClassNames(sibling
);
448 for (var j
= 0; j
< siblingClassNamesArray
.length
; ++j
) {
449 var siblingClass
= siblingClassNamesArray
[j
];
450 if (!ownClassNames
.hasOwnProperty(siblingClass
))
452 delete ownClassNames
[siblingClass
];
453 if (!--ownClassNameCount
) {
454 needsNthChild
= true;
460 var result
= nodeName
;
461 if (isTargetNode
&& nodeName
.toLowerCase() === "input" && node
.getAttribute("type") && !node
.getAttribute("id") && !node
.getAttribute("class"))
462 result
+= "[type=\"" + node
.getAttribute("type") + "\"]";
464 result
+= ":nth-child(" + (ownIndex
+ 1) + ")";
465 } else if (needsClassNames
) {
466 for (var prefixedName
in prefixedOwnClassNamesArray
.keySet())
467 result
+= "." + escapeIdentifierIfNeeded(prefixedName
.substr(1));
470 return new WebInspector
.DOMNodePathStep(result
, false);
474 * @param {!WebInspector.DOMNode} node
475 * @param {boolean=} optimized
478 WebInspector
.DOMPresentationUtils
.xPath = function(node
, optimized
)
480 if (node
.nodeType() === Node
.DOCUMENT_NODE
)
484 var contextNode
= node
;
485 while (contextNode
) {
486 var step
= WebInspector
.DOMPresentationUtils
._xPathValue(contextNode
, optimized
);
488 break; // Error - bail out early.
492 contextNode
= contextNode
.parentNode
;
496 return (steps
.length
&& steps
[0].optimized
? "" : "/") + steps
.join("/");
500 * @param {!WebInspector.DOMNode} node
501 * @param {boolean=} optimized
502 * @return {?WebInspector.DOMNodePathStep}
504 WebInspector
.DOMPresentationUtils
._xPathValue = function(node
, optimized
)
507 var ownIndex
= WebInspector
.DOMPresentationUtils
._xPathIndex(node
);
509 return null; // Error.
511 switch (node
.nodeType()) {
512 case Node
.ELEMENT_NODE
:
513 if (optimized
&& node
.getAttribute("id"))
514 return new WebInspector
.DOMNodePathStep("//*[@id=\"" + node
.getAttribute("id") + "\"]", true);
515 ownValue
= node
.localName();
517 case Node
.ATTRIBUTE_NODE
:
518 ownValue
= "@" + node
.nodeName();
521 case Node
.CDATA_SECTION_NODE
:
524 case Node
.PROCESSING_INSTRUCTION_NODE
:
525 ownValue
= "processing-instruction()";
527 case Node
.COMMENT_NODE
:
528 ownValue
= "comment()";
530 case Node
.DOCUMENT_NODE
:
539 ownValue
+= "[" + ownIndex
+ "]";
541 return new WebInspector
.DOMNodePathStep(ownValue
, node
.nodeType() === Node
.DOCUMENT_NODE
);
545 * @param {!WebInspector.DOMNode} node
548 WebInspector
.DOMPresentationUtils
._xPathIndex = function(node
)
550 // Returns -1 in case of error, 0 if no siblings matching the same expression, <XPath index among the same expression-matching sibling nodes> otherwise.
551 function areNodesSimilar(left
, right
)
556 if (left
.nodeType() === Node
.ELEMENT_NODE
&& right
.nodeType() === Node
.ELEMENT_NODE
)
557 return left
.localName() === right
.localName();
559 if (left
.nodeType() === right
.nodeType())
562 // XPath treats CDATA as text nodes.
563 var leftType
= left
.nodeType() === Node
.CDATA_SECTION_NODE
? Node
.TEXT_NODE
: left
.nodeType();
564 var rightType
= right
.nodeType() === Node
.CDATA_SECTION_NODE
? Node
.TEXT_NODE
: right
.nodeType();
565 return leftType
=== rightType
;
568 var siblings
= node
.parentNode
? node
.parentNode
.children() : null;
570 return 0; // Root node - no siblings.
571 var hasSameNamedElements
;
572 for (var i
= 0; i
< siblings
.length
; ++i
) {
573 if (areNodesSimilar(node
, siblings
[i
]) && siblings
[i
] !== node
) {
574 hasSameNamedElements
= true;
578 if (!hasSameNamedElements
)
580 var ownIndex
= 1; // XPath indices start with 1.
581 for (var i
= 0; i
< siblings
.length
; ++i
) {
582 if (areNodesSimilar(node
, siblings
[i
])) {
583 if (siblings
[i
] === node
)
588 return -1; // An error occurred: |node| not found in parent's children.
593 * @param {string} value
594 * @param {boolean} optimized
596 WebInspector
.DOMNodePathStep = function(value
, optimized
)
599 this.optimized
= optimized
|| false;
602 WebInspector
.DOMNodePathStep
.prototype = {
616 WebInspector
.DOMPresentationUtils
.MarkerDecorator = function()
620 WebInspector
.DOMPresentationUtils
.MarkerDecorator
.prototype = {
622 * @param {!WebInspector.DOMNode} node
623 * @return {?{title: string, color: string}}
625 decorate: function(node
) { }