Reviewed by Tim Hatcher.
[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.resizeArea = document.createElement("div");
275             this.views.dom.contentElement.resizeArea.className = "sidebarResizeArea";
276             this.views.dom.contentElement.resizeArea.addEventListener("mousedown", function(event) { panel.rightSidebarResizerDragStart(event) }, false);
277
278             this.views.dom.contentElement.appendChild(this.views.dom.contentElement.sideContentElement);
279             this.views.dom.contentElement.appendChild(this.views.dom.contentElement.sidebarElement);
280             this.views.dom.contentElement.appendChild(this.views.dom.contentElement.resizeArea);
281
282             this.rootDOMNode = this.resource.documentNode;
283         } else if (this.resource.category === WebInspector.resourceCategories.images) {
284             this.views.source.contentElement.removeStyleClass("source");
285             this.views.source.contentElement.addStyleClass("image");
286
287             var container = document.createElement("div");
288             container.className = "image";
289             this.views.source.contentElement.appendChild(container);
290
291             this.imagePreviewElement = document.createElement("img");
292             this.imagePreviewElement.setAttribute("src", this.resource.url);
293
294             container.appendChild(this.imagePreviewElement);
295
296             container = document.createElement("div");
297             container.className = "info";
298             this.views.source.contentElement.appendChild(container);
299
300             var imageNameElement = document.createElement("h1");
301             imageNameElement.className = "title";
302             imageNameElement.textContent = this.resource.displayName;
303             container.appendChild(imageNameElement);
304
305             var infoListElement = document.createElement("dl");
306             infoListElement.className = "infoList";
307
308             var imageProperties = [
309                 {name: "Dimensions", value: this.imagePreviewElement.naturalWidth + " \u00D7 " + this.imagePreviewElement.height},
310                 {name: "File size", value: (this.resource.contentLength / 1024).toPrecision(2) + "KB"},
311                 {name: "MIME type", value: this.resource.mimeType}
312             ];
313
314             var listHTML = '';
315             for (var i = 0; i < imageProperties.length; ++i)
316                 listHTML += "<dt>" + imageProperties[i].name + "</dt><dd>" + imageProperties[i].value + "</dd>";
317
318             infoListElement.innerHTML = listHTML;
319             container.appendChild(infoListElement);
320         } else if (this.resource.category === WebInspector.resourceCategories.fonts) {
321             var panel = this;
322             var resizeListener = function() {
323                 panel.updateFontPreviewSize();
324             };
325
326             this.views.source.onshow = function() {
327                 this.panel.updateFontPreviewSize();
328                 window.addEventListener("resize", resizeListener, false);
329             };
330
331             this.views.source.onhide = function() {
332                 window.removeEventListener("resize", resizeListener, false);
333             };
334
335             window.addEventListener("resize", resizeListener, false);
336
337             this.views.source.contentElement.removeStyleClass("source");
338             this.views.source.contentElement.addStyleClass("font");
339
340             var uniqueFontName = "WebInspectorFontPreview" + this.resource.identifier;
341
342             this.fontPreviewElement = document.createElement("div");
343             this.fontPreviewElement.className = "preview";
344             this.views.source.contentElement.appendChild(this.fontPreviewElement);
345
346             this.fontPreviewElement.style.setProperty("font-family", uniqueFontName, null);
347             this.fontPreviewElement.innerHTML = "ABCDEFGHIJKLM<br>NOPQRSTUVWXYZ<br>abcdefghijklm<br>nopqrstuvwxyz<br>1234567890";
348
349             this.updateFontPreviewSize();
350         } else if (this.resource.category === WebInspector.resourceCategories.stylesheets || this.resource.category === WebInspector.resourceCategories.scripts) {
351             this.views.source.contentElement.addStyleClass(this.resource.category.title.toLowerCase());
352             this._createSourceFrame();
353         } else {
354             if (this.resource.category)
355                 this.views.source.contentElement.addStyleClass(this.resource.category.title.toLowerCase());
356         }
357     },
358
359     updateFontPreviewSize: function ()
360     {
361         if (!this.fontPreviewElement)
362             return;
363
364         this.fontPreviewElement.removeStyleClass("preview");
365
366         var measureFontSize = 50;
367         this.fontPreviewElement.style.setProperty("position", "absolute", null);
368         this.fontPreviewElement.style.setProperty("font-size", measureFontSize + "px", null);
369         this.fontPreviewElement.style.removeProperty("height");
370
371         var height = this.fontPreviewElement.offsetHeight;
372         var width = this.fontPreviewElement.offsetWidth;
373
374         var containerHeight = this.views.source.contentElement.offsetHeight;
375         var containerWidth = this.views.source.contentElement.offsetWidth;
376
377         if (!height || !width || !containerHeight || !containerWidth) {
378             this.fontPreviewElement.style.removeProperty("font-size");
379             this.fontPreviewElement.style.removeProperty("position");
380             this.fontPreviewElement.addStyleClass("preview");
381             return;
382         }
383
384         var lineCount = this.fontPreviewElement.getElementsByTagName("br").length + 1;
385         var realLineHeight = Math.floor(height / lineCount);
386         var fontSizeLineRatio = measureFontSize / realLineHeight;
387         var widthRatio = containerWidth / width;
388         var heightRatio = containerHeight / height;
389
390         if (heightRatio < widthRatio)
391             var finalFontSize = Math.floor(realLineHeight * heightRatio * fontSizeLineRatio) - 1;
392         else
393             var finalFontSize = Math.floor(realLineHeight * widthRatio * fontSizeLineRatio) - 1;
394
395         this.fontPreviewElement.style.setProperty("font-size", finalFontSize + "px", null);
396         this.fontPreviewElement.style.setProperty("height", this.fontPreviewElement.offsetHeight + "px", null);
397         this.fontPreviewElement.style.removeProperty("position");
398
399         this.fontPreviewElement.addStyleClass("preview");
400     },
401
402     get currentView()
403     {
404         return this._currentView;
405     },
406
407     set currentView(x)
408     {
409         if (this._currentView === x)
410             return;
411
412         if (this._currentView) {
413             this._currentView.buttonElement.removeStyleClass("selected");
414             this._currentView.contentElement.removeStyleClass("selected");
415             if ("onhide" in this._currentView)
416                 this._currentView.onhide();
417         }
418
419         this._currentView = x;
420
421         if (x) {
422             x.buttonElement.addStyleClass("selected");
423             x.contentElement.addStyleClass("selected");
424             if ("onshow" in x)
425                 x.onshow();
426
427             if (x.contentElement.parentNode !== this.element)
428                 InspectorController.log("Tried to set currentView to a view " + x.title + " whose contentElement is a non-child");
429         }
430     },
431
432     get rootDOMNode()
433     {
434         return this._rootDOMNode;
435     },
436
437     set rootDOMNode(x)
438     {
439         if (this._rootDOMNode === x)
440             return;
441
442         this._rootDOMNode = x;
443
444         this.updateBreadcrumb();
445         this.updateTreeOutline();
446     },
447
448     get focusedDOMNode()
449     {
450         return this._focusedDOMNode;
451     },
452
453     set focusedDOMNode(x)
454     {
455         if (this.resource.category !== WebInspector.resourceCategories.documents) {
456             InspectorController.log("Called set focusedDOMNode on a non-document resource " + this.resource.displayName + " which is not a document");
457             return;
458         }
459
460         if (this._focusedDOMNode === x) {
461             var nodeItem = this.revealNode(x);
462             if (nodeItem)
463                 nodeItem.select();
464             return;
465         }
466
467         this._focusedDOMNode = x;
468
469         this.updateBreadcrumb();
470
471         for (var pane in this.views.dom.contentElement.sidebarPanes)
472             this.views.dom.contentElement.sidebarPanes[pane].needsUpdate = true;
473
474         this.updateStyles();
475         this.updateMetrics();
476         this.updateProperties();
477
478         InspectorController.highlightDOMNode(x);
479
480         var nodeItem = this.revealNode(x);
481         if (nodeItem)
482             nodeItem.select();
483     },
484
485     revealNode: function(node)
486     {
487         var nodeItem = this.domTreeOutline.findTreeElement(node, function(a, b) { return isAncestorNode.call(a, b); }, function(a) { return a.parentNode; });
488         if (!nodeItem)
489             return;
490
491         nodeItem.reveal();
492         return nodeItem;
493     },
494
495     updateTreeOutline: function()
496     {
497         this.domTreeOutline.removeChildrenRecursive();
498
499         if (!this.rootDOMNode)
500             return;
501
502         // FIXME: this could use findTreeElement to reuse a tree element if it already exists
503         var node = (Preferences.ignoreWhitespace ? firstChildSkippingWhitespace.call(this.rootDOMNode) : this.rootDOMNode.firstChild);
504         while (node) {
505             var item = new WebInspector.DOMNodeTreeElement(node);
506             this.domTreeOutline.appendChild(item);
507             node = Preferences.ignoreWhitespace ? nextSiblingSkippingWhitespace.call(node) : node.nextSibling;
508         }
509
510         this.updateTreeSelection();
511     },
512
513     updateBreadcrumb: function()
514     {
515         if (!this.views || !this.views.dom.contentElement)
516             return;
517         var crumbs = this.views.dom.contentElement.innerCrumbsElement;
518         if (!crumbs)
519             return;
520
521         var handled = false;
522         var foundRoot = false;
523         var crumb = crumbs.firstChild;
524         while (crumb) {
525             if (crumb.representedObject === this.rootDOMNode)
526                 foundRoot = true;
527
528             if (foundRoot)
529                 crumb.addStyleClass("hidden");
530             else
531                 crumb.removeStyleClass("hidden");
532
533             if (crumb.representedObject === this.focusedDOMNode) {
534                 crumb.addStyleClass("selected");
535                 handled = true;
536             } else {
537                 crumb.removeStyleClass("selected");
538             }
539
540             crumb = crumb.nextSibling;
541         }
542
543         if (handled)
544             return;
545
546         crumbs.removeChildren();
547
548         var panel = this;
549         var selectCrumbFunction = function(event)
550         {
551             if (event.currentTarget.hasStyleClass("hidden"))
552                 panel.rootDOMNode = event.currentTarget.representedObject.parentNode;
553             panel.focusedDOMNode = event.currentTarget.representedObject;
554             event.preventDefault();
555             event.stopPropagation();
556         }
557
558         var selectCrumbRootFunction = function(event)
559         {
560             panel.rootDOMNode = event.currentTarget.representedObject.parentNode;
561             panel.focusedDOMNode = event.currentTarget.representedObject;
562             event.preventDefault();
563             event.stopPropagation();
564         }
565
566         foundRoot = false;
567         var current = this.focusedDOMNode;
568         while (current) {
569             if (current.nodeType === Node.DOCUMENT_NODE)
570                 break;
571
572             if (current === this.rootDOMNode)
573                 foundRoot = true;
574
575             var crumb = document.createElement("span");
576             crumb.className = "crumb";
577             crumb.representedObject = current;
578             crumb.addEventListener("mousedown", selectCrumbFunction, false);
579             crumb.addEventListener("dblclick", selectCrumbRootFunction, false);
580
581             var crumbTitle;
582             switch (current.nodeType) {
583                 case Node.ELEMENT_NODE:
584                     crumbTitle = current.nodeName.toLowerCase();
585     
586                     var value = current.getAttribute("id");
587                     if (value && value.length)
588                         crumbTitle += "#" + value;
589
590                     value = current.getAttribute("class");
591                     if (value && value.length) {
592                         var classes = value.split(/\s+/);
593                         var classesLength = classes.length;
594                         for (var i = 0; i < classesLength; ++i) {
595                             value = classes[i];
596                             if (value && value.length)
597                                 crumbTitle += "." + value;
598                         }
599                     }
600
601                     break;
602
603                 case Node.TEXT_NODE:
604                     if (isNodeWhitespace.call(current))
605                         crumbTitle = "(whitespace)";
606                     else
607                         crumbTitle = "(text)";
608                     break
609
610                 case Node.COMMENT_NODE:
611                     crumbTitle = "<!-->";
612                     break;
613
614                 default:
615                     crumbTitle = current.nodeName.toLowerCase();
616             }
617
618             crumb.textContent = crumbTitle;
619
620             if (foundRoot)
621                 crumb.addStyleClass("hidden");
622             if (current === this.focusedDOMNode)
623                 crumb.addStyleClass("selected");
624             if (!crumbs.childNodes.length)
625                 crumb.addStyleClass("end");
626             if (current.parentNode.nodeType === Node.DOCUMENT_NODE)
627                 crumb.addStyleClass("start");
628
629             crumbs.appendChild(crumb);
630             current = current.parentNode;
631         }
632     },
633
634     updateStyles: function()
635     {
636         if (!this.views.dom.contentElement.sidebarPanes.styles.expanded)
637             return;
638         if (!this.views.dom.contentElement.sidebarPanes.styles.needsUpdate)
639             return;
640
641         this.views.dom.contentElement.sidebarPanes.styles.needsUpdate = false;
642
643         var stylesBody = this.views.dom.contentElement.sidebarPanes.styles.bodyElement;
644
645         stylesBody.removeChildren();
646         this.views.dom.contentElement.sidebarPanes.styles.sections = [];
647
648         if (!this.focusedDOMNode)
649             return;
650
651         var styleNode = this.focusedDOMNode;
652         if (styleNode.nodeType === Node.TEXT_NODE && styleNode.parentNode)
653             styleNode = styleNode.parentNode;
654
655         var styleRules = [];
656         var styleProperties = [];
657
658         if (styleNode.nodeType === Node.ELEMENT_NODE) {
659             var propertyCount = [];
660
661             var computedStyle = styleNode.ownerDocument.defaultView.getComputedStyle(styleNode);
662             if (computedStyle && computedStyle.length)
663                 styleRules.push({ isComputedStyle: true, selectorText: "Computed Style", style: computedStyle });
664
665             var styleNodeName = styleNode.nodeName.toLowerCase();
666             for (var i = 0; i < styleNode.attributes.length; ++i) {
667                 var attr = styleNode.attributes[i];
668                 if (attr.style) {
669                     var attrStyle = { attrName: attr.name, style: attr.style };
670                     attrStyle.subtitle = "element\u2019s \u201C" + attr.name + "\u201D attribute";
671                     attrStyle.selectorText = styleNodeName + "[" + attr.name;
672                     if (attr.value.length)
673                         attrStyle.selectorText += "=" + attr.value;
674                     attrStyle.selectorText += "]";
675                     styleRules.push(attrStyle);
676                 }
677             }
678
679             if (styleNode.style && styleNode.style.length) {
680                 var inlineStyle = { selectorText: "Inline Style Attribute", style: styleNode.style };
681                 inlineStyle.subtitle = "element\u2019s \u201Cstyle\u201D attribute";
682                 styleRules.push(inlineStyle);
683             }
684
685             var matchedStyleRules = styleNode.ownerDocument.defaultView.getMatchedCSSRules(styleNode, "", !Preferences.showUserAgentStyles);
686             if (matchedStyleRules) {
687                 for (var i = matchedStyleRules.length - 1; i >= 0; --i)
688                     styleRules.push(matchedStyleRules[i]);
689             }
690
691             var priorityUsed = false;
692             var usedProperties = {};
693             var shorthandProperties = {};
694             for (var i = 0; i < styleRules.length; ++i) {
695                 styleProperties[i] = [];
696
697                 var style = styleRules[i].style;
698                 var styleShorthandLookup = [];
699                 for (var j = 0; j < style.length; ++j) {
700                     var prop = null;
701                     var name = style[j];
702                     var shorthand = style.getPropertyShorthand(name);
703                     if (!shorthand && styleRules[i].isComputedStyle)
704                         shorthand = shorthandProperties[name];
705
706                     if (shorthand) {
707                         prop = styleShorthandLookup[shorthand];
708                         shorthandProperties[name] = shorthand;
709                     }
710
711                     if (!priorityUsed && style.getPropertyPriority(name).length)
712                         priorityUsed = true;
713
714                     if (prop) {
715                         prop.subProperties.push(name);
716                     } else {
717                         prop = { style: style, subProperties: [name], unusedProperties: [], name: (shorthand ? shorthand : name) };
718                         styleProperties[i].push(prop);
719
720                         if (shorthand) {
721                             styleShorthandLookup[shorthand] = prop;
722                             if (!styleRules[i].isComputedStyle) {
723                                 if (!propertyCount[shorthand]) {
724                                     propertyCount[shorthand] = 1;
725                                 } else {
726                                     prop.unusedProperties[shorthand] = true;
727                                     propertyCount[shorthand]++;
728                                 }
729                             }
730                         }
731                     }
732
733                     if (styleRules[i].isComputedStyle)
734                         continue;
735
736                     usedProperties[name] = true;
737                     if (shorthand)
738                         usedProperties[shorthand] = true;
739
740                     if (!propertyCount[name]) {
741                         propertyCount[name] = 1;
742                     } else {
743                         prop.unusedProperties[name] = true;
744                         propertyCount[name]++;
745                     }
746                 }
747             }
748
749             if (priorityUsed) {
750                 // walk the properties again and account for !important
751                 var priorityCount = [];
752                 for (var i = 0; i < styleRules.length; ++i) {
753                     if (styleRules[i].isComputedStyle)
754                         continue;
755                     var style = styleRules[i].style;
756                     for (var j = 0; j < styleProperties[i].length; ++j) {
757                         var prop = styleProperties[i][j];
758                         for (var k = 0; k < prop.subProperties.length; ++k) {
759                             var name = prop.subProperties[k];
760                             if (style.getPropertyPriority(name).length) {
761                                 if (!priorityCount[name]) {
762                                     if (prop.unusedProperties[name])
763                                         prop.unusedProperties[name] = false;
764                                     priorityCount[name] = 1;
765                                 } else {
766                                     priorityCount[name]++;
767                                 }
768                             } else if (priorityCount[name]) {
769                                 prop.unusedProperties[name] = true;
770                             }
771                         }
772                     }
773                 }
774             }
775
776             var styleRulesLength = styleRules.length;
777             for (var i = 0; i < styleRulesLength; ++i) {
778                 var selectorText = styleRules[i].selectorText;
779
780                 var section = new WebInspector.PropertiesSection(selectorText);
781                 section.expanded = true;
782
783                 if (styleRules[i].isComputedStyle) {
784                     if (Preferences.showInheritedComputedStyleProperties)
785                         section.element.addStyleClass("show-inherited");
786
787                     var showInheritedLabel = document.createElement("label");
788                     var showInheritedInput = document.createElement("input");
789                     showInheritedInput.type = "checkbox";
790                     showInheritedInput.checked = Preferences.showInheritedComputedStyleProperties;
791
792                     var computedStyleSection = section;
793                     var showInheritedToggleFunction = function(event) {
794                         Preferences.showInheritedComputedStyleProperties = showInheritedInput.checked;
795                         if (Preferences.showInheritedComputedStyleProperties)
796                             computedStyleSection.element.addStyleClass("show-inherited");
797                         else
798                             computedStyleSection.element.removeStyleClass("show-inherited");
799                         event.stopPropagation();
800                     };
801
802                     showInheritedLabel.addEventListener("click", showInheritedToggleFunction, false);
803
804                     showInheritedLabel.appendChild(showInheritedInput);
805                     showInheritedLabel.appendChild(document.createTextNode("Show implicit properties"));
806                     section.subtitleElement.appendChild(showInheritedLabel);
807                 } else {
808                     var sheet;
809                     var file = false;
810                     if (styleRules[i].subtitle)
811                         sheet = styleRules[i].subtitle;
812                     else if (styleRules[i].parentStyleSheet && styleRules[i].parentStyleSheet.href) {
813                         var url = styleRules[i].parentStyleSheet.href;
814                         sheet = WebInspector.linkifyURL(url, url.trimURL(WebInspector.mainResource.domain).escapeHTML());
815                         file = true;
816                     } else if (styleRules[i].parentStyleSheet && !styleRules[i].parentStyleSheet.ownerNode)
817                         sheet = "user agent stylesheet";
818                     else
819                         sheet = "inline stylesheet";
820
821                     if (file)
822                         section.subtitleElement.addStyleClass("file");
823
824                     section.subtitle = sheet;
825                 }
826
827                 stylesBody.appendChild(section.element);
828                 this.views.dom.contentElement.sidebarPanes.styles.sections.push(section);
829
830                 var properties = styleProperties[i];
831                 var isComputedStyle = styleRules[i].isComputedStyle;
832
833                 for (var j = 0; j < properties.length; ++j) {
834                     var prop = properties[j];
835
836                     var propTreeElement = new WebInspector.StylePropertyTreeElement(prop, isComputedStyle, usedProperties);
837                     section.propertiesTreeOutline.appendChild(propTreeElement);
838                 }
839             }
840         } else {
841             // can't style this node
842         }
843     },
844
845     updateMetrics: function()
846     {
847         if (!this.views.dom.contentElement.sidebarPanes.metrics.expanded)
848             return;
849         if (!this.views.dom.contentElement.sidebarPanes.metrics.needsUpdate)
850             return;
851
852         this.views.dom.contentElement.sidebarPanes.metrics.needsUpdate = false;
853
854         var metricsBody = this.views.dom.contentElement.sidebarPanes.metrics.bodyElement;
855
856         metricsBody.removeChildren();
857
858         if (!this.focusedDOMNode)
859             return;
860
861         var style;
862         if (this.focusedDOMNode.nodeType === Node.ELEMENT_NODE)
863             style = this.focusedDOMNode.ownerDocument.defaultView.getComputedStyle(this.focusedDOMNode);
864         if (!style)
865             return;
866
867         var metricsElement = document.createElement("div");
868         metricsElement.className = "metrics";
869
870         function boxPartValue(style, name, suffix)
871         {
872             var value = style.getPropertyValue(name + suffix);
873             if (value === "" || value === "0px")
874                 value = "\u2012";
875             return value.replace(/px$/, "");
876         }
877
878         // Display types for which margin is ignored.
879         var noMarginDisplayType = {
880             "table-cell": true,
881             "table-column": true,
882             "table-column-group": true,
883             "table-footer-group": true,
884             "table-header-group": true,
885             "table-row": true,
886             "table-row-group": true
887         };
888
889         // Display types for which padding is ignored.
890         var noPaddingDisplayType = {
891             "table-column": true,
892             "table-column-group": true,
893             "table-footer-group": true,
894             "table-header-group": true,
895             "table-row": true,
896             "table-row-group": true
897         };
898
899         var boxes = ["content", "padding", "border", "margin"];
900         var previousBox;
901         for (var i = 0; i < boxes.length; ++i) {
902             var name = boxes[i];
903
904             if (name === "margin" && noMarginDisplayType[style.display])
905                 continue;
906             if (name === "padding" && noPaddingDisplayType[style.display])
907                 continue;
908
909             var boxElement = document.createElement("div");
910             boxElement.className = name;
911
912             if (boxes[i] === "content") {
913                 var width = style.width.replace(/px$/, "");
914                 var height = style.height.replace(/px$/, "");
915                 boxElement.textContent = width + " \u00D7 " + height;
916             } else {
917                 var suffix = boxes[i] === "border" ? "-width" : "";
918
919                 var labelElement = document.createElement("div");
920                 labelElement.className = "label";
921                 labelElement.textContent = name;
922                 boxElement.appendChild(labelElement);
923
924                 var topElement = document.createElement("div");
925                 topElement.className = "top";
926                 topElement.textContent = boxPartValue(style, name + "-top", suffix);
927                 boxElement.appendChild(topElement);
928
929                 var leftElement = document.createElement("div");
930                 leftElement.className = "left";
931                 leftElement.textContent = boxPartValue(style, name + "-left", suffix);
932                 boxElement.appendChild(leftElement);
933
934                 if (previousBox)
935                     boxElement.appendChild(previousBox);
936
937                 var rightElement = document.createElement("div");
938                 rightElement.className = "right";
939                 rightElement.textContent = boxPartValue(style, name + "-right", suffix);
940                 boxElement.appendChild(rightElement);
941
942                 var bottomElement = document.createElement("div");
943                 bottomElement.className = "bottom";
944                 bottomElement.textContent = boxPartValue(style, name + "-bottom", suffix);
945                 boxElement.appendChild(bottomElement);
946             }
947
948             previousBox = boxElement;
949         }
950
951         metricsElement.appendChild(previousBox);
952         metricsBody.appendChild(metricsElement);
953     },
954
955     updateProperties: function()
956     {
957         if (!this.views.dom.contentElement.sidebarPanes.properties.expanded)
958             return;
959         if (!this.views.dom.contentElement.sidebarPanes.properties.needsUpdate)
960             return;
961
962         this.views.dom.contentElement.sidebarPanes.properties.needsUpdate = false;
963
964         var propertiesBody = this.views.dom.contentElement.sidebarPanes.properties.bodyElement;
965
966         propertiesBody.removeChildren();
967         this.views.dom.contentElement.sidebarPanes.properties.sections = [];
968
969         if (!this.focusedDOMNode)
970             return;
971
972         for (var prototype = this.focusedDOMNode; prototype; prototype = prototype.__proto__) {
973             var hasChildren = false;
974             for (var prop in prototype) {
975                 if (prop === "__treeElementIdentifier")
976                     continue;
977                 if (!prototype.hasOwnProperty(prop))
978                     continue;
979
980                 hasChildren = true;
981                 break;
982             }
983
984             if (!hasChildren)
985                 continue;
986
987             var title = Object.describe(prototype);
988             var subtitle;
989             if (title.match(/Prototype$/)) {
990                 title = title.replace(/Prototype$/, "");
991                 subtitle = "Prototype";
992             }
993             var section = new WebInspector.PropertiesSection(title, subtitle);
994             section.onpopulate = WebInspector.DOMPropertiesSection.onpopulate(prototype);
995
996             propertiesBody.appendChild(section.element);
997             this.views.dom.contentElement.sidebarPanes.properties.sections.push(section);
998         }
999     },
1000
1001     handleKeyEvent: function(event)
1002     {
1003         if (this.domTreeOutline && this.currentView && this.currentView === this.views.dom)
1004             this.domTreeOutline.handleKeyEvent(event);
1005     },
1006
1007     rightSidebarResizerDragStart: function(event)
1008     {
1009         var panel = this; 
1010         WebInspector.dividerDragStart(this.views.dom.contentElement.sidebarElement, function(event) { panel.rightSidebarResizerDrag(event) }, function(event) { panel.rightSidebarResizerDragEnd(event) }, event, "col-resize");
1011     },
1012
1013     rightSidebarResizerDragEnd: function(event)
1014     {
1015         var panel = this;
1016         WebInspector.dividerDragEnd(this.views.dom.contentElement.sidebarElement, function(event) { panel.rightSidebarResizerDrag(event) }, function(event) { panel.rightSidebarResizerDragEnd(event) }, event);
1017     },
1018
1019     rightSidebarResizerDrag: function(event)
1020     {
1021         var rightSidebar = this.views.dom.contentElement.sidebarElement;
1022         if (rightSidebar.dragging == true) {
1023             var x = event.clientX + window.scrollX;
1024
1025             var leftSidebarWidth = document.defaultView.getComputedStyle(document.getElementById("sidebar")).getPropertyCSSValue("width").getFloatValue(CSSPrimitiveValue.CSS_PX);
1026             var newWidth = Number.constrain(window.innerWidth - x, 100, window.innerWidth - leftSidebarWidth - 100);
1027
1028             if (x == newWidth)
1029                 rightSidebar.dragLastX = x;
1030
1031             rightSidebar.style.width = newWidth + "px";
1032             this.views.dom.contentElement.sideContentElement.style.right = newWidth + "px";
1033             this.views.dom.contentElement.resizeArea.style.right = (newWidth - 3) + "px";
1034             event.preventDefault();
1035         }
1036     }
1037 }
1038
1039 WebInspector.ResourcePanel.sourceFrameCounter = 0;
1040
1041 WebInspector.ResourcePanel.prototype.__proto__ = WebInspector.Panel.prototype;
1042
1043 WebInspector.SidebarPane = function(title)
1044 {
1045     this.element = document.createElement("div");
1046     this.element.className = "pane";
1047
1048     this.titleElement = document.createElement("div");
1049     this.titleElement.className = "title";
1050
1051     var pane = this;
1052     this.titleElement.addEventListener("click", function() { pane.expanded = !pane.expanded; }, false);
1053
1054     this.bodyElement = document.createElement("div");
1055     this.bodyElement.className = "body";
1056
1057     this.element.appendChild(this.titleElement);
1058     this.element.appendChild(this.bodyElement);
1059
1060     this.title = title;
1061     this.growbarVisible = false;
1062     this.expanded = false;
1063     this.onexpand = null;
1064     this.oncollapse = null;
1065 }
1066
1067 WebInspector.SidebarPane.prototype = {
1068     get title()
1069     {
1070         return this._title;
1071     },
1072
1073     set title(x)
1074     {
1075         if (this._title === x)
1076             return;
1077         this._title = x;
1078         this.titleElement.textContent = x;
1079     },
1080
1081     get growbarVisible()
1082     {
1083         return this._growbarVisible;
1084     },
1085
1086     set growbarVisible(x)
1087     {
1088         if (this._growbarVisible === x)
1089             return;
1090
1091         this._growbarVisible = x;
1092
1093         if (x && !this._growbarElement) {
1094             this._growbarElement = document.createElement("div");
1095             this._growbarElement.className = "growbar";
1096             this.element.appendChild(this._growbarElement);
1097         } else if (!x && this._growbarElement) {
1098             if (this._growbarElement.parentNode)
1099                 this._growbarElement.parentNode(this._growbarElement);
1100             delete this._growbarElement;
1101         }
1102     },
1103
1104     get expanded()
1105     {
1106         return this._expanded;
1107     },
1108
1109     set expanded(x)
1110     {
1111         if (x)
1112             this.expand();
1113         else
1114             this.collapse();
1115     },
1116
1117     expand: function()
1118     {
1119         if (this._expanded)
1120             return;
1121         this._expanded = true;
1122         this.element.addStyleClass("expanded");
1123         if (this.onexpand)
1124             this.onexpand(this);
1125     },
1126
1127     collapse: function()
1128     {
1129         if (!this._expanded)
1130             return;
1131         this._expanded = false;
1132         this.element.removeStyleClass("expanded");
1133         if (this.oncollapse)
1134             this.oncollapse(this);
1135     }
1136 }
1137
1138 WebInspector.PropertiesSection = function(title, subtitle)
1139 {
1140     this.element = document.createElement("div");
1141     this.element.className = "section";
1142
1143     this.headerElement = document.createElement("div");
1144     this.headerElement.className = "header";
1145
1146     this.titleElement = document.createElement("div");
1147     this.titleElement.className = "title";
1148
1149     this.subtitleElement = document.createElement("div");
1150     this.subtitleElement.className = "subtitle";
1151
1152     this.headerElement.appendChild(this.titleElement);
1153     this.headerElement.appendChild(this.subtitleElement);
1154
1155     var section = this;
1156     this.headerElement.addEventListener("click", function() { section.expanded = !section.expanded; }, false);
1157
1158     this.propertiesElement = document.createElement("ol");
1159     this.propertiesElement.className = "properties";
1160     this.propertiesTreeOutline = new TreeOutline(this.propertiesElement);
1161
1162     this.element.appendChild(this.headerElement);
1163     this.element.appendChild(this.propertiesElement);
1164
1165     this.title = title;
1166     this.subtitle = subtitle;
1167     this.expanded = false;
1168 }
1169
1170 WebInspector.PropertiesSection.prototype = {
1171     get title()
1172     {
1173         return this._title;
1174     },
1175
1176     set title(x)
1177     {
1178         if (this._title === x)
1179             return;
1180         this._title = x;
1181         this.titleElement.textContent = x;
1182     },
1183
1184     get subtitle()
1185     {
1186         return this._subtitle;
1187     },
1188
1189     set subtitle(x)
1190     {
1191         if (this._subtitle === x)
1192             return;
1193         this._subtitle = x;
1194         this.subtitleElement.innerHTML = x;
1195     },
1196
1197     get expanded()
1198     {
1199         return this._expanded;
1200     },
1201
1202     set expanded(x)
1203     {
1204         if (x)
1205             this.expand();
1206         else
1207             this.collapse();
1208     },
1209
1210     expand: function()
1211     {
1212         if (this._expanded)
1213             return;
1214         this._expanded = true;
1215
1216         if (!this._populated && this.onpopulate) {
1217             this.onpopulate(this);
1218             this._populated = true;
1219         }
1220         this.element.addStyleClass("expanded");
1221     },
1222
1223     collapse: function()
1224     {
1225         if (!this._expanded)
1226             return;
1227         this._expanded = false;
1228         this.element.removeStyleClass("expanded");
1229     }
1230 }
1231
1232 WebInspector.DOMPropertiesSection = function()
1233 {
1234     // FIXME: Perhaps this should be a real subclass someday
1235 }
1236
1237 WebInspector.DOMPropertiesSection.onpopulate = function(prototype)
1238 {
1239     return function(section) {
1240         var properties = Object.sortedProperties(prototype);
1241         for (var i = 0; i < properties.length; ++i) {
1242             var name = properties[i];
1243             if (!prototype.hasOwnProperty(name))
1244                 continue;
1245             if (name === "__treeElementIdentifier")
1246                 continue;
1247             var item = new WebInspector.DOMPropertyTreeElement(name, prototype);
1248             section.propertiesTreeOutline.appendChild(item);
1249         }
1250     }
1251 }
1252
1253 WebInspector.DOMNodeTreeElement = function(node)
1254 {
1255     var hasChildren = (Preferences.ignoreWhitespace ? (firstChildSkippingWhitespace.call(node) ? true : false) : node.hasChildNodes());
1256
1257     var titleInfo = nodeTitleInfo.call(node, hasChildren, WebInspector.linkifyURL);
1258     var title = titleInfo.title;
1259     hasChildren = titleInfo.hasChildren;
1260
1261     var item = new TreeElement(title, node, hasChildren);
1262     item.updateSelection = WebInspector.DOMNodeTreeElement.updateSelection;
1263     item.onpopulate = WebInspector.DOMNodeTreeElement.populate;
1264     item.onexpand = WebInspector.DOMNodeTreeElement.expanded;
1265     item.oncollapse = WebInspector.DOMNodeTreeElement.collapsed;
1266     item.onselect = WebInspector.DOMNodeTreeElement.selected;
1267     item.onreveal = WebInspector.DOMNodeTreeElement.revealed;
1268     item.ondblclick = WebInspector.DOMNodeTreeElement.doubleClicked;
1269     if (hasChildren) 
1270         item.whitespaceIgnored = Preferences.ignoreWhitespace;
1271     return item;
1272 }
1273
1274 WebInspector.DOMNodeTreeElement.updateSelection = function(element)
1275 {
1276     if (!element || !element._listItemNode)
1277         return;
1278
1279     if (!element.selectionElement) {
1280         element.selectionElement = document.createElement("div");
1281         element.selectionElement.className = "selection selected";
1282         element._listItemNode.insertBefore(element.selectionElement, element._listItemNode.firstChild);
1283     }
1284
1285     element.selectionElement.style.height = element._listItemNode.offsetHeight + "px";
1286 }
1287
1288 WebInspector.DOMNodeTreeElement.populate = function(element)
1289 {
1290     if (element.children.length || element.whitespaceIgnored !== Preferences.ignoreWhitespace)
1291         return;
1292
1293     element.removeChildren();
1294     element.whitespaceIgnored = Preferences.ignoreWhitespace;
1295
1296     var node = (Preferences.ignoreWhitespace ? firstChildSkippingWhitespace.call(element.representedObject) : element.representedObject.firstChild);
1297     while (node) {
1298         var item = new WebInspector.DOMNodeTreeElement(node);
1299         element.appendChild(item);
1300         node = Preferences.ignoreWhitespace ? nextSiblingSkippingWhitespace.call(node) : node.nextSibling;
1301     }
1302
1303     if (element.representedObject.nodeType == Node.ELEMENT_NODE) {
1304         var title = "<span class=\"webkit-html-tag close\">&lt;/" + element.representedObject.nodeName.toLowerCase().escapeHTML() + "&gt;</span>";
1305         var item = new TreeElement(title, element.representedObject, false);
1306         item.selectable = false;
1307         element.appendChild(item);
1308     }
1309 }
1310
1311 WebInspector.DOMNodeTreeElement.expanded = function(element)
1312 {
1313     element.treeOutline.panel.updateTreeSelection();
1314 }
1315
1316 WebInspector.DOMNodeTreeElement.collapsed = function(element)
1317 {
1318     element.treeOutline.panel.updateTreeSelection();
1319 }
1320
1321 WebInspector.DOMNodeTreeElement.revealed = function(element)
1322 {
1323     if (!element._listItemNode || !element.treeOutline || !element.treeOutline._childrenListNode)
1324         return;
1325     var parent = element.treeOutline.panel.views.dom.contentElement.treeContentElement;
1326     parent.scrollToElement(element._listItemNode);
1327 }
1328
1329 WebInspector.DOMNodeTreeElement.selected = function(element)
1330 {
1331     var panel = element.treeOutline.panel;
1332     panel.focusedDOMNode = element.representedObject;
1333
1334     // Call updateSelection twice to make sure the height is correct,
1335     // the first time might have a bad height because we are in a weird tree state
1336     element.updateSelection(element);
1337     setTimeout(function() { element.updateSelection(element) }, 0);
1338 }
1339
1340 WebInspector.DOMNodeTreeElement.doubleClicked = function(element)
1341 {
1342     var panel = element.treeOutline.panel;
1343     panel.rootDOMNode = element.representedObject.parentNode;
1344     panel.focusedDOMNode = element.representedObject;
1345 }
1346
1347 WebInspector.StylePropertyTreeElement = function(prop, computedStyle, usedProperties)
1348 {
1349     var overloadCount = 0;
1350     var priority;
1351     if (prop.subProperties && prop.subProperties.length > 1) {
1352         for (var i = 0; i < prop.subProperties.length; ++i) {
1353             var name = prop.subProperties[i];
1354             if (!priority)
1355                 priority = prop.style.getPropertyPriority(name);
1356             if (prop.unusedProperties[name])
1357                 overloadCount++;
1358         }
1359     }
1360
1361     if (!priority)
1362         priority = prop.style.getPropertyPriority(prop.name);
1363
1364     var overloaded = (prop.unusedProperties[prop.name] || overloadCount === prop.subProperties.length);
1365     var title = WebInspector.StylePropertyTreeElement.createTitle(prop.name, prop.style, overloaded, priority, computedStyle, usedProperties);
1366
1367     var item = new TreeElement(title, prop, (prop.subProperties && prop.subProperties.length > 1));
1368     item.computedStyle = computedStyle;
1369     item.onpopulate = WebInspector.StylePropertyTreeElement.populate;
1370     return item;
1371 }
1372
1373 WebInspector.StylePropertyTreeElement.createTitle = function(name, style, overloaded, priority, computed, usedProperties)
1374 {
1375     // "Nicknames" for some common values that are easier to read.
1376     var valueNickname = {
1377         "rgb(0, 0, 0)": "black",
1378         "rgb(255, 255, 255)": "white",
1379         "rgba(0, 0, 0, 0)": "transparent"
1380     };
1381
1382     var alwaysShowComputedProperties = { "display": true, "height": true, "width": true };
1383
1384     var value = style.getPropertyValue(name);
1385
1386     var textValue = value;
1387     if (value) {
1388         var urls = value.match(/url\([^)]+\)/);
1389         if (urls) {
1390             for (var i = 0; i < urls.length; ++i) {
1391                 var url = urls[i].substring(4, urls[i].length - 1);
1392                 textValue = textValue.replace(urls[i], "url(" + WebInspector.linkifyURL(url) + ")");
1393             }
1394         } else {
1395             if (value in valueNickname)
1396                 textValue = valueNickname[value];
1397             textValue = textValue.escapeHTML();
1398         }
1399     }
1400
1401     var classes = [];
1402     if (!computed && (style.isPropertyImplicit(name) || value == "initial"))
1403         classes.push("implicit");
1404     if (computed && !usedProperties[name] && !alwaysShowComputedProperties[name])
1405         classes.push("inherited");
1406     if (overloaded)
1407         classes.push("overloaded");
1408
1409     var title = "";
1410     if (classes.length)
1411         title += "<span class=\"" + classes.join(" ") + "\">";
1412
1413     title += "<span class=\"name\">" + name.escapeHTML() + "</span>: ";
1414     title += "<span class=\"value\">" + textValue;
1415     if (priority && priority.length)
1416         title += " !" + priority;
1417     title += "</span>;";
1418
1419     if (value) {
1420         // FIXME: this dosen't catch keyword based colors like black and white
1421         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);
1422         if (colors) {
1423             var colorsLength = colors.length;
1424             for (var i = 0; i < colorsLength; ++i)
1425                 title += "<span class=\"swatch\" style=\"background-color: " + colors[i] + "\"></span>";
1426         }
1427     }
1428
1429     if (classes.length)
1430         title += "</span>";
1431
1432     return title;
1433 }
1434
1435 WebInspector.StylePropertyTreeElement.populate = function(element)
1436 {
1437     if (element.children.length)
1438         return;
1439
1440     var prop = element.representedObject;
1441     if (prop.subProperties && prop.subProperties.length > 1) {
1442         for (var i = 0; i < prop.subProperties.length; ++i) {
1443             var name = prop.subProperties[i];
1444             var overloaded = (prop.unusedProperties[prop.name] || prop.unusedProperties[name]);
1445             var priority = prop.style.getPropertyPriority(name);
1446             var title = WebInspector.StylePropertyTreeElement.createTitle(name, prop.style, overloaded, priority, element.computedStyle);
1447             var subitem = new TreeElement(title, {}, false);
1448             element.appendChild(subitem);
1449         }
1450     }
1451 }
1452
1453 WebInspector.RequestResponseTreeElement = function(name, object)
1454 {
1455     var title = "<span class=\"name\">" + name.escapeHTML() + "</span>";
1456
1457     var hasChildren = object instanceof Object;
1458     if (!hasChildren) {
1459         var objectAsString = object.toString();
1460         title += ": <span class=\"value\">" + objectAsString.escapeHTML() + "</span>";
1461     }
1462
1463     var item = new TreeElement(title, object, hasChildren);
1464     item.onpopulate = WebInspector.RequestResponseTreeElement.onpopulate;
1465     return item;
1466 }
1467
1468 WebInspector.RequestResponseTreeElement.onpopulate = function(element)
1469 {
1470     if (element.children.length)
1471         return;
1472
1473     for (var field in element.representedObject) {
1474         var item = new WebInspector.RequestResponseTreeElement(field, element.representedObject[field]);
1475         element.appendChild(item);
1476     }
1477 }
1478
1479 WebInspector.DOMPropertyTreeElement = function(name, object)
1480 {
1481     var title = "<span class=\"name\">" + name.escapeHTML() + "</span>: ";
1482     title += "<span class=\"value\">" + Object.describe(object[name], true).escapeHTML() + "</span>";
1483
1484     var hasChildren = false;
1485     var type = typeof object[name];
1486     if (object[name] && (type === "object" || type === "function")) {
1487         for (value in object[name]) {
1488             hasChildren = true;
1489             break;
1490         }
1491     }
1492
1493     var representedObj = { object: object, name: name };
1494     var item = new TreeElement(title, representedObj, hasChildren);
1495     item.onpopulate = WebInspector.DOMPropertyTreeElement.populate;
1496     return item;
1497 }
1498
1499 WebInspector.DOMPropertyTreeElement.populate = function(element)
1500 {
1501     if (element.children.length)
1502         return;
1503
1504     var parent = element.representedObject.object[element.representedObject.name];
1505     var properties = Object.sortedProperties(parent);
1506     for (var i = 0; i < properties.length; ++i) {
1507         if (properties[i] === "__treeElementIdentifier")
1508             continue;
1509         var item = new WebInspector.DOMPropertyTreeElement(properties[i], parent);
1510         element.appendChild(item);
1511     }
1512 }