b626a951f239aea15b32c5d0c7bd7596377cc19f
[WebKit-https.git] / WebCore / page / inspector / DocumentPanel.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.DocumentPanel = function(resource, views)
30 {
31     var allViews = [{ title: "DOM" }];
32     if (views)
33         allViews = allViews.concat(views);
34
35     WebInspector.SourcePanel.call(this, resource, allViews);
36
37     var domView = this.views.dom;
38     domView.show = function() { InspectorController.highlightDOMNode(panel.focusedDOMNode) };
39     domView.hide = function() { InspectorController.hideDOMNodeHighlight() };
40
41     domView.sideContentElement = document.createElement("div");
42     domView.sideContentElement.className = "content side";
43
44     domView.treeContentElement = document.createElement("div");
45     domView.treeContentElement.className = "content tree outline-disclosure";
46
47     domView.treeListElement = document.createElement("ol");
48     domView.treeOutline = new TreeOutline(domView.treeListElement);
49     domView.treeOutline.panel = this;
50
51     var panel = this;
52     window.addEventListener("resize", function() { panel.updateTreeSelection() }, false);
53
54     domView.crumbsElement = document.createElement("div");
55     domView.crumbsElement.className = "crumbs";
56
57     domView.innerCrumbsElement = document.createElement("div");
58     domView.crumbsElement.appendChild(domView.innerCrumbsElement);
59
60     domView.sidebarPanes = {};
61     domView.sidebarPanes.styles = new WebInspector.SidebarPane("Styles");
62     domView.sidebarPanes.metrics = new WebInspector.SidebarPane("Metrics");
63     domView.sidebarPanes.properties = new WebInspector.SidebarPane("Properties");
64
65     var panel = this;
66     domView.sidebarPanes.styles.onexpand = function() { panel.updateStyles() };
67     domView.sidebarPanes.metrics.onexpand = function() { panel.updateMetrics() };
68     domView.sidebarPanes.properties.onexpand = function() { panel.updateProperties() };
69
70     domView.sidebarPanes.styles.expanded = true;
71
72     domView.sidebarElement = document.createElement("div");
73     domView.sidebarElement.className = "sidebar";
74
75     domView.sidebarElement.appendChild(domView.sidebarPanes.styles.element);
76     domView.sidebarElement.appendChild(domView.sidebarPanes.metrics.element);
77     domView.sidebarElement.appendChild(domView.sidebarPanes.properties.element);
78
79     domView.sideContentElement.appendChild(domView.treeContentElement);
80     domView.sideContentElement.appendChild(domView.crumbsElement);
81     domView.treeContentElement.appendChild(domView.treeListElement);
82
83     domView.sidebarResizeElement = document.createElement("div");
84     domView.sidebarResizeElement.className = "sidebar-resizer";
85     domView.sidebarResizeElement.addEventListener("mousedown", function(event) { panel.rightSidebarResizerDragStart(event) }, false);
86
87     domView.contentElement.appendChild(domView.sideContentElement);
88     domView.contentElement.appendChild(domView.sidebarElement);
89     domView.contentElement.appendChild(domView.sidebarResizeElement);
90
91     this.rootDOMNode = this.resource.documentNode;
92 }
93
94 WebInspector.DocumentPanel.prototype = {
95     updateTreeSelection: function()
96     {
97         if (!this.views.dom.treeOutline || !this.views.dom.treeOutline.selectedTreeElement)
98             return;
99         var element = this.views.dom.treeOutline.selectedTreeElement;
100         element.updateSelection(element);
101     },
102
103     get rootDOMNode()
104     {
105         return this._rootDOMNode;
106     },
107
108     set rootDOMNode(x)
109     {
110         if (this._rootDOMNode === x)
111             return;
112
113         this._rootDOMNode = x;
114
115         this.updateBreadcrumb();
116         this.updateTreeOutline();
117     },
118
119     get focusedDOMNode()
120     {
121         return this._focusedDOMNode;
122     },
123
124     set focusedDOMNode(x)
125     {
126         if (this.resource.category !== WebInspector.resourceCategories.documents) {
127             InspectorController.log("Called set focusedDOMNode on a non-document resource " + this.resource.displayName + " which is not a document");
128             return;
129         }
130
131         if (this._focusedDOMNode === x) {
132             var nodeItem = this.revealNode(x);
133             if (nodeItem)
134                 nodeItem.select();
135             return;
136         }
137
138         this._focusedDOMNode = x;
139
140         this.updateBreadcrumb();
141
142         for (var pane in this.views.dom.sidebarPanes)
143             this.views.dom.sidebarPanes[pane].needsUpdate = true;
144
145         this.updateStyles();
146         this.updateMetrics();
147         this.updateProperties();
148
149         InspectorController.highlightDOMNode(x);
150
151         var nodeItem = this.revealNode(x);
152         if (nodeItem)
153             nodeItem.select();
154     },
155
156     revealNode: function(node)
157     {
158         var nodeItem = this.views.dom.treeOutline.findTreeElement(node, function(a, b) { return isAncestorNode.call(a, b); }, function(a) { return a.parentNode; });
159         if (!nodeItem)
160             return;
161
162         nodeItem.reveal();
163         return nodeItem;
164     },
165
166     updateTreeOutline: function()
167     {
168         this.views.dom.treeOutline.removeChildrenRecursive();
169
170         if (!this.rootDOMNode)
171             return;
172
173         // FIXME: this could use findTreeElement to reuse a tree element if it already exists
174         var node = (Preferences.ignoreWhitespace ? firstChildSkippingWhitespace.call(this.rootDOMNode) : this.rootDOMNode.firstChild);
175         while (node) {
176             var item = new WebInspector.DOMNodeTreeElement(node);
177             this.views.dom.treeOutline.appendChild(item);
178             node = Preferences.ignoreWhitespace ? nextSiblingSkippingWhitespace.call(node) : node.nextSibling;
179         }
180
181         this.updateTreeSelection();
182     },
183
184     updateBreadcrumb: function()
185     {
186         if (!this.views || !this.views.dom.contentElement)
187             return;
188         var crumbs = this.views.dom.innerCrumbsElement;
189         if (!crumbs)
190             return;
191
192         var handled = false;
193         var foundRoot = false;
194         var crumb = crumbs.firstChild;
195         while (crumb) {
196             if (crumb.representedObject === this.rootDOMNode)
197                 foundRoot = true;
198
199             if (foundRoot)
200                 crumb.addStyleClass("hidden");
201             else
202                 crumb.removeStyleClass("hidden");
203
204             if (crumb.representedObject === this.focusedDOMNode) {
205                 crumb.addStyleClass("selected");
206                 handled = true;
207             } else {
208                 crumb.removeStyleClass("selected");
209             }
210
211             crumb = crumb.nextSibling;
212         }
213
214         if (handled)
215             return;
216
217         crumbs.removeChildren();
218
219         var panel = this;
220         var selectCrumbFunction = function(event)
221         {
222             if (event.currentTarget.hasStyleClass("hidden"))
223                 panel.rootDOMNode = event.currentTarget.representedObject.parentNode;
224             panel.focusedDOMNode = event.currentTarget.representedObject;
225             event.preventDefault();
226             event.stopPropagation();
227         }
228
229         var selectCrumbRootFunction = function(event)
230         {
231             panel.rootDOMNode = event.currentTarget.representedObject.parentNode;
232             panel.focusedDOMNode = event.currentTarget.representedObject;
233             event.preventDefault();
234             event.stopPropagation();
235         }
236
237         foundRoot = false;
238         var current = this.focusedDOMNode;
239         while (current) {
240             if (current.nodeType === Node.DOCUMENT_NODE)
241                 break;
242
243             if (current === this.rootDOMNode)
244                 foundRoot = true;
245
246             var crumb = document.createElement("span");
247             crumb.className = "crumb";
248             crumb.representedObject = current;
249             crumb.addEventListener("mousedown", selectCrumbFunction, false);
250             crumb.addEventListener("dblclick", selectCrumbRootFunction, false);
251
252             var crumbTitle;
253             switch (current.nodeType) {
254                 case Node.ELEMENT_NODE:
255                     crumbTitle = current.nodeName.toLowerCase();
256     
257                     var value = current.getAttribute("id");
258                     if (value && value.length)
259                         crumbTitle += "#" + value;
260
261                     value = current.getAttribute("class");
262                     if (value && value.length) {
263                         var classes = value.split(/\s+/);
264                         var classesLength = classes.length;
265                         for (var i = 0; i < classesLength; ++i) {
266                             value = classes[i];
267                             if (value && value.length)
268                                 crumbTitle += "." + value;
269                         }
270                     }
271
272                     break;
273
274                 case Node.TEXT_NODE:
275                     if (isNodeWhitespace.call(current))
276                         crumbTitle = "(whitespace)";
277                     else
278                         crumbTitle = "(text)";
279                     break
280
281                 case Node.COMMENT_NODE:
282                     crumbTitle = "<!-->";
283                     break;
284
285                 default:
286                     crumbTitle = current.nodeName.toLowerCase();
287             }
288
289             crumb.textContent = crumbTitle;
290
291             if (foundRoot)
292                 crumb.addStyleClass("hidden");
293             if (current === this.focusedDOMNode)
294                 crumb.addStyleClass("selected");
295             if (!crumbs.childNodes.length)
296                 crumb.addStyleClass("end");
297             if (current.parentNode.nodeType === Node.DOCUMENT_NODE)
298                 crumb.addStyleClass("start");
299
300             crumbs.appendChild(crumb);
301             current = current.parentNode;
302         }
303     },
304
305     updateStyles: function()
306     {
307         if (!this.views.dom.sidebarPanes.styles.expanded)
308             return;
309         if (!this.views.dom.sidebarPanes.styles.needsUpdate)
310             return;
311
312         this.views.dom.sidebarPanes.styles.needsUpdate = false;
313
314         var stylesBody = this.views.dom.sidebarPanes.styles.bodyElement;
315
316         stylesBody.removeChildren();
317         this.views.dom.sidebarPanes.styles.sections = [];
318
319         if (!this.focusedDOMNode)
320             return;
321
322         var styleNode = this.focusedDOMNode;
323         if (styleNode.nodeType === Node.TEXT_NODE && styleNode.parentNode)
324             styleNode = styleNode.parentNode;
325
326         var styleRules = [];
327         var styleProperties = [];
328
329         if (styleNode.nodeType === Node.ELEMENT_NODE) {
330             var propertyCount = [];
331
332             var computedStyle = styleNode.ownerDocument.defaultView.getComputedStyle(styleNode);
333             if (computedStyle && computedStyle.length)
334                 styleRules.push({ isComputedStyle: true, selectorText: "Computed Style", style: computedStyle });
335
336             var styleNodeName = styleNode.nodeName.toLowerCase();
337             for (var i = 0; i < styleNode.attributes.length; ++i) {
338                 var attr = styleNode.attributes[i];
339                 if (attr.style) {
340                     var attrStyle = { attrName: attr.name, style: attr.style };
341                     attrStyle.subtitle = "element\u2019s \u201C" + attr.name + "\u201D attribute";
342                     attrStyle.selectorText = styleNodeName + "[" + attr.name;
343                     if (attr.value.length)
344                         attrStyle.selectorText += "=" + attr.value;
345                     attrStyle.selectorText += "]";
346                     styleRules.push(attrStyle);
347                 }
348             }
349
350             if (styleNode.style && styleNode.style.length) {
351                 var inlineStyle = { selectorText: "Inline Style Attribute", style: styleNode.style };
352                 inlineStyle.subtitle = "element\u2019s \u201Cstyle\u201D attribute";
353                 styleRules.push(inlineStyle);
354             }
355
356             var matchedStyleRules = styleNode.ownerDocument.defaultView.getMatchedCSSRules(styleNode, "", !Preferences.showUserAgentStyles);
357             if (matchedStyleRules) {
358                 for (var i = matchedStyleRules.length - 1; i >= 0; --i)
359                     styleRules.push(matchedStyleRules[i]);
360             }
361
362             var priorityUsed = false;
363             var usedProperties = {};
364             var shorthandProperties = {};
365             for (var i = 0; i < styleRules.length; ++i) {
366                 styleProperties[i] = [];
367
368                 var style = styleRules[i].style;
369                 var styleShorthandLookup = [];
370                 for (var j = 0; j < style.length; ++j) {
371                     var prop = null;
372                     var name = style[j];
373                     var shorthand = style.getPropertyShorthand(name);
374                     if (!shorthand && styleRules[i].isComputedStyle)
375                         shorthand = shorthandProperties[name];
376
377                     if (shorthand) {
378                         prop = styleShorthandLookup[shorthand];
379                         shorthandProperties[name] = shorthand;
380                     }
381
382                     if (!priorityUsed && style.getPropertyPriority(name).length)
383                         priorityUsed = true;
384
385                     if (prop) {
386                         prop.subProperties.push(name);
387                     } else {
388                         prop = { style: style, subProperties: [name], unusedProperties: [], name: (shorthand ? shorthand : name) };
389                         styleProperties[i].push(prop);
390
391                         if (shorthand) {
392                             styleShorthandLookup[shorthand] = prop;
393                             if (!styleRules[i].isComputedStyle) {
394                                 if (!propertyCount[shorthand]) {
395                                     propertyCount[shorthand] = 1;
396                                 } else {
397                                     prop.unusedProperties[shorthand] = true;
398                                     propertyCount[shorthand]++;
399                                 }
400                             }
401                         }
402                     }
403
404                     if (styleRules[i].isComputedStyle)
405                         continue;
406
407                     usedProperties[name] = true;
408                     if (shorthand)
409                         usedProperties[shorthand] = true;
410
411                     if (!propertyCount[name]) {
412                         propertyCount[name] = 1;
413                     } else {
414                         prop.unusedProperties[name] = true;
415                         propertyCount[name]++;
416                     }
417                 }
418             }
419
420             if (priorityUsed) {
421                 // walk the properties again and account for !important
422                 var priorityCount = [];
423                 for (var i = 0; i < styleRules.length; ++i) {
424                     if (styleRules[i].isComputedStyle)
425                         continue;
426                     var style = styleRules[i].style;
427                     for (var j = 0; j < styleProperties[i].length; ++j) {
428                         var prop = styleProperties[i][j];
429                         for (var k = 0; k < prop.subProperties.length; ++k) {
430                             var name = prop.subProperties[k];
431                             if (style.getPropertyPriority(name).length) {
432                                 if (!priorityCount[name]) {
433                                     if (prop.unusedProperties[name])
434                                         prop.unusedProperties[name] = false;
435                                     priorityCount[name] = 1;
436                                 } else {
437                                     priorityCount[name]++;
438                                 }
439                             } else if (priorityCount[name]) {
440                                 prop.unusedProperties[name] = true;
441                             }
442                         }
443                     }
444                 }
445             }
446
447             var styleRulesLength = styleRules.length;
448             for (var i = 0; i < styleRulesLength; ++i) {
449                 var selectorText = styleRules[i].selectorText;
450
451                 var section = new WebInspector.PropertiesSection(selectorText);
452                 section.expanded = true;
453
454                 if (styleRules[i].isComputedStyle) {
455                     if (Preferences.showInheritedComputedStyleProperties)
456                         section.element.addStyleClass("show-inherited");
457
458                     var showInheritedLabel = document.createElement("label");
459                     var showInheritedInput = document.createElement("input");
460                     showInheritedInput.type = "checkbox";
461                     showInheritedInput.checked = Preferences.showInheritedComputedStyleProperties;
462
463                     var computedStyleSection = section;
464                     var showInheritedToggleFunction = function(event) {
465                         Preferences.showInheritedComputedStyleProperties = showInheritedInput.checked;
466                         if (Preferences.showInheritedComputedStyleProperties)
467                             computedStyleSection.element.addStyleClass("show-inherited");
468                         else
469                             computedStyleSection.element.removeStyleClass("show-inherited");
470                         event.stopPropagation();
471                     };
472
473                     showInheritedLabel.addEventListener("click", showInheritedToggleFunction, false);
474
475                     showInheritedLabel.appendChild(showInheritedInput);
476                     showInheritedLabel.appendChild(document.createTextNode("Show implicit properties"));
477                     section.subtitleElement.appendChild(showInheritedLabel);
478                 } else {
479                     var sheet;
480                     var file = false;
481                     if (styleRules[i].subtitle)
482                         sheet = styleRules[i].subtitle;
483                     else if (styleRules[i].parentStyleSheet && styleRules[i].parentStyleSheet.href) {
484                         var url = styleRules[i].parentStyleSheet.href;
485                         sheet = WebInspector.linkifyURL(url, url.trimURL(WebInspector.mainResource.domain).escapeHTML());
486                         file = true;
487                     } else if (styleRules[i].parentStyleSheet && !styleRules[i].parentStyleSheet.ownerNode)
488                         sheet = "user agent stylesheet";
489                     else
490                         sheet = "inline stylesheet";
491
492                     if (file)
493                         section.subtitleElement.addStyleClass("file");
494
495                     section.subtitle = sheet;
496                 }
497
498                 stylesBody.appendChild(section.element);
499                 this.views.dom.sidebarPanes.styles.sections.push(section);
500
501                 var properties = styleProperties[i];
502                 var isComputedStyle = styleRules[i].isComputedStyle;
503
504                 for (var j = 0; j < properties.length; ++j) {
505                     var prop = properties[j];
506
507                     var propTreeElement = new WebInspector.StylePropertyTreeElement(prop, isComputedStyle, usedProperties);
508                     section.propertiesTreeOutline.appendChild(propTreeElement);
509                 }
510             }
511         } else {
512             // can't style this node
513         }
514     },
515
516     updateMetrics: function()
517     {
518         if (!this.views.dom.sidebarPanes.metrics.expanded)
519             return;
520         if (!this.views.dom.sidebarPanes.metrics.needsUpdate)
521             return;
522
523         this.views.dom.sidebarPanes.metrics.needsUpdate = false;
524
525         var metricsBody = this.views.dom.sidebarPanes.metrics.bodyElement;
526
527         metricsBody.removeChildren();
528
529         if (!this.focusedDOMNode)
530             return;
531
532         var style;
533         if (this.focusedDOMNode.nodeType === Node.ELEMENT_NODE)
534             style = this.focusedDOMNode.ownerDocument.defaultView.getComputedStyle(this.focusedDOMNode);
535         if (!style)
536             return;
537
538         var metricsElement = document.createElement("div");
539         metricsElement.className = "metrics";
540
541         function boxPartValue(style, name, suffix)
542         {
543             var value = style.getPropertyValue(name + suffix);
544             if (value === "" || value === "0px")
545                 value = "\u2012";
546             return value.replace(/px$/, "");
547         }
548
549         // Display types for which margin is ignored.
550         var noMarginDisplayType = {
551             "table-cell": true,
552             "table-column": true,
553             "table-column-group": true,
554             "table-footer-group": true,
555             "table-header-group": true,
556             "table-row": true,
557             "table-row-group": true
558         };
559
560         // Display types for which padding is ignored.
561         var noPaddingDisplayType = {
562             "table-column": true,
563             "table-column-group": true,
564             "table-footer-group": true,
565             "table-header-group": true,
566             "table-row": true,
567             "table-row-group": true
568         };
569
570         var boxes = ["content", "padding", "border", "margin"];
571         var previousBox;
572         for (var i = 0; i < boxes.length; ++i) {
573             var name = boxes[i];
574
575             if (name === "margin" && noMarginDisplayType[style.display])
576                 continue;
577             if (name === "padding" && noPaddingDisplayType[style.display])
578                 continue;
579
580             var boxElement = document.createElement("div");
581             boxElement.className = name;
582
583             if (boxes[i] === "content") {
584                 var width = style.width.replace(/px$/, "");
585                 var height = style.height.replace(/px$/, "");
586                 boxElement.textContent = width + " \u00D7 " + height;
587             } else {
588                 var suffix = boxes[i] === "border" ? "-width" : "";
589
590                 var labelElement = document.createElement("div");
591                 labelElement.className = "label";
592                 labelElement.textContent = name;
593                 boxElement.appendChild(labelElement);
594
595                 var topElement = document.createElement("div");
596                 topElement.className = "top";
597                 topElement.textContent = boxPartValue(style, name + "-top", suffix);
598                 boxElement.appendChild(topElement);
599
600                 var leftElement = document.createElement("div");
601                 leftElement.className = "left";
602                 leftElement.textContent = boxPartValue(style, name + "-left", suffix);
603                 boxElement.appendChild(leftElement);
604
605                 if (previousBox)
606                     boxElement.appendChild(previousBox);
607
608                 var rightElement = document.createElement("div");
609                 rightElement.className = "right";
610                 rightElement.textContent = boxPartValue(style, name + "-right", suffix);
611                 boxElement.appendChild(rightElement);
612
613                 var bottomElement = document.createElement("div");
614                 bottomElement.className = "bottom";
615                 bottomElement.textContent = boxPartValue(style, name + "-bottom", suffix);
616                 boxElement.appendChild(bottomElement);
617             }
618
619             previousBox = boxElement;
620         }
621
622         metricsElement.appendChild(previousBox);
623         metricsBody.appendChild(metricsElement);
624     },
625
626     updateProperties: function()
627     {
628         if (!this.views.dom.sidebarPanes.properties.expanded)
629             return;
630         if (!this.views.dom.sidebarPanes.properties.needsUpdate)
631             return;
632
633         this.views.dom.sidebarPanes.properties.needsUpdate = false;
634
635         var propertiesBody = this.views.dom.sidebarPanes.properties.bodyElement;
636
637         propertiesBody.removeChildren();
638         this.views.dom.sidebarPanes.properties.sections = [];
639
640         if (!this.focusedDOMNode)
641             return;
642
643         for (var prototype = this.focusedDOMNode; prototype; prototype = prototype.__proto__) {
644             var hasChildren = false;
645             for (var prop in prototype) {
646                 if (prop === "__treeElementIdentifier")
647                     continue;
648                 if (!prototype.hasOwnProperty(prop))
649                     continue;
650
651                 hasChildren = true;
652                 break;
653             }
654
655             if (!hasChildren)
656                 continue;
657
658             var title = Object.describe(prototype);
659             var subtitle;
660             if (title.match(/Prototype$/)) {
661                 title = title.replace(/Prototype$/, "");
662                 subtitle = "Prototype";
663             }
664
665             var section = new WebInspector.PropertiesSection(title, subtitle);
666             section.onpopulate = WebInspector.DOMPropertiesSection.onpopulate(prototype);
667
668             propertiesBody.appendChild(section.element);
669             this.views.dom.sidebarPanes.properties.sections.push(section);
670         }
671     },
672
673     handleKeyEvent: function(event)
674     {
675         if (this.views.dom.treeOutline && this.currentView && this.currentView === this.views.dom)
676             this.views.dom.treeOutline.handleKeyEvent(event);
677     },
678
679     rightSidebarResizerDragStart: function(event)
680     {
681         var panel = this; 
682         WebInspector.dividerDragStart(this.views.dom.sidebarElement, function(event) { panel.rightSidebarResizerDrag(event) }, function(event) { panel.rightSidebarResizerDragEnd(event) }, event, "col-resize");
683     },
684
685     rightSidebarResizerDragEnd: function(event)
686     {
687         var panel = this;
688         WebInspector.dividerDragEnd(this.views.dom.sidebarElement, function(event) { panel.rightSidebarResizerDrag(event) }, function(event) { panel.rightSidebarResizerDragEnd(event) }, event);
689     },
690
691     rightSidebarResizerDrag: function(event)
692     {
693         var rightSidebar = this.views.dom.sidebarElement;
694         if (rightSidebar.dragging == true) {
695             var x = event.clientX + window.scrollX;
696
697             var leftSidebarWidth = window.getComputedStyle(document.getElementById("sidebar")).getPropertyCSSValue("width").getFloatValue(CSSPrimitiveValue.CSS_PX);
698             var newWidth = Number.constrain(window.innerWidth - x, 100, window.innerWidth - leftSidebarWidth - 100);
699
700             if (x == newWidth)
701                 rightSidebar.dragLastX = x;
702
703             rightSidebar.style.width = newWidth + "px";
704             this.views.dom.sideContentElement.style.right = newWidth + "px";
705             this.views.dom.sidebarResizeElement.style.right = (newWidth - 3) + "px";
706             event.preventDefault();
707         }
708     }
709 }
710
711 WebInspector.DocumentPanel.prototype.__proto__ = WebInspector.SourcePanel.prototype;
712
713 WebInspector.DOMPropertiesSection = function()
714 {
715     // FIXME: Perhaps this should be a real subclass someday
716 }
717
718 WebInspector.DOMPropertiesSection.onpopulate = function(prototype)
719 {
720     return function(section) {
721         var properties = Object.sortedProperties(prototype);
722         for (var i = 0; i < properties.length; ++i) {
723             var name = properties[i];
724             if (!prototype.hasOwnProperty(name))
725                 continue;
726             if (name === "__treeElementIdentifier")
727                 continue;
728             var item = new WebInspector.DOMPropertyTreeElement(name, prototype);
729             section.propertiesTreeOutline.appendChild(item);
730         }
731     }
732 }
733
734 WebInspector.DOMNodeTreeElement = function(node)
735 {
736     var hasChildren = (Preferences.ignoreWhitespace ? (firstChildSkippingWhitespace.call(node) ? true : false) : node.hasChildNodes());
737
738     var titleInfo = nodeTitleInfo.call(node, hasChildren, WebInspector.linkifyURL);
739     var title = titleInfo.title;
740     hasChildren = titleInfo.hasChildren;
741
742     var item = new TreeElement(title, node, hasChildren);
743     item.updateSelection = WebInspector.DOMNodeTreeElement.updateSelection;
744     item.onpopulate = WebInspector.DOMNodeTreeElement.populate;
745     item.onexpand = WebInspector.DOMNodeTreeElement.expanded;
746     item.oncollapse = WebInspector.DOMNodeTreeElement.collapsed;
747     item.onselect = WebInspector.DOMNodeTreeElement.selected;
748     item.onreveal = WebInspector.DOMNodeTreeElement.revealed;
749     item.ondblclick = WebInspector.DOMNodeTreeElement.doubleClicked;
750     if (hasChildren) 
751         item.whitespaceIgnored = Preferences.ignoreWhitespace;
752     return item;
753 }
754
755 WebInspector.DOMNodeTreeElement.updateSelection = function(element)
756 {
757     if (!element || !element._listItemNode)
758         return;
759
760     if (!element.selectionElement) {
761         element.selectionElement = document.createElement("div");
762         element.selectionElement.className = "selection selected";
763         element._listItemNode.insertBefore(element.selectionElement, element._listItemNode.firstChild);
764     }
765
766     element.selectionElement.style.height = element._listItemNode.offsetHeight + "px";
767 }
768
769 WebInspector.DOMNodeTreeElement.populate = function(element)
770 {
771     if (element.children.length || element.whitespaceIgnored !== Preferences.ignoreWhitespace)
772         return;
773
774     element.removeChildren();
775     element.whitespaceIgnored = Preferences.ignoreWhitespace;
776
777     var node = (Preferences.ignoreWhitespace ? firstChildSkippingWhitespace.call(element.representedObject) : element.representedObject.firstChild);
778     while (node) {
779         var item = new WebInspector.DOMNodeTreeElement(node);
780         element.appendChild(item);
781         node = Preferences.ignoreWhitespace ? nextSiblingSkippingWhitespace.call(node) : node.nextSibling;
782     }
783
784     if (element.representedObject.nodeType == Node.ELEMENT_NODE) {
785         var title = "<span class=\"webkit-html-tag close\">&lt;/" + element.representedObject.nodeName.toLowerCase().escapeHTML() + "&gt;</span>";
786         var item = new TreeElement(title, element.representedObject, false);
787         item.selectable = false;
788         element.appendChild(item);
789     }
790 }
791
792 WebInspector.DOMNodeTreeElement.expanded = function(element)
793 {
794     element.treeOutline.panel.updateTreeSelection();
795 }
796
797 WebInspector.DOMNodeTreeElement.collapsed = function(element)
798 {
799     element.treeOutline.panel.updateTreeSelection();
800 }
801
802 WebInspector.DOMNodeTreeElement.revealed = function(element)
803 {
804     if (!element._listItemNode || !element.treeOutline || !element.treeOutline._childrenListNode)
805         return;
806     var parent = element.treeOutline.panel.views.dom.treeContentElement;
807     parent.scrollToElement(element._listItemNode);
808 }
809
810 WebInspector.DOMNodeTreeElement.selected = function(element)
811 {
812     var panel = element.treeOutline.panel;
813     panel.focusedDOMNode = element.representedObject;
814
815     // Call updateSelection twice to make sure the height is correct,
816     // the first time might have a bad height because we are in a weird tree state
817     element.updateSelection(element);
818     setTimeout(function() { element.updateSelection(element) }, 0);
819 }
820
821 WebInspector.DOMNodeTreeElement.doubleClicked = function(element)
822 {
823     var panel = element.treeOutline.panel;
824     panel.rootDOMNode = element.representedObject.parentNode;
825     panel.focusedDOMNode = element.representedObject;
826 }
827
828 WebInspector.StylePropertyTreeElement = function(prop, computedStyle, usedProperties)
829 {
830     var overloadCount = 0;
831     var priority;
832     if (prop.subProperties && prop.subProperties.length > 1) {
833         for (var i = 0; i < prop.subProperties.length; ++i) {
834             var name = prop.subProperties[i];
835             if (!priority)
836                 priority = prop.style.getPropertyPriority(name);
837             if (prop.unusedProperties[name])
838                 overloadCount++;
839         }
840     }
841
842     if (!priority)
843         priority = prop.style.getPropertyPriority(prop.name);
844
845     var overloaded = (prop.unusedProperties[prop.name] || overloadCount === prop.subProperties.length);
846     var title = WebInspector.StylePropertyTreeElement.createTitle(prop.name, prop.style, overloaded, priority, computedStyle, usedProperties);
847
848     var item = new TreeElement(title, prop, (prop.subProperties && prop.subProperties.length > 1));
849     item.computedStyle = computedStyle;
850     item.onpopulate = WebInspector.StylePropertyTreeElement.populate;
851     return item;
852 }
853
854 WebInspector.StylePropertyTreeElement.createTitle = function(name, style, overloaded, priority, computed, usedProperties)
855 {
856     // "Nicknames" for some common values that are easier to read.
857     var valueNickname = {
858         "rgb(0, 0, 0)": "black",
859         "rgb(255, 255, 255)": "white",
860         "rgba(0, 0, 0, 0)": "transparent"
861     };
862
863     var alwaysShowComputedProperties = { "display": true, "height": true, "width": true };
864
865     var value = style.getPropertyValue(name);
866
867     var textValue = value;
868     if (value) {
869         var urls = value.match(/url\([^)]+\)/);
870         if (urls) {
871             for (var i = 0; i < urls.length; ++i) {
872                 var url = urls[i].substring(4, urls[i].length - 1);
873                 textValue = textValue.replace(urls[i], "url(" + WebInspector.linkifyURL(url) + ")");
874             }
875         } else {
876             if (value in valueNickname)
877                 textValue = valueNickname[value];
878             textValue = textValue.escapeHTML();
879         }
880     }
881
882     var classes = [];
883     if (!computed && (style.isPropertyImplicit(name) || value == "initial"))
884         classes.push("implicit");
885     if (computed && !usedProperties[name] && !alwaysShowComputedProperties[name])
886         classes.push("inherited");
887     if (overloaded)
888         classes.push("overloaded");
889
890     var title = "";
891     if (classes.length)
892         title += "<span class=\"" + classes.join(" ") + "\">";
893
894     title += "<span class=\"name\">" + name.escapeHTML() + "</span>: ";
895     title += "<span class=\"value\">" + textValue;
896     if (priority && priority.length)
897         title += " !" + priority;
898     title += "</span>;";
899
900     if (value) {
901         // FIXME: this dosen't catch keyword based colors like black and white
902         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);
903         if (colors) {
904             var colorsLength = colors.length;
905             for (var i = 0; i < colorsLength; ++i)
906                 title += "<span class=\"swatch\" style=\"background-color: " + colors[i] + "\"></span>";
907         }
908     }
909
910     if (classes.length)
911         title += "</span>";
912
913     return title;
914 }
915
916 WebInspector.StylePropertyTreeElement.populate = function(element)
917 {
918     if (element.children.length)
919         return;
920
921     var prop = element.representedObject;
922     if (prop.subProperties && prop.subProperties.length > 1) {
923         for (var i = 0; i < prop.subProperties.length; ++i) {
924             var name = prop.subProperties[i];
925             var overloaded = (prop.unusedProperties[prop.name] || prop.unusedProperties[name]);
926             var priority = prop.style.getPropertyPriority(name);
927             var title = WebInspector.StylePropertyTreeElement.createTitle(name, prop.style, overloaded, priority, element.computedStyle);
928             var subitem = new TreeElement(title, {}, false);
929             element.appendChild(subitem);
930         }
931     }
932 }
933
934 WebInspector.DOMPropertyTreeElement = function(name, object)
935 {
936     var title = "<span class=\"name\">" + name.escapeHTML() + "</span>: ";
937     title += "<span class=\"value\">" + Object.describe(object[name], true).escapeHTML() + "</span>";
938
939     var hasChildren = false;
940     var type = typeof object[name];
941     if (object[name] && (type === "object" || type === "function")) {
942         for (value in object[name]) {
943             hasChildren = true;
944             break;
945         }
946     }
947
948     var representedObj = { object: object, name: name };
949     var item = new TreeElement(title, representedObj, hasChildren);
950     item.onpopulate = WebInspector.DOMPropertyTreeElement.populate;
951     return item;
952 }
953
954 WebInspector.DOMPropertyTreeElement.populate = function(element)
955 {
956     if (element.children.length)
957         return;
958
959     var parent = element.representedObject.object[element.representedObject.name];
960     var properties = Object.sortedProperties(parent);
961     for (var i = 0; i < properties.length; ++i) {
962         if (properties[i] === "__treeElementIdentifier")
963             continue;
964         var item = new WebInspector.DOMPropertyTreeElement(properties[i], parent);
965         element.appendChild(item);
966     }
967 }