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