082c3036747edf1b8755da19a527f776cc8e9d62
[WebKit-https.git] / WebCore / page / inspector / ResourcePanel.js
1 /*
2  * Copyright (C) 2007 Apple Inc.  All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  *
8  * 1.  Redistributions of source code must retain the above copyright
9  *     notice, this list of conditions and the following disclaimer. 
10  * 2.  Redistributions in binary form must reproduce the above copyright
11  *     notice, this list of conditions and the following disclaimer in the
12  *     documentation and/or other materials provided with the distribution. 
13  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
14  *     its contributors may be used to endorse or promote products derived
15  *     from this software without specific prior written permission. 
16  *
17  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28
29 WebInspector.ResourcePanel = function(resource)
30 {
31     this.resource = resource;
32     this.messages = [];
33
34     WebInspector.Panel.call(this);
35 }
36
37 WebInspector.ResourcePanel.prototype = {
38     show: function()
39     {
40         WebInspector.Panel.prototype.show.call(this);
41
42         this.resource.listItem.select(true);
43         this.resource.listItem.reveal();
44
45         if (this.currentView && "onshow" in this.currentView)
46             this.currentView.onshow();
47     },
48
49     hide: function()
50     {
51         this.resource.listItem.deselect(true);
52         if (this.currentView && "onhide" in this.currentView)
53             this.currentView.onhide();
54         WebInspector.Panel.prototype.hide.call(this);
55     },
56
57     sourceRow: function(lineNumber)
58     {
59         this.refreshIfNeeded();
60         var doc = this.sourceFrame.contentDocument;
61         var rows = doc.getElementsByTagName("table")[0].rows;
62
63         // Line numbers are a 1-based index, but the rows collection is 0-based.
64         --lineNumber;
65         if (lineNumber >= rows.length)
66             lineNumber = rows.length - 1;
67
68         return rows[lineNumber];
69     },
70
71     showSourceLine: function(lineNumber)
72     {
73         var row = this.sourceRow(lineNumber);
74         if (!row)
75             return;
76         this.navigateToView("source");
77         row.scrollIntoView(true);
78     },
79
80     addMessageToSource: function(msg)
81     {
82         this.messages.push(msg);
83         if (this.sourceFrame)
84             this._addMessageToSource(msg);
85     },
86
87     _addMessageToSource: function(msg)
88     {
89         var row = this.sourceRow(msg.line);
90         if (!row)
91             return;
92         var cell = row.getElementsByTagName("td")[1];
93
94         var errorDiv = cell.lastChild;
95         if (!errorDiv || errorDiv.nodeName.toLowerCase() !== "div" || !errorDiv.hasStyleClass("webkit-html-message-bubble")) {
96             errorDiv = document.createElement("div");
97             errorDiv.className = "webkit-html-message-bubble";
98             cell.appendChild(errorDiv);
99         }
100         var imageURL;
101         switch (msg.level) {
102             case WebInspector.ConsoleMessage.MessageLevel.Error:
103                 errorDiv.addStyleClass("webkit-html-error-message");
104                 imageURL = "Images/errorIcon.png";
105                 break;
106             case WebInspector.ConsoleMessage.MessageLevel.Warning:
107                 errorDiv.addStyleClass("webkit-html-warning-message");
108                 imageURL = "Images/warningIcon.png";
109                 break;
110         }
111
112         var lineDiv = document.createElement("div");
113         lineDiv.className = "webkit-html-message-line";
114         errorDiv.appendChild(lineDiv);
115
116         var image = document.createElement("img");
117         image.src = imageURL;
118         image.className = "webkit-html-message-icon";
119         lineDiv.appendChild(image);
120
121         lineDiv.appendChild(document.createTextNode(msg.message));
122     },
123
124     _addMessagesToSource: function()
125     {
126         for (var i = 0; i < this.messages.length; ++i)
127             this._addMessageToSource(this.messages[i]);
128     },
129
130     navigateToView: function(view)
131     {
132         if (typeof view === "string" || view instanceof String) {
133             if (this.needsRefresh)
134                 this.refresh();
135             view = this.views[view];
136         }
137
138         WebInspector.navigateToView(view);
139     },
140
141     createViews: function()
142     {
143         this.currentView = null;
144
145         var selectViewFunction = function(event)
146         {
147             WebInspector.navigateToView(event.currentTarget.view);
148         }
149
150         var views = [
151             {name: "source", title: "Source", selected: true},
152         ];
153
154         if (this.resource.category === WebInspector.resourceCategories.documents) {
155             var domView = {name: "dom", title: "DOM", selected: false};
156             var panel = this;
157             domView.onshow = function() { InspectorController.highlightDOMNode(panel.focusedDOMNode) };
158             domView.onhide = function() { InspectorController.hideDOMNodeHighlight() };
159             views.push(domView);
160         }
161
162         var oldViews = this.views || {};
163         this.views = {};
164         this.viewButtons = [];
165         for (var i = 0; i < views.length; ++i) {
166             var buttonElement = document.createElement("button");
167             buttonElement.setAttribute("title", views[i].title);
168             buttonElement.addEventListener("click", selectViewFunction, "true");
169             buttonElement.appendChild(document.createElement("img"));
170
171             var contentElement = document.createElement("div");
172             contentElement.className = "content " + views[i].name;
173
174             // We need to reuse the old view object if possible because it might
175             // already be in the backForwardList
176             var view = oldViews[views[i].name] || {};
177
178             view.buttonElement = buttonElement;
179             view.contentElement = contentElement;
180             view.panel = this;
181             view.buttonElement.view = view;
182             if ("onshow" in views[i])
183                 view.onshow = views[i].onshow;
184             if ("onhide" in views[i])
185                 view.onhide = views[i].onhide;
186
187             this.views[views[i].name] = view;
188             this.viewButtons.push(buttonElement);
189             this.element.appendChild(contentElement);
190         }
191
192         this.currentView = this.views.source;
193
194         if (WebInspector.currentPanel == this)
195             WebInspector.updateViewButtons();
196     },
197
198     _createSourceFrame: function()
199     {
200         this.sourceFrame = document.createElement("iframe");
201         this.sourceFrame.setAttribute("viewsource", "true");
202         this.sourceFrame.name = "sourceFrame" + WebInspector.ResourcePanel.sourceFrameCounter++;
203         this.views.source.contentElement.appendChild(this.sourceFrame);
204         InspectorController.addSourceToFrame(this.resource.identifier, this.sourceFrame);
205         WebInspector.addMainEventListeners(this.sourceFrame.contentDocument);
206         this._addMessagesToSource();
207     },
208
209     updateTreeSelection: function()
210     {
211         if (!this.domTreeOutline || !this.domTreeOutline.selectedTreeElement)
212             return;
213         var element = this.domTreeOutline.selectedTreeElement;
214         element.updateSelection(element);
215     },
216
217     refresh: function()
218     {
219         this.needsRefresh = false;
220
221         this.element.removeChildren();
222
223         this.createViews();
224
225         if (!this.resource.finished)
226             this.views.source.contentElement.addStyleClass("loading");
227         else if (this.resource.failed)
228             this.views.source.contentElement.removeStyleClass("failed");
229         else if (this.resource.category === WebInspector.resourceCategories.documents) {
230             this._createSourceFrame();
231
232             this.views.dom.contentElement.sideContentElement = document.createElement("div");
233             this.views.dom.contentElement.sideContentElement.className = "content side";
234
235             this.views.dom.contentElement.treeContentElement = document.createElement("div");
236             this.views.dom.contentElement.treeContentElement.className = "content tree outline-disclosure";
237
238             this.views.dom.contentElement.treeListElement = document.createElement("ol");
239             this.domTreeOutline = new TreeOutline(this.views.dom.contentElement.treeListElement);
240             this.domTreeOutline.panel = this;
241
242             var panel = this;
243             window.addEventListener("resize", function() { panel.updateTreeSelection() }, false);
244
245             this.views.dom.contentElement.crumbsElement = document.createElement("div");
246             this.views.dom.contentElement.crumbsElement.className = "crumbs";
247
248             this.views.dom.contentElement.innerCrumbsElement = document.createElement("div");
249             this.views.dom.contentElement.crumbsElement.appendChild(this.views.dom.contentElement.innerCrumbsElement);
250
251             this.views.dom.contentElement.sidebarPanes = {};
252             this.views.dom.contentElement.sidebarPanes.styles = new WebInspector.SidebarPane("Styles");
253             this.views.dom.contentElement.sidebarPanes.metrics = new WebInspector.SidebarPane("Metrics");
254             this.views.dom.contentElement.sidebarPanes.properties = new WebInspector.SidebarPane("Properties");
255
256             var panel = this;
257             this.views.dom.contentElement.sidebarPanes.styles.onexpand = function() { panel.updateStyles() };
258             this.views.dom.contentElement.sidebarPanes.metrics.onexpand = function() { panel.updateMetrics() };
259             this.views.dom.contentElement.sidebarPanes.properties.onexpand = function() { panel.updateProperties() };
260
261             this.views.dom.contentElement.sidebarPanes.styles.expanded = true;
262
263             this.views.dom.contentElement.sidebarElement = document.createElement("div");
264             this.views.dom.contentElement.sidebarElement.className = "sidebar";
265
266             this.views.dom.contentElement.sidebarElement.appendChild(this.views.dom.contentElement.sidebarPanes.styles.element);
267             this.views.dom.contentElement.sidebarElement.appendChild(this.views.dom.contentElement.sidebarPanes.metrics.element);
268             this.views.dom.contentElement.sidebarElement.appendChild(this.views.dom.contentElement.sidebarPanes.properties.element);
269
270             this.views.dom.contentElement.sideContentElement.appendChild(this.views.dom.contentElement.treeContentElement);
271             this.views.dom.contentElement.sideContentElement.appendChild(this.views.dom.contentElement.crumbsElement);
272             this.views.dom.contentElement.treeContentElement.appendChild(this.views.dom.contentElement.treeListElement);
273
274             this.views.dom.contentElement.appendChild(this.views.dom.contentElement.sideContentElement);
275             this.views.dom.contentElement.appendChild(this.views.dom.contentElement.sidebarElement);
276
277             this.rootDOMNode = this.resource.documentNode;
278         } else if (this.resource.category === WebInspector.resourceCategories.images) {
279             this.views.source.contentElement.removeStyleClass("source");
280             this.views.source.contentElement.addStyleClass("image");
281
282             var container = document.createElement("div");
283             container.className = "image";
284             this.views.source.contentElement.appendChild(container);
285
286             this.imagePreviewElement = document.createElement("img");
287             this.imagePreviewElement.setAttribute("src", this.resource.url);
288
289             container.appendChild(this.imagePreviewElement);
290
291             container = document.createElement("div");
292             container.className = "info";
293             this.views.source.contentElement.appendChild(container);
294
295             var imageNameElement = document.createElement("h1");
296             imageNameElement.className = "title";
297             imageNameElement.textContent = this.resource.displayName;
298             container.appendChild(imageNameElement);
299
300             var infoListElement = document.createElement("dl");
301             infoListElement.className = "infoList";
302
303             var imageProperties = [
304                 {name: "Dimensions", value: this.imagePreviewElement.naturalWidth + " \u00D7 " + this.imagePreviewElement.height},
305                 {name: "File size", value: (this.resource.contentLength / 1024).toPrecision(2) + "KB"},
306                 {name: "MIME type", value: this.resource.mimeType}
307             ];
308
309             var listHTML = '';
310             for (var i = 0; i < imageProperties.length; ++i)
311                 listHTML += "<dt>" + imageProperties[i].name + "</dt><dd>" + imageProperties[i].value + "</dd>";
312
313             infoListElement.innerHTML = listHTML;
314             container.appendChild(infoListElement);
315         } else if (this.resource.category === WebInspector.resourceCategories.fonts) {
316             var panel = this;
317             var resizeListener = function() {
318                 panel.updateFontPreviewSize();
319             };
320
321             this.views.source.onshow = function() {
322                 this.panel.updateFontPreviewSize();
323                 window.addEventListener("resize", resizeListener, false);
324             };
325
326             this.views.source.onhide = function() {
327                 window.removeEventListener("resize", resizeListener, false);
328             };
329
330             window.addEventListener("resize", resizeListener, false);
331
332             this.views.source.contentElement.removeStyleClass("source");
333             this.views.source.contentElement.addStyleClass("font");
334
335             var uniqueFontName = "WebInspectorFontPreview" + this.resource.identifier;
336
337             this.fontPreviewElement = document.createElement("div");
338             this.fontPreviewElement.className = "preview";
339             this.views.source.contentElement.appendChild(this.fontPreviewElement);
340
341             this.fontPreviewElement.style.setProperty("font-family", uniqueFontName, null);
342             this.fontPreviewElement.innerHTML = "ABCDEFGHIJKLM<br>NOPQRSTUVWXYZ<br>abcdefghijklm<br>nopqrstuvwxyz<br>1234567890";
343
344             this.updateFontPreviewSize();
345         } else if (this.resource.category === WebInspector.resourceCategories.stylesheets || this.resource.category === WebInspector.resourceCategories.scripts) {
346             this.views.source.contentElement.addStyleClass(this.resource.category.title.toLowerCase());
347             this._createSourceFrame();
348         } else {
349             if (this.resource.category)
350                 this.views.source.contentElement.addStyleClass(this.resource.category.title.toLowerCase());
351         }
352     },
353
354     updateFontPreviewSize: function ()
355     {
356         if (!this.fontPreviewElement)
357             return;
358
359         this.fontPreviewElement.removeStyleClass("preview");
360
361         var measureFontSize = 50;
362         this.fontPreviewElement.style.setProperty("position", "absolute", null);
363         this.fontPreviewElement.style.setProperty("font-size", measureFontSize + "px", null);
364         this.fontPreviewElement.style.removeProperty("height");
365
366         var height = this.fontPreviewElement.offsetHeight;
367         var width = this.fontPreviewElement.offsetWidth;
368
369         var containerHeight = this.views.source.contentElement.offsetHeight;
370         var containerWidth = this.views.source.contentElement.offsetWidth;
371
372         if (!height || !width || !containerHeight || !containerWidth) {
373             this.fontPreviewElement.style.removeProperty("font-size");
374             this.fontPreviewElement.style.removeProperty("position");
375             this.fontPreviewElement.addStyleClass("preview");
376             return;
377         }
378
379         var lineCount = this.fontPreviewElement.getElementsByTagName("br").length + 1;
380         var realLineHeight = Math.floor(height / lineCount);
381         var fontSizeLineRatio = measureFontSize / realLineHeight;
382         var widthRatio = containerWidth / width;
383         var heightRatio = containerHeight / height;
384
385         if (heightRatio < widthRatio)
386             var finalFontSize = Math.floor(realLineHeight * heightRatio * fontSizeLineRatio) - 1;
387         else
388             var finalFontSize = Math.floor(realLineHeight * widthRatio * fontSizeLineRatio) - 1;
389
390         this.fontPreviewElement.style.setProperty("font-size", finalFontSize + "px", null);
391         this.fontPreviewElement.style.setProperty("height", this.fontPreviewElement.offsetHeight + "px", null);
392         this.fontPreviewElement.style.removeProperty("position");
393
394         this.fontPreviewElement.addStyleClass("preview");
395     },
396
397     get currentView()
398     {
399         return this._currentView;
400     },
401
402     set currentView(x)
403     {
404         if (this._currentView === x)
405             return;
406
407         if (this._currentView) {
408             this._currentView.buttonElement.removeStyleClass("selected");
409             this._currentView.contentElement.removeStyleClass("selected");
410             if ("onhide" in this._currentView)
411                 this._currentView.onhide();
412         }
413
414         this._currentView = x;
415
416         if (x) {
417             x.buttonElement.addStyleClass("selected");
418             x.contentElement.addStyleClass("selected");
419             if ("onshow" in x)
420                 x.onshow();
421
422             if (x.contentElement.parentNode !== this.element)
423                 InspectorController.log("Tried to set currentView to a view " + x.title + " whose contentElement is a non-child");
424         }
425     },
426
427     get rootDOMNode()
428     {
429         return this._rootDOMNode;
430     },
431
432     set rootDOMNode(x)
433     {
434         if (this._rootDOMNode === x)
435             return;
436
437         this._rootDOMNode = x;
438
439         this.updateBreadcrumb();
440         this.updateTreeOutline();
441     },
442
443     get focusedDOMNode()
444     {
445         return this._focusedDOMNode;
446     },
447
448     set focusedDOMNode(x)
449     {
450         if (this.resource.category !== WebInspector.resourceCategories.documents) {
451             InspectorController.log("Called set focusedDOMNode on a non-document resource " + this.resource.displayName + " which is not a document");
452             return;
453         }
454
455         if (this._focusedDOMNode === x) {
456             var nodeItem = this.revealNode(x);
457             if (nodeItem)
458                 nodeItem.select();
459             return;
460         }
461
462         this._focusedDOMNode = x;
463
464         this.updateBreadcrumb();
465
466         for (var pane in this.views.dom.contentElement.sidebarPanes)
467             this.views.dom.contentElement.sidebarPanes[pane].needsUpdate = true;
468
469         this.updateStyles();
470         this.updateMetrics();
471         this.updateProperties();
472
473         InspectorController.highlightDOMNode(x);
474
475         var nodeItem = this.revealNode(x);
476         if (nodeItem)
477             nodeItem.select();
478     },
479
480     revealNode: function(node)
481     {
482         var nodeItem = this.domTreeOutline.findTreeElement(node, function(a, b) { return isAncestorNode.call(a, b); }, function(a) { return a.parentNode; });
483         if (!nodeItem)
484             return;
485
486         nodeItem.reveal();
487         return nodeItem;
488     },
489
490     updateTreeOutline: function()
491     {
492         this.domTreeOutline.removeChildrenRecursive();
493
494         if (!this.rootDOMNode)
495             return;
496
497         // FIXME: this could use findTreeElement to reuse a tree element if it already exists
498         var node = (Preferences.ignoreWhitespace ? firstChildSkippingWhitespace.call(this.rootDOMNode) : this.rootDOMNode.firstChild);
499         while (node) {
500             var item = new WebInspector.DOMNodeTreeElement(node);
501             this.domTreeOutline.appendChild(item);
502             node = Preferences.ignoreWhitespace ? nextSiblingSkippingWhitespace.call(node) : node.nextSibling;
503         }
504
505         this.updateTreeSelection();
506     },
507
508     updateBreadcrumb: function()
509     {
510         if (!this.views || !this.views.dom.contentElement)
511             return;
512         var crumbs = this.views.dom.contentElement.innerCrumbsElement;
513         if (!crumbs)
514             return;
515
516         var handled = false;
517         var foundRoot = false;
518         var crumb = crumbs.firstChild;
519         while (crumb) {
520             if (crumb.representedObject === this.rootDOMNode)
521                 foundRoot = true;
522
523             if (foundRoot)
524                 crumb.addStyleClass("hidden");
525             else
526                 crumb.removeStyleClass("hidden");
527
528             if (crumb.representedObject === this.focusedDOMNode) {
529                 crumb.addStyleClass("selected");
530                 handled = true;
531             } else {
532                 crumb.removeStyleClass("selected");
533             }
534
535             crumb = crumb.nextSibling;
536         }
537
538         if (handled)
539             return;
540
541         crumbs.removeChildren();
542
543         var panel = this;
544         var selectCrumbFunction = function(event)
545         {
546             if (event.currentTarget.hasStyleClass("hidden"))
547                 panel.rootDOMNode = event.currentTarget.representedObject.parentNode;
548             panel.focusedDOMNode = event.currentTarget.representedObject;
549             event.preventDefault();
550             event.stopPropagation();
551         }
552
553         var selectCrumbRootFunction = function(event)
554         {
555             panel.rootDOMNode = event.currentTarget.representedObject.parentNode;
556             panel.focusedDOMNode = event.currentTarget.representedObject;
557             event.preventDefault();
558             event.stopPropagation();
559         }
560
561         foundRoot = false;
562         var current = this.focusedDOMNode;
563         while (current) {
564             if (current.nodeType === Node.DOCUMENT_NODE)
565                 break;
566
567             if (current === this.rootDOMNode)
568                 foundRoot = true;
569
570             var crumb = document.createElement("span");
571             crumb.className = "crumb";
572             crumb.representedObject = current;
573             crumb.addEventListener("mousedown", selectCrumbFunction, false);
574             crumb.addEventListener("dblclick", selectCrumbRootFunction, false);
575
576             var crumbTitle;
577             switch (current.nodeType) {
578                 case Node.ELEMENT_NODE:
579                     crumbTitle = current.nodeName.toLowerCase();
580     
581                     var value = current.getAttribute("id");
582                     if (value && value.length)
583                         crumbTitle += "#" + value;
584
585                     value = current.getAttribute("class");
586                     if (value && value.length) {
587                         var classes = value.split(/\s+/);
588                         var classesLength = classes.length;
589                         for (var i = 0; i < classesLength; ++i) {
590                             value = classes[i];
591                             if (value && value.length)
592                                 crumbTitle += "." + value;
593                         }
594                     }
595
596                     break;
597
598                 case Node.TEXT_NODE:
599                     if (isNodeWhitespace.call(current))
600                         crumbTitle = "(whitespace)";
601                     else
602                         crumbTitle = "(text)";
603                     break
604
605                 case Node.COMMENT_NODE:
606                     crumbTitle = "<!-->";
607                     break;
608
609                 default:
610                     crumbTitle = current.nodeName.toLowerCase();
611             }
612
613             crumb.textContent = crumbTitle;
614
615             if (foundRoot)
616                 crumb.addStyleClass("hidden");
617             if (current === this.focusedDOMNode)
618                 crumb.addStyleClass("selected");
619             if (!crumbs.childNodes.length)
620                 crumb.addStyleClass("end");
621             if (current.parentNode.nodeType === Node.DOCUMENT_NODE)
622                 crumb.addStyleClass("start");
623
624             crumbs.appendChild(crumb);
625             current = current.parentNode;
626         }
627     },
628
629     updateStyles: function()
630     {
631         if (!this.views.dom.contentElement.sidebarPanes.styles.expanded)
632             return;
633         if (!this.views.dom.contentElement.sidebarPanes.styles.needsUpdate)
634             return;
635
636         this.views.dom.contentElement.sidebarPanes.styles.needsUpdate = false;
637
638         var stylesBody = this.views.dom.contentElement.sidebarPanes.styles.bodyElement;
639
640         stylesBody.removeChildren();
641         this.views.dom.contentElement.sidebarPanes.styles.sections = [];
642
643         if (!this.focusedDOMNode)
644             return;
645
646         var styleNode = this.focusedDOMNode;
647         if (styleNode.nodeType === Node.TEXT_NODE && styleNode.parentNode)
648             styleNode = styleNode.parentNode;
649
650         var styleRules = [];
651         var styleProperties = [];
652
653         if (styleNode.nodeType === Node.ELEMENT_NODE) {
654             var propertyCount = [];
655
656             var computedStyle = styleNode.ownerDocument.defaultView.getComputedStyle(styleNode);
657             if (computedStyle && computedStyle.length)
658                 styleRules.push({ isComputedStyle: true, selectorText: "Computed Style", style: computedStyle });
659
660             var styleNodeName = styleNode.nodeName.toLowerCase();
661             for (var i = 0; i < styleNode.attributes.length; ++i) {
662                 var attr = styleNode.attributes[i];
663                 if (attr.style) {
664                     var attrStyle = { attrName: attr.name, style: attr.style };
665                     attrStyle.subtitle = "element\u2019s \u201C" + attr.name + "\u201D attribute";
666                     attrStyle.selectorText = styleNodeName + "[" + attr.name;
667                     if (attr.value.length)
668                         attrStyle.selectorText += "=" + attr.value;
669                     attrStyle.selectorText += "]";
670                     styleRules.push(attrStyle);
671                 }
672             }
673
674             if (styleNode.style && styleNode.style.length) {
675                 var inlineStyle = { selectorText: "Inline Style Attribute", style: styleNode.style };
676                 inlineStyle.subtitle = "element\u2019s \u201Cstyle\u201D attribute";
677                 styleRules.push(inlineStyle);
678             }
679
680             var matchedStyleRules = styleNode.ownerDocument.defaultView.getMatchedCSSRules(styleNode, "", !Preferences.showUserAgentStyles);
681             if (matchedStyleRules) {
682                 for (var i = matchedStyleRules.length - 1; i >= 0; --i)
683                     styleRules.push(matchedStyleRules[i]);
684             }
685
686             var priorityUsed = false;
687             var usedProperties = {};
688             var shorthandProperties = {};
689             for (var i = 0; i < styleRules.length; ++i) {
690                 styleProperties[i] = [];
691
692                 var style = styleRules[i].style;
693                 var styleShorthandLookup = [];
694                 for (var j = 0; j < style.length; ++j) {
695                     var prop = null;
696                     var name = style[j];
697                     var shorthand = style.getPropertyShorthand(name);
698                     if (!shorthand && styleRules[i].isComputedStyle)
699                         shorthand = shorthandProperties[name];
700
701                     if (shorthand) {
702                         prop = styleShorthandLookup[shorthand];
703                         shorthandProperties[name] = shorthand;
704                     }
705
706                     if (!priorityUsed && style.getPropertyPriority(name).length)
707                         priorityUsed = true;
708
709                     if (prop) {
710                         prop.subProperties.push(name);
711                     } else {
712                         prop = { style: style, subProperties: [name], unusedProperties: [], name: (shorthand ? shorthand : name) };
713                         styleProperties[i].push(prop);
714
715                         if (shorthand) {
716                             styleShorthandLookup[shorthand] = prop;
717                             if (!styleRules[i].isComputedStyle) {
718                                 if (!propertyCount[shorthand]) {
719                                     propertyCount[shorthand] = 1;
720                                 } else {
721                                     prop.unusedProperties[shorthand] = true;
722                                     propertyCount[shorthand]++;
723                                 }
724                             }
725                         }
726                     }
727
728                     if (styleRules[i].isComputedStyle)
729                         continue;
730
731                     usedProperties[name] = true;
732                     if (shorthand)
733                         usedProperties[shorthand] = true;
734
735                     if (!propertyCount[name]) {
736                         propertyCount[name] = 1;
737                     } else {
738                         prop.unusedProperties[name] = true;
739                         propertyCount[name]++;
740                     }
741                 }
742             }
743
744             if (priorityUsed) {
745                 // walk the properties again and account for !important
746                 var priorityCount = [];
747                 for (var i = 0; i < styleRules.length; ++i) {
748                     if (styleRules[i].isComputedStyle)
749                         continue;
750                     var style = styleRules[i].style;
751                     for (var j = 0; j < styleProperties[i].length; ++j) {
752                         var prop = styleProperties[i][j];
753                         for (var k = 0; k < prop.subProperties.length; ++k) {
754                             var name = prop.subProperties[k];
755                             if (style.getPropertyPriority(name).length) {
756                                 if (!priorityCount[name]) {
757                                     if (prop.unusedProperties[name])
758                                         prop.unusedProperties[name] = false;
759                                     priorityCount[name] = 1;
760                                 } else {
761                                     priorityCount[name]++;
762                                 }
763                             } else if (priorityCount[name]) {
764                                 prop.unusedProperties[name] = true;
765                             }
766                         }
767                     }
768                 }
769             }
770
771             var styleRulesLength = styleRules.length;
772             for (var i = 0; i < styleRulesLength; ++i) {
773                 var selectorText = styleRules[i].selectorText;
774
775                 var section = new WebInspector.PropertiesSection(selectorText);
776                 section.expanded = true;
777
778                 if (styleRules[i].isComputedStyle) {
779                     if (Preferences.showInheritedComputedStyleProperties)
780                         section.element.addStyleClass("show-inherited");
781
782                     var showInheritedLabel = document.createElement("label");
783                     var showInheritedInput = document.createElement("input");
784                     showInheritedInput.type = "checkbox";
785                     showInheritedInput.checked = Preferences.showInheritedComputedStyleProperties;
786
787                     var computedStyleSection = section;
788                     var showInheritedToggleFunction = function(event) {
789                         Preferences.showInheritedComputedStyleProperties = showInheritedInput.checked;
790                         if (Preferences.showInheritedComputedStyleProperties)
791                             computedStyleSection.element.addStyleClass("show-inherited");
792                         else
793                             computedStyleSection.element.removeStyleClass("show-inherited");
794                         event.stopPropagation();
795                     };
796
797                     showInheritedLabel.addEventListener("click", showInheritedToggleFunction, false);
798
799                     showInheritedLabel.appendChild(showInheritedInput);
800                     showInheritedLabel.appendChild(document.createTextNode("Show implicit properties"));
801                     section.subtitleElement.appendChild(showInheritedLabel);
802                 } else {
803                     var sheet;
804                     var file = false;
805                     if (styleRules[i].subtitle)
806                         sheet = styleRules[i].subtitle;
807                     else if (styleRules[i].parentStyleSheet && styleRules[i].parentStyleSheet.href) {
808                         var url = styleRules[i].parentStyleSheet.href;
809                         sheet = WebInspector.linkifyURL(url, url.trimURL(WebInspector.mainResource.domain).escapeHTML());
810                         file = true;
811                     } else if (styleRules[i].parentStyleSheet && !styleRules[i].parentStyleSheet.ownerNode)
812                         sheet = "user agent stylesheet";
813                     else
814                         sheet = "inline stylesheet";
815
816                     if (file)
817                         section.subtitleElement.addStyleClass("file");
818
819                     section.subtitle = sheet;
820                 }
821
822                 stylesBody.appendChild(section.element);
823                 this.views.dom.contentElement.sidebarPanes.styles.sections.push(section);
824
825                 var properties = styleProperties[i];
826                 var isComputedStyle = styleRules[i].isComputedStyle;
827
828                 for (var j = 0; j < properties.length; ++j) {
829                     var prop = properties[j];
830
831                     var propTreeElement = new WebInspector.StylePropertyTreeElement(prop, isComputedStyle, usedProperties);
832                     section.propertiesTreeOutline.appendChild(propTreeElement);
833                 }
834             }
835         } else {
836             // can't style this node
837         }
838     },
839
840     updateMetrics: function()
841     {
842         if (!this.views.dom.contentElement.sidebarPanes.metrics.expanded)
843             return;
844         if (!this.views.dom.contentElement.sidebarPanes.metrics.needsUpdate)
845             return;
846
847         this.views.dom.contentElement.sidebarPanes.metrics.needsUpdate = false;
848
849         var metricsBody = this.views.dom.contentElement.sidebarPanes.metrics.bodyElement;
850
851         metricsBody.removeChildren();
852
853         if (!this.focusedDOMNode)
854             return;
855
856         var style;
857         if (this.focusedDOMNode.nodeType === Node.ELEMENT_NODE)
858             style = this.focusedDOMNode.ownerDocument.defaultView.getComputedStyle(this.focusedDOMNode);
859         if (!style)
860             return;
861
862         var metricsElement = document.createElement("div");
863         metricsElement.className = "metrics";
864
865         function boxPartValue(style, name, suffix)
866         {
867             var value = style.getPropertyValue(name + suffix);
868             if (value === "" || value === "0px")
869                 value = "\u2012";
870             return value.replace(/px$/, "");
871         }
872
873         // Display types for which margin is ignored.
874         var noMarginDisplayType = {
875             "table-cell": true,
876             "table-column": true,
877             "table-column-group": true,
878             "table-footer-group": true,
879             "table-header-group": true,
880             "table-row": true,
881             "table-row-group": true
882         };
883
884         // Display types for which padding is ignored.
885         var noPaddingDisplayType = {
886             "table-column": true,
887             "table-column-group": true,
888             "table-footer-group": true,
889             "table-header-group": true,
890             "table-row": true,
891             "table-row-group": true
892         };
893
894         var boxes = ["content", "padding", "border", "margin"];
895         var previousBox;
896         for (var i = 0; i < boxes.length; ++i) {
897             var name = boxes[i];
898
899             if (name === "margin" && noMarginDisplayType[style.display])
900                 continue;
901             if (name === "padding" && noPaddingDisplayType[style.display])
902                 continue;
903
904             var boxElement = document.createElement("div");
905             boxElement.className = name;
906
907             if (boxes[i] === "content") {
908                 var width = style.width.replace(/px$/, "");
909                 var height = style.height.replace(/px$/, "");
910                 boxElement.textContent = width + " \u00D7 " + height;
911             } else {
912                 var suffix = boxes[i] === "border" ? "-width" : "";
913
914                 var labelElement = document.createElement("div");
915                 labelElement.className = "label";
916                 labelElement.textContent = name;
917                 boxElement.appendChild(labelElement);
918
919                 var topElement = document.createElement("div");
920                 topElement.className = "top";
921                 topElement.textContent = boxPartValue(style, name + "-top", suffix);
922                 boxElement.appendChild(topElement);
923
924                 var leftElement = document.createElement("div");
925                 leftElement.className = "left";
926                 leftElement.textContent = boxPartValue(style, name + "-left", suffix);
927                 boxElement.appendChild(leftElement);
928
929                 if (previousBox)
930                     boxElement.appendChild(previousBox);
931
932                 var rightElement = document.createElement("div");
933                 rightElement.className = "right";
934                 rightElement.textContent = boxPartValue(style, name + "-right", suffix);
935                 boxElement.appendChild(rightElement);
936
937                 var bottomElement = document.createElement("div");
938                 bottomElement.className = "bottom";
939                 bottomElement.textContent = boxPartValue(style, name + "-bottom", suffix);
940                 boxElement.appendChild(bottomElement);
941             }
942
943             previousBox = boxElement;
944         }
945
946         metricsElement.appendChild(previousBox);
947         metricsBody.appendChild(metricsElement);
948     },
949
950     updateProperties: function()
951     {
952         if (!this.views.dom.contentElement.sidebarPanes.properties.expanded)
953             return;
954         if (!this.views.dom.contentElement.sidebarPanes.properties.needsUpdate)
955             return;
956
957         this.views.dom.contentElement.sidebarPanes.properties.needsUpdate = false;
958
959         var propertiesBody = this.views.dom.contentElement.sidebarPanes.properties.bodyElement;
960
961         propertiesBody.removeChildren();
962         this.views.dom.contentElement.sidebarPanes.properties.sections = [];
963
964         if (!this.focusedDOMNode)
965             return;
966
967         for (var prototype = this.focusedDOMNode; prototype; prototype = prototype.__proto__) {
968             var hasChildren = false;
969             for (var prop in prototype) {
970                 if (prop === "__treeElementIdentifier")
971                     continue;
972                 if (!prototype.hasOwnProperty(prop))
973                     continue;
974
975                 hasChildren = true;
976                 break;
977             }
978
979             if (!hasChildren)
980                 continue;
981
982             var title = Object.describe(prototype);
983             var subtitle;
984             if (title.match(/Prototype$/)) {
985                 title = title.replace(/Prototype$/, "");
986                 subtitle = "Prototype";
987             }
988             var section = new WebInspector.PropertiesSection(title, subtitle);
989             section.onpopulate = WebInspector.DOMPropertiesSection.onpopulate(prototype);
990
991             propertiesBody.appendChild(section.element);
992             this.views.dom.contentElement.sidebarPanes.properties.sections.push(section);
993         }
994     },
995
996     handleKeyEvent: function(event)
997     {
998         if (this.domTreeOutline && this.currentView && this.currentView === this.views.dom)
999             this.domTreeOutline.handleKeyEvent(event);
1000     }
1001 }
1002
1003 WebInspector.ResourcePanel.sourceFrameCounter = 0;
1004
1005 WebInspector.ResourcePanel.prototype.__proto__ = WebInspector.Panel.prototype;
1006
1007 WebInspector.SidebarPane = function(title)
1008 {
1009     this.element = document.createElement("div");
1010     this.element.className = "pane";
1011
1012     this.titleElement = document.createElement("div");
1013     this.titleElement.className = "title";
1014
1015     var pane = this;
1016     this.titleElement.addEventListener("click", function() { pane.expanded = !pane.expanded; }, false);
1017
1018     this.bodyElement = document.createElement("div");
1019     this.bodyElement.className = "body";
1020
1021     this.element.appendChild(this.titleElement);
1022     this.element.appendChild(this.bodyElement);
1023
1024     this.title = title;
1025     this.growbarVisible = false;
1026     this.expanded = false;
1027     this.onexpand = null;
1028     this.oncollapse = null;
1029 }
1030
1031 WebInspector.SidebarPane.prototype = {
1032     get title()
1033     {
1034         return this._title;
1035     },
1036
1037     set title(x)
1038     {
1039         if (this._title === x)
1040             return;
1041         this._title = x;
1042         this.titleElement.textContent = x;
1043     },
1044
1045     get growbarVisible()
1046     {
1047         return this._growbarVisible;
1048     },
1049
1050     set growbarVisible(x)
1051     {
1052         if (this._growbarVisible === x)
1053             return;
1054
1055         this._growbarVisible = x;
1056
1057         if (x && !this._growbarElement) {
1058             this._growbarElement = document.createElement("div");
1059             this._growbarElement.className = "growbar";
1060             this.element.appendChild(this._growbarElement);
1061         } else if (!x && this._growbarElement) {
1062             if (this._growbarElement.parentNode)
1063                 this._growbarElement.parentNode(this._growbarElement);
1064             delete this._growbarElement;
1065         }
1066     },
1067
1068     get expanded()
1069     {
1070         return this._expanded;
1071     },
1072
1073     set expanded(x)
1074     {
1075         if (x)
1076             this.expand();
1077         else
1078             this.collapse();
1079     },
1080
1081     expand: function()
1082     {
1083         if (this._expanded)
1084             return;
1085         this._expanded = true;
1086         this.element.addStyleClass("expanded");
1087         if (this.onexpand)
1088             this.onexpand(this);
1089     },
1090
1091     collapse: function()
1092     {
1093         if (!this._expanded)
1094             return;
1095         this._expanded = false;
1096         this.element.removeStyleClass("expanded");
1097         if (this.oncollapse)
1098             this.oncollapse(this);
1099     }
1100 }
1101
1102 WebInspector.PropertiesSection = function(title, subtitle)
1103 {
1104     this.element = document.createElement("div");
1105     this.element.className = "section";
1106
1107     this.headerElement = document.createElement("div");
1108     this.headerElement.className = "header";
1109
1110     this.titleElement = document.createElement("div");
1111     this.titleElement.className = "title";
1112
1113     this.subtitleElement = document.createElement("div");
1114     this.subtitleElement.className = "subtitle";
1115
1116     this.headerElement.appendChild(this.titleElement);
1117     this.headerElement.appendChild(this.subtitleElement);
1118
1119     var section = this;
1120     this.headerElement.addEventListener("click", function() { section.expanded = !section.expanded; }, false);
1121
1122     this.propertiesElement = document.createElement("ol");
1123     this.propertiesElement.className = "properties";
1124     this.propertiesTreeOutline = new TreeOutline(this.propertiesElement);
1125
1126     this.element.appendChild(this.headerElement);
1127     this.element.appendChild(this.propertiesElement);
1128
1129     this.title = title;
1130     this.subtitle = subtitle;
1131     this.expanded = false;
1132 }
1133
1134 WebInspector.PropertiesSection.prototype = {
1135     get title()
1136     {
1137         return this._title;
1138     },
1139
1140     set title(x)
1141     {
1142         if (this._title === x)
1143             return;
1144         this._title = x;
1145         this.titleElement.textContent = x;
1146     },
1147
1148     get subtitle()
1149     {
1150         return this._subtitle;
1151     },
1152
1153     set subtitle(x)
1154     {
1155         if (this._subtitle === x)
1156             return;
1157         this._subtitle = x;
1158         this.subtitleElement.innerHTML = x;
1159     },
1160
1161     get expanded()
1162     {
1163         return this._expanded;
1164     },
1165
1166     set expanded(x)
1167     {
1168         if (x)
1169             this.expand();
1170         else
1171             this.collapse();
1172     },
1173
1174     expand: function()
1175     {
1176         if (this._expanded)
1177             return;
1178         this._expanded = true;
1179
1180         if (!this._populated && this.onpopulate) {
1181             this.onpopulate(this);
1182             this._populated = true;
1183         }
1184         this.element.addStyleClass("expanded");
1185     },
1186
1187     collapse: function()
1188     {
1189         if (!this._expanded)
1190             return;
1191         this._expanded = false;
1192         this.element.removeStyleClass("expanded");
1193     }
1194 }
1195
1196 WebInspector.DOMPropertiesSection = function()
1197 {
1198     // FIXME: Perhaps this should be a real subclass someday
1199 }
1200
1201 WebInspector.DOMPropertiesSection.onpopulate = function(prototype)
1202 {
1203     return function(section) {
1204         var properties = Object.sortedProperties(prototype);
1205         for (var i = 0; i < properties.length; ++i) {
1206             var name = properties[i];
1207             if (!prototype.hasOwnProperty(name))
1208                 continue;
1209             if (name === "__treeElementIdentifier")
1210                 continue;
1211             var item = new WebInspector.DOMPropertyTreeElement(name, prototype);
1212             section.propertiesTreeOutline.appendChild(item);
1213         }
1214     }
1215 }
1216
1217 WebInspector.DOMNodeTreeElement = function(node)
1218 {
1219     var hasChildren = (Preferences.ignoreWhitespace ? (firstChildSkippingWhitespace.call(node) ? true : false) : node.hasChildNodes());
1220
1221     var titleInfo = nodeTitleInfo.call(node, hasChildren, WebInspector.linkifyURL);
1222     var title = titleInfo.title;
1223     hasChildren = titleInfo.hasChildren;
1224
1225     var item = new TreeElement(title, node, hasChildren);
1226     item.updateSelection = WebInspector.DOMNodeTreeElement.updateSelection;
1227     item.onpopulate = WebInspector.DOMNodeTreeElement.populate;
1228     item.onexpand = WebInspector.DOMNodeTreeElement.expanded;
1229     item.oncollapse = WebInspector.DOMNodeTreeElement.collapsed;
1230     item.onselect = WebInspector.DOMNodeTreeElement.selected;
1231     item.onreveal = WebInspector.DOMNodeTreeElement.revealed;
1232     item.ondblclick = WebInspector.DOMNodeTreeElement.doubleClicked;
1233     if (hasChildren) 
1234         item.whitespaceIgnored = Preferences.ignoreWhitespace;
1235     return item;
1236 }
1237
1238 WebInspector.DOMNodeTreeElement.updateSelection = function(element)
1239 {
1240     if (!element || !element._listItemNode)
1241         return;
1242
1243     if (!element.selectionElement) {
1244         element.selectionElement = document.createElement("div");
1245         element.selectionElement.className = "selection selected";
1246         element._listItemNode.insertBefore(element.selectionElement, element._listItemNode.firstChild);
1247     }
1248
1249     element.selectionElement.style.height = element._listItemNode.offsetHeight + "px";
1250 }
1251
1252 WebInspector.DOMNodeTreeElement.populate = function(element)
1253 {
1254     if (element.children.length || element.whitespaceIgnored !== Preferences.ignoreWhitespace)
1255         return;
1256
1257     element.removeChildren();
1258     element.whitespaceIgnored = Preferences.ignoreWhitespace;
1259
1260     var node = (Preferences.ignoreWhitespace ? firstChildSkippingWhitespace.call(element.representedObject) : element.representedObject.firstChild);
1261     while (node) {
1262         var item = new WebInspector.DOMNodeTreeElement(node);
1263         element.appendChild(item);
1264         node = Preferences.ignoreWhitespace ? nextSiblingSkippingWhitespace.call(node) : node.nextSibling;
1265     }
1266
1267     if (element.representedObject.nodeType == Node.ELEMENT_NODE) {
1268         var title = "<span class=\"webkit-html-tag close\">&lt;/" + element.representedObject.nodeName.toLowerCase().escapeHTML() + "&gt;</span>";
1269         var item = new TreeElement(title, element.representedObject, false);
1270         item.selectable = false;
1271         element.appendChild(item);
1272     }
1273 }
1274
1275 WebInspector.DOMNodeTreeElement.expanded = function(element)
1276 {
1277     element.treeOutline.panel.updateTreeSelection();
1278 }
1279
1280 WebInspector.DOMNodeTreeElement.collapsed = function(element)
1281 {
1282     element.treeOutline.panel.updateTreeSelection();
1283 }
1284
1285 WebInspector.DOMNodeTreeElement.revealed = function(element)
1286 {
1287     if (!element._listItemNode || !element.treeOutline || !element.treeOutline._childrenListNode)
1288         return;
1289     var parent = element.treeOutline.panel.views.dom.contentElement.treeContentElement;
1290     parent.scrollToElement(element._listItemNode);
1291 }
1292
1293 WebInspector.DOMNodeTreeElement.selected = function(element)
1294 {
1295     var panel = element.treeOutline.panel;
1296     panel.focusedDOMNode = element.representedObject;
1297
1298     // Call updateSelection twice to make sure the height is correct,
1299     // the first time might have a bad height because we are in a weird tree state
1300     element.updateSelection(element);
1301     setTimeout(function() { element.updateSelection(element) }, 0);
1302 }
1303
1304 WebInspector.DOMNodeTreeElement.doubleClicked = function(element)
1305 {
1306     var panel = element.treeOutline.panel;
1307     panel.rootDOMNode = element.representedObject.parentNode;
1308     panel.focusedDOMNode = element.representedObject;
1309 }
1310
1311 WebInspector.StylePropertyTreeElement = function(prop, computedStyle, usedProperties)
1312 {
1313     var overloadCount = 0;
1314     var priority;
1315     if (prop.subProperties && prop.subProperties.length > 1) {
1316         for (var i = 0; i < prop.subProperties.length; ++i) {
1317             var name = prop.subProperties[i];
1318             if (!priority)
1319                 priority = prop.style.getPropertyPriority(name);
1320             if (prop.unusedProperties[name])
1321                 overloadCount++;
1322         }
1323     }
1324
1325     if (!priority)
1326         priority = prop.style.getPropertyPriority(prop.name);
1327
1328     var overloaded = (prop.unusedProperties[prop.name] || overloadCount === prop.subProperties.length);
1329     var title = WebInspector.StylePropertyTreeElement.createTitle(prop.name, prop.style, overloaded, priority, computedStyle, usedProperties);
1330
1331     var item = new TreeElement(title, prop, (prop.subProperties && prop.subProperties.length > 1));
1332     item.computedStyle = computedStyle;
1333     item.onpopulate = WebInspector.StylePropertyTreeElement.populate;
1334     return item;
1335 }
1336
1337 WebInspector.StylePropertyTreeElement.createTitle = function(name, style, overloaded, priority, computed, usedProperties)
1338 {
1339     // "Nicknames" for some common values that are easier to read.
1340     var valueNickname = {
1341         "rgb(0, 0, 0)": "black",
1342         "rgb(255, 255, 255)": "white",
1343         "rgba(0, 0, 0, 0)": "transparent"
1344     };
1345
1346     var alwaysShowComputedProperties = { "display": true, "height": true, "width": true };
1347
1348     var value = style.getPropertyValue(name);
1349
1350     var textValue = value;
1351     if (value) {
1352         var urls = value.match(/url\([^)]+\)/);
1353         if (urls) {
1354             for (var i = 0; i < urls.length; ++i) {
1355                 var url = urls[i].substring(4, urls[i].length - 1);
1356                 textValue = textValue.replace(urls[i], "url(" + WebInspector.linkifyURL(url) + ")");
1357             }
1358         } else {
1359             if (value in valueNickname)
1360                 textValue = valueNickname[value];
1361             textValue = textValue.escapeHTML();
1362         }
1363     }
1364
1365     var classes = [];
1366     if (!computed && (style.isPropertyImplicit(name) || value == "initial"))
1367         classes.push("implicit");
1368     if (computed && !usedProperties[name] && !alwaysShowComputedProperties[name])
1369         classes.push("inherited");
1370     if (overloaded)
1371         classes.push("overloaded");
1372
1373     var title = "";
1374     if (classes.length)
1375         title += "<span class=\"" + classes.join(" ") + "\">";
1376
1377     title += "<span class=\"name\">" + name.escapeHTML() + "</span>: ";
1378     title += "<span class=\"value\">" + textValue;
1379     if (priority && priority.length)
1380         title += " !" + priority;
1381     title += "</span>;";
1382
1383     if (value) {
1384         // FIXME: this dosen't catch keyword based colors like black and white
1385         var colors = value.match(/(rgb\([0-9]+,\s?[0-9]+,\s?[0-9]+\))|(rgba\([0-9]+,\s?[0-9]+,\s?[0-9]+,\s?[0-9]+\))/g);
1386         if (colors) {
1387             var colorsLength = colors.length;
1388             for (var i = 0; i < colorsLength; ++i)
1389                 title += "<span class=\"swatch\" style=\"background-color: " + colors[i] + "\"></span>";
1390         }
1391     }
1392
1393     if (classes.length)
1394         title += "</span>";
1395
1396     return title;
1397 }
1398
1399 WebInspector.StylePropertyTreeElement.populate = function(element)
1400 {
1401     if (element.children.length)
1402         return;
1403
1404     var prop = element.representedObject;
1405     if (prop.subProperties && prop.subProperties.length > 1) {
1406         for (var i = 0; i < prop.subProperties.length; ++i) {
1407             var name = prop.subProperties[i];
1408             var overloaded = (prop.unusedProperties[prop.name] || prop.unusedProperties[name]);
1409             var priority = prop.style.getPropertyPriority(name);
1410             var title = WebInspector.StylePropertyTreeElement.createTitle(name, prop.style, overloaded, priority, element.computedStyle);
1411             var subitem = new TreeElement(title, {}, false);
1412             element.appendChild(subitem);
1413         }
1414     }
1415 }
1416
1417 WebInspector.RequestResponseTreeElement = function(name, object)
1418 {
1419     var title = "<span class=\"name\">" + name.escapeHTML() + "</span>";
1420
1421     var hasChildren = object instanceof Object;
1422     if (!hasChildren) {
1423         var objectAsString = object.toString();
1424         title += ": <span class=\"value\">" + objectAsString.escapeHTML() + "</span>";
1425     }
1426
1427     var item = new TreeElement(title, object, hasChildren);
1428     item.onpopulate = WebInspector.RequestResponseTreeElement.onpopulate;
1429     return item;
1430 }
1431
1432 WebInspector.RequestResponseTreeElement.onpopulate = function(element)
1433 {
1434     if (element.children.length)
1435         return;
1436
1437     for (var field in element.representedObject) {
1438         var item = new WebInspector.RequestResponseTreeElement(field, element.representedObject[field]);
1439         element.appendChild(item);
1440     }
1441 }
1442
1443 WebInspector.DOMPropertyTreeElement = function(name, object)
1444 {
1445     var title = "<span class=\"name\">" + name.escapeHTML() + "</span>: ";
1446     title += "<span class=\"value\">" + Object.describe(object[name], true).escapeHTML() + "</span>";
1447
1448     var hasChildren = false;
1449     var type = typeof object[name];
1450     if (object[name] && (type === "object" || type === "function")) {
1451         for (value in object[name]) {
1452             hasChildren = true;
1453             break;
1454         }
1455     }
1456
1457     var representedObj = { object: object, name: name };
1458     var item = new TreeElement(title, representedObj, hasChildren);
1459     item.onpopulate = WebInspector.DOMPropertyTreeElement.populate;
1460     return item;
1461 }
1462
1463 WebInspector.DOMPropertyTreeElement.populate = function(element)
1464 {
1465     if (element.children.length)
1466         return;
1467
1468     var parent = element.representedObject.object[element.representedObject.name];
1469     var properties = Object.sortedProperties(parent);
1470     for (var i = 0; i < properties.length; ++i) {
1471         if (properties[i] === "__treeElementIdentifier")
1472             continue;
1473         var item = new WebInspector.DOMPropertyTreeElement(properties[i], parent);
1474         element.appendChild(item);
1475     }
1476 }