2007-02-06 Mark Rowe <mrowe@apple.com>
[WebKit-https.git] / WebKit / WebInspector / webInspector / inspector.js
1 /*
2  * Copyright (C) 2006 Apple Computer, 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 var Inspector = null;
30 var showUserAgentStyles = true;
31
32 // Property values to omit in the computed style list.
33 // If a property has this value, it will be omitted.
34 // Note we do not provide a value for "display", "height", or "width", for example,
35 // since we always want to display those.
36 var typicalStylePropertyValue = {
37     "-webkit-appearance": "none",
38     "-webkit-background-clip": "border",
39     "-webkit-background-composite": "source-over",
40     "-webkit-background-origin": "padding",
41     "-webkit-background-size": "auto auto",
42     "-webkit-border-horizontal-spacing": "0px",
43     "-webkit-border-vertical-spacing": "0px",
44     "-webkit-box-align": "stretch",
45     "-webkit-box-direction": "normal",
46     "-webkit-box-flex": "0",
47     "-webkit-box-flex-group": "1",
48     "-webkit-box-lines": "single",
49     "-webkit-box-ordinal-group": "1",
50     "-webkit-box-orient": "horizontal",
51     "-webkit-box-pack": "start",
52     "-webkit-box-shadow": "none",
53     "-webkit-column-break-after" : "auto",
54     "-webkit-column-break-before" : "auto",
55     "-webkit-column-break-inside" : "auto",
56     "-webkit-column-count" : "auto",
57     "-webkit-column-gap" : "normal",
58     "-webkit-column-rule-color" : "rgb(0, 0, 0)",
59     "-webkit-column-rule-style" : "none",
60     "-webkit-column-rule-width" : "0px",
61     "-webkit-column-width" : "auto",
62     "-webkit-highlight": "none",
63     "-webkit-line-break": "normal",
64     "-webkit-line-clamp": "none",
65     "-webkit-margin-bottom-collapse": "collapse",
66     "-webkit-margin-top-collapse": "collapse",
67     "-webkit-marquee-direction": "auto",
68     "-webkit-marquee-increment": "6px",
69     "-webkit-marquee-repetition": "infinite",
70     "-webkit-marquee-style": "scroll",
71     "-webkit-nbsp-mode": "normal",
72     "-webkit-rtl-ordering": "logical",
73     "-webkit-text-decorations-in-effect": "none",
74     "-webkit-text-fill-color": "rgb(0, 0, 0)",
75     "-webkit-text-security": "none",
76     "-webkit-text-stroke-color": "rgb(0, 0, 0)",
77     "-webkit-text-stroke-width": "0",
78     "-webkit-user-drag": "auto",
79     "-webkit-user-modify": "read-only",
80     "-webkit-user-select": "auto",
81     "background-attachment": "scroll",
82     "background-color": "rgba(0, 0, 0, 0)",
83     "background-image": "none",
84     "background-position-x": "auto",
85     "background-position-y": "auto",
86     "background-repeat": "repeat",
87     "border-bottom-color": "rgb(0, 0, 0)",
88     "border-bottom-style": "none",
89     "border-bottom-width": "0px",
90     "border-collapse": "separate",
91     "border-left-color": "rgb(0, 0, 0)",
92     "border-left-style": "none",
93     "border-left-width": "0px",
94     "border-right-color": "rgb(0, 0, 0)",
95     "border-right-style": "none",
96     "border-right-width": "0px",
97     "border-top-color": "rgb(0, 0, 0)",
98     "border-top-style": "none",
99     "border-top-width": "0px",
100     "bottom": "auto",
101     "box-sizing": "border-box",
102     "caption-side": "top",
103     "clear": "none",
104     "color": "rgb(0, 0, 0)",
105     "cursor": "auto",
106     "direction": "ltr",
107     "empty-cells": "show",
108     "float": "none",
109     "font-style": "normal",
110     "font-variant": "normal",
111     "font-weight": "normal",
112     "left": "auto",
113     "letter-spacing": "normal",
114     "line-height": "normal",
115     "list-style-image": "none",
116     "list-style-position": "outside",
117     "list-style-type": "disc",
118     "margin-bottom": "0px",
119     "margin-left": "0px",
120     "margin-right": "0px",
121     "margin-top": "0px",
122     "max-height": "none",
123     "max-width": "none",
124     "min-height": "0px",
125     "min-width": "0px",
126     "opacity": "1",
127     "orphans": "2",
128     "outline-color": "rgb(0, 0, 0)",
129     "outline-style": "none",
130     "outline-width": "0px",
131     "overflow": "visible",
132     "overflow-x": "visible",
133     "overflow-y": "visible",
134     "padding-bottom": "0px",
135     "padding-left": "0px",
136     "padding-right": "0px",
137     "padding-top": "0px",
138     "page-break-after": "auto",
139     "page-break-before": "auto",
140     "page-break-inside": "auto",
141     "position": "static",
142     "resize": "none",
143     "right": "auto",
144     "table-layout": "auto",
145     "text-align": "auto",
146     "text-decoration": "none",
147     "text-indent": "0px",
148     "text-shadow": "none",
149     "text-transform": "none",
150     "top": "auto",
151     "unicode-bidi": "normal",
152     "vertical-align": "baseline",
153     "visibility": "visible",
154     "white-space": "normal",
155     "widows": "2",
156     "word-spacing": "0px",
157     "word-wrap": "normal",
158     "z-index": "normal",
159 };
160
161 // "Nicknames" for some common values that are easier to read.
162 var valueNickname = {
163     "rgb(0, 0, 0)": "black",
164     "rgb(255, 255, 255)": "white",
165     "rgba(0, 0, 0, 0)": "transparent",
166 };
167
168 // Display types for which margin is ignored.
169 var noMarginDisplayType = {
170     "table-cell": "no",
171     "table-column": "no",
172     "table-column-group": "no",
173     "table-footer-group": "no",
174     "table-header-group": "no",
175     "table-row": "no",
176     "table-row-group": "no",
177 };
178
179 // Display types for which padding is ignored.
180 var noPaddingDisplayType = {
181     "table-column": "no",
182     "table-column-group": "no",
183     "table-footer-group": "no",
184     "table-header-group": "no",
185     "table-row": "no",
186     "table-row-group": "no",
187 };
188
189 function setUpScrollbar(id)
190 {
191     var bar = new AppleVerticalScrollbar(document.getElementById(id));
192
193     bar.setTrackStart("Images/scrollTrackTop.png", 18);
194     bar.setTrackMiddle("Images/scrollTrackMiddle.png");
195     bar.setTrackEnd("Images/scrollTrackBottom.png", 18);
196     bar.setThumbStart("Images/scrollThumbTop.png", 9);
197     bar.setThumbMiddle("Images/scrollThumbMiddle.png");
198     bar.setThumbEnd("Images/scrollThumbBottom.png", 9);
199
200     return bar;
201 }
202
203 function loaded()
204 {
205     treeScrollbar = setUpScrollbar("treeScrollbar");
206
207     nodeContentsScrollArea = new AppleScrollArea(document.getElementById("nodeContentsScrollview"),
208         setUpScrollbar("nodeContentsScrollbar"));
209     elementAttributesScrollArea = new AppleScrollArea(document.getElementById("elementAttributesScrollview"),
210         setUpScrollbar("elementAttributesScrollbar"));
211     
212     styleRulesScrollArea = new AppleScrollArea(document.getElementById("styleRulesScrollview"),
213         setUpScrollbar("styleRulesScrollbar"));
214     stylePropertiesScrollArea = new AppleScrollArea(document.getElementById("stylePropertiesScrollview"),
215         setUpScrollbar("stylePropertiesScrollbar"));
216
217     jsPropertiesScrollArea = new AppleScrollArea(document.getElementById("jsPropertiesScrollview"),
218         setUpScrollbar("jsPropertiesScrollbar"));
219
220     treeScrollbar._getViewToContentRatio = function() {
221         var contentHeight = Inspector.treeViewScrollHeight();
222         var height = document.getElementById("treeScrollArea").offsetHeight;
223         if (contentHeight > height)
224             return height / contentHeight;
225         return 1.0;
226     }
227
228     treeScrollbar._computeTrackOffset = function() { return Inspector.treeViewOffsetTop(); }
229     treeScrollbar._getContentLength = function() { return Inspector.treeViewScrollHeight(); }
230     treeScrollbar._getViewLength = function() { return document.getElementById("treeScrollArea").offsetHeight; }
231     treeScrollbar._canScroll = function() { return true; }
232
233     treeScrollbar.scrollTo = function(pos) {
234         Inspector.treeViewScrollTo(pos);
235         this.verticalHasScrolled();
236     }
237
238     treeScrollbar.verticalHasScrolled = function() {
239         var new_thumb_pos = this._thumbPositionForContentPosition(Inspector.treeViewOffsetTop());
240         this._thumbStart = new_thumb_pos;
241         this._thumb.style.top = new_thumb_pos + "px";
242     }
243
244     // much better AppleScrollArea reveal
245     AppleScrollArea.prototype.reveal = function(node) {
246         var offsetY = 0;
247         var obj = node;
248         do {
249             offsetY += obj.offsetTop;
250             obj = obj.offsetParent;
251         } while (obj && obj != this.content);
252
253         var offsetX = 0;
254         obj = node;
255         do {
256             offsetX += obj.offsetLeft;
257             obj = obj.offsetParent;
258         } while (obj && obj != this.content);
259
260         var top = this.content.scrollTop;
261         var height = this.viewHeight;
262         if ((top + height) < (offsetY + node.clientHeight)) 
263             this.verticalScrollTo(offsetY - height + node.clientHeight);
264         else if (top > offsetY)
265             this.verticalScrollTo(offsetY);
266
267         var left = this.content.scrollLeft;
268         var width = this.viewWidth;
269         if ((left + width) < (offsetX + node.clientWidth)) 
270             this.horizontalScrollTo(offsetX - width + node.clientWidth);
271         else if (left > offsetX)
272             this.horizontalScrollTo(offsetX);
273     }
274
275     // Change the standard show/hide to include the entire scrollbar.
276     // This lets the content reflow to use the additional space when the scrollbar is hidden.
277     AppleScrollbar.prototype.hide = function() {
278         this._track.style.display = "none";
279         this.scrollbar.style.display = "none";
280         this.hidden = true;
281     }
282     AppleScrollbar.prototype.show = function() {
283         this._track.style.display = "block";
284         this.scrollbar.style.removeProperty("display");
285         if (this.hidden) {
286             this.hidden = false;
287             this.refresh();
288         }
289     }
290
291     window.addEventListener("resize", refreshScrollbars, false);
292
293     toggleNoSelection(false);
294     switchPane("node");
295 }
296
297 function refreshScrollbars()
298 {
299     elementAttributesScrollArea.refresh();
300     jsPropertiesScrollArea.refresh();
301     nodeContentsScrollArea.refresh();
302     stylePropertiesScrollArea.refresh();
303     styleRulesScrollArea.refresh();
304 }
305
306 var searchActive = false;
307
308 function performSearch(query)
309 {
310     var treePopup = document.getElementById("treePopup");
311     var searchField = document.getElementById("search");
312     var searchCount = document.getElementById("searchCount");
313
314     if (query.length && !searchActive) {
315         treePopup.style.display = "none";
316         searchCount.style.display = "block";
317         searchField.style.width = "150px";
318         searchActive = true;
319     } else if (!query.length && searchActive) {
320         treePopup.style.removeProperty("display");
321         searchCount.style.removeProperty("display");
322         searchField.style.removeProperty("width");
323         searchActive = false;
324     }
325
326     Inspector.searchPerformed(query);
327 }
328
329 function resultsWithXpathQuery(query)
330 {
331     var nodeList = null;
332     try {
333         var focusedNode = Inspector.focusedDOMNode();
334         nodeList = focusedNode.document.evaluate(query, focusedNode.document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
335     } catch(err) {
336         // ignore any exceptions. the query might be malformed, but we allow that 
337     }
338     return nodeList;
339 }
340
341 var tabNames = ["node","metrics","style","properties"];
342 var currentPane = "node";
343 var paneUpdateState = [];
344 var noSelection = false;
345
346 function toggleNoSelection(state)
347 {
348     noSelection = state;
349     if (noSelection) {
350         for (var i = 0; i < tabNames.length; i++)
351             document.getElementById(tabNames[i] + "Pane").style.display = "none";
352         document.getElementById("noSelection").style.removeProperty("display");
353     } else {
354         document.getElementById("noSelection").style.display = "none";
355         switchPane(currentPane);
356     }
357 }
358
359 function switchPane(pane)
360 {
361     currentPane = pane;
362     for (var i = 0; i < tabNames.length; i++) {
363         var paneElement = document.getElementById(tabNames[i] + "Pane");
364         var button = document.getElementById(tabNames[i] + "Button");
365         if (!button.originalClassName)
366             button.originalClassName = button.className;
367         if (pane == tabNames[i]) {
368             if (!noSelection)
369                 paneElement.style.removeProperty("display");
370             button.className = button.originalClassName + " selected";
371         } else {
372             paneElement.style.display = "none";
373             button.className = button.originalClassName;
374         }
375     }
376
377     if (noSelection)
378         return;
379
380     if (!paneUpdateState[pane]) {
381         eval("update" + pane.charAt(0).toUpperCase() + pane.substr(1) + "Pane()");
382         paneUpdateState[pane] = true;
383     } else {
384         refreshScrollbars();
385     }
386 }
387
388 function nodeTypeName(node)
389 {
390     switch (node.nodeType) {
391         case Node.ELEMENT_NODE: return "Element";
392         case Node.ATTRIBUTE_NODE: return "Attribute";
393         case Node.TEXT_NODE: return "Text";
394         case Node.CDATA_SECTION_NODE: return "Character Data";
395         case Node.ENTITY_REFERENCE_NODE: return "Entity Reference";
396         case Node.ENTITY_NODE: return "Entity";
397         case Node.PROCESSING_INSTRUCTION_NODE: return "Processing Instruction";
398         case Node.COMMENT_NODE: return "Comment";
399         case Node.DOCUMENT_NODE: return "Document";
400         case Node.DOCUMENT_TYPE_NODE: return "Document Type";
401         case Node.DOCUMENT_FRAGMENT_NODE: return "Document Fragment";
402         case Node.NOTATION_NODE: return "Notation";
403     }
404     return "(unknown)";
405 }
406
407 function updatePanes()
408 {
409     for (var i = 0; i < tabNames.length; i++)
410         paneUpdateState[tabNames[i]] = false;
411     if (noSelection)
412         return;
413     eval("update" + currentPane.charAt(0).toUpperCase() + currentPane.substr(1) + "Pane()");    
414     paneUpdateState[currentPane] = true;
415 }
416
417 function updateElementAttributes()
418 {
419     var focusedNode = Inspector.focusedDOMNode();
420     var attributesList = document.getElementById("elementAttributesList")
421
422     attributesList.innerHTML = "";
423
424     if (!focusedNode.attributes.length)
425         attributesList.innerHTML = "<span class=\"disabled\">(none)</span>";
426
427     for (i = 0; i < focusedNode.attributes.length; i++) {
428         var attr = focusedNode.attributes[i];
429         var li = document.createElement("li");
430
431         var span = document.createElement("span");
432         span.className = "property";
433         if (attr.namespaceURI)
434             span.title = attr.namespaceURI;
435         span.textContent = attr.name;
436         li.appendChild(span);
437         
438         span = document.createElement("span");
439         span.className = "relation";
440         span.textContent = "=";
441         li.appendChild(span);
442
443         span = document.createElement("span");
444         span.className = "value";
445         span.textContent = "\"" + attr.value + "\"";
446         span.title = attr.value;
447         li.appendChild(span);
448
449         if (attr.style) {
450             span = document.createElement("span");
451             span.className = "mapped";
452             span.innerHTML = "(<a href=\"javascript:selectMappedStyleRule('" + attr.name + "')\">mapped style</a>)";
453             li.appendChild(span);
454         }
455
456         attributesList.appendChild(li);
457     }
458
459     elementAttributesScrollArea.refresh();
460 }
461
462 function updateNodePane()
463 {
464     if (!Inspector)
465         return;
466     var focusedNode = Inspector.focusedDOMNode();
467
468     if (focusedNode.nodeType == Node.TEXT_NODE || focusedNode.nodeType == Node.COMMENT_NODE) {
469         document.getElementById("nodeNamespaceRow").style.display = "none";
470         document.getElementById("elementAttributes").style.display = "none";
471         document.getElementById("nodeContents").style.removeProperty("display");
472
473         document.getElementById("nodeContentsScrollview").textContent = focusedNode.nodeValue;
474         nodeContentsScrollArea.refresh();
475     } else if (focusedNode.nodeType == Node.ELEMENT_NODE) {
476         document.getElementById("elementAttributes").style.removeProperty("display");
477         document.getElementById("nodeContents").style.display = "none";
478
479         updateElementAttributes();
480         
481         if (focusedNode.namespaceURI.length > 0) {
482             document.getElementById("nodeNamespace").textContent = focusedNode.namespaceURI;
483             document.getElementById("nodeNamespace").title = focusedNode.namespaceURI;
484             document.getElementById("nodeNamespaceRow").style.removeProperty("display");
485         } else {
486             document.getElementById("nodeNamespaceRow").style.display = "none";
487         }
488     } else if (focusedNode.nodeType == Node.DOCUMENT_NODE) {
489         document.getElementById("nodeNamespaceRow").style.display = "none";
490         document.getElementById("elementAttributes").style.display = "none";
491         document.getElementById("nodeContents").style.display = "none";
492     }
493
494     document.getElementById("nodeType").textContent = nodeTypeName(focusedNode);
495     document.getElementById("nodeName").textContent = focusedNode.nodeName;
496
497     refreshScrollbars();
498 }
499
500 var styleRules = null;
501 var selectedStyleRuleIndex = 0;
502 var styleProperties = null;
503 var expandedStyleShorthands = [];
504
505 function updateStylePane()
506 {
507     var focusedNode = Inspector.focusedDOMNode();
508     if (focusedNode.nodeType == Node.TEXT_NODE && focusedNode.parentNode && focusedNode.parentNode.nodeType == Node.ELEMENT_NODE)
509         focusedNode = focusedNode.parentNode;
510     var rulesArea = document.getElementById("styleRulesScrollview");
511     var propertiesArea = document.getElementById("stylePropertiesTree");
512
513     rulesArea.innerHTML = "";
514     propertiesArea.innerHTML = "";
515     styleRules = [];
516     styleProperties = [];
517
518     if (focusedNode.nodeType == Node.ELEMENT_NODE) {
519         document.getElementById("styleRules").style.removeProperty("display");
520         document.getElementById("styleProperties").style.removeProperty("display");
521         document.getElementById("noStyle").style.display = "none";
522
523         var propertyCount = [];
524
525         var computedStyle = focusedNode.ownerDocument.defaultView.getComputedStyle(focusedNode);
526         if (computedStyle && computedStyle.length) {
527             var computedObj = {
528                 isComputedStyle: true,
529                 selectorText: "Computed Style",
530                 style: computedStyle,
531                 subtitle: "",
532             };
533             styleRules.push(computedObj);
534         }
535
536         var focusedNodeName = focusedNode.nodeName.toLowerCase();
537         for (var i = 0; i < focusedNode.attributes.length; i++) {
538             var attr = focusedNode.attributes[i];
539             if (attr.style) {
540                 var attrStyle = {
541                     attrName: attr.name,
542                     style: attr.style,
543                     subtitle: "element\u2019s \u201C" + attr.name + "\u201D attribute",
544                 };
545                 attrStyle.selectorText = focusedNodeName + "[" + attr.name;
546                 if (attr.value.length)
547                     attrStyle.selectorText += "=" + attr.value;
548                 attrStyle.selectorText += "]";
549                 styleRules.push(attrStyle);
550             }
551         }
552
553         var matchedStyleRules = focusedNode.ownerDocument.defaultView.getMatchedCSSRules(focusedNode, "", !showUserAgentStyles);
554         if (matchedStyleRules) {
555             for (var i = 0; i < matchedStyleRules.length; i++) {
556                 styleRules.push(matchedStyleRules[i]);
557             }
558         }
559
560         if (focusedNode.style.length) {
561             var inlineStyle = {
562                 selectorText: "Inline Style Attribute",
563                 style: focusedNode.style,
564                 subtitle: "element\u2019s \u201Cstyle\u201D attribute",
565             };
566             styleRules.push(inlineStyle);
567         }
568
569         if (styleRules.length && selectedStyleRuleIndex >= styleRules.length)
570             selectedStyleRuleIndex = (styleRules.length - 1);
571
572         var priorityUsed = false;
573         for (var i = (styleRules.length - 1); i >= 0; --i) {
574             styleProperties[i] = [];
575
576             var row = document.createElement("div");
577             row.className = "row";
578             if (i == selectedStyleRuleIndex)
579                 row.className += " focused";
580             if (styleRules[i].isComputedStyle)
581                 row.className += " computedStyle";
582
583             var cell = document.createElement("div");
584             cell.className = "cell selector";
585             var text = styleRules[i].selectorText;
586             cell.textContent = text;
587             cell.title = text;
588             row.appendChild(cell);
589
590             cell = document.createElement("div");
591             cell.className = "cell stylesheet";
592             var sheet;
593             if (styleRules[i].subtitle != null)
594                 sheet = styleRules[i].subtitle;
595             else if (styleRules[i].parentStyleSheet && styleRules[i].parentStyleSheet.href)
596                 sheet = styleRules[i].parentStyleSheet.href;
597             else if (styleRules[i].parentStyleSheet && !styleRules[i].parentStyleSheet.ownerNode)
598                 sheet = "user agent stylesheet";
599             else
600                 sheet = "inline stylesheet";
601             cell.textContent = sheet;
602             cell.title = sheet;
603             row.appendChild(cell);
604
605             row.styleRuleIndex = i;
606             row.addEventListener("click", styleRuleSelect, true);
607
608             var style = styleRules[i].style;
609             var styleShorthandLookup = [];
610             for (var j = 0; j < style.length; j++) {
611                 var prop = null;
612                 var name = style[j];
613                 var shorthand = style.getPropertyShorthand(name);
614                 if (shorthand)
615                     prop = styleShorthandLookup[shorthand];
616
617                 if (!priorityUsed && style.getPropertyPriority(name).length)
618                     priorityUsed = true;
619
620                 if (prop) {
621                     prop.subProperties.push(name);
622                 } else {
623                     prop = {
624                         style: style,
625                         subProperties: [name],
626                         unusedProperties: [],
627                         name: (shorthand ? shorthand : name),
628                     };
629                     styleProperties[i].push(prop);
630                     if (shorthand) {
631                         styleShorthandLookup[shorthand] = prop;
632                         if (!propertyCount[shorthand]) {
633                             propertyCount[shorthand] = 1;
634                         } else {
635                             prop.unusedProperties[shorthand] = true;
636                             propertyCount[shorthand]++;
637                         }
638                     }
639                 }
640
641                 if (styleRules[i].isComputedStyle)
642                     continue;
643
644                 if (!propertyCount[name]) {
645                     propertyCount[name] = 1;
646                 } else {
647                     prop.unusedProperties[name] = true;
648                     propertyCount[name]++;
649                 }
650             }
651
652             if (styleRules[i].isComputedStyle && styleRules.length > 1) {
653                 var divider = document.createElement("hr");
654                 divider.className = "divider";
655                 rulesArea.insertBefore(divider, rulesArea.firstChild);
656             }
657
658             if (rulesArea.firstChild)
659                 rulesArea.insertBefore(row, rulesArea.firstChild);
660             else
661                 rulesArea.appendChild(row);
662         }
663
664         if (priorityUsed) {
665             // walk the properties again and account for !important
666             var priorityCount = [];
667             for (var i = 0; i < styleRules.length; i++) {
668                 if (styleRules[i].isComputedStyle)
669                     continue;
670                 var style = styleRules[i].style;
671                 for (var j = 0; j < styleProperties[i].length; j++) {
672                     var prop = styleProperties[i][j];
673                     for (var k = 0; k < prop.subProperties.length; k++) {
674                         var name = prop.subProperties[k];
675                         if (style.getPropertyPriority(name).length) {
676                             if (!priorityCount[name]) {
677                                 if (prop.unusedProperties[name])
678                                     prop.unusedProperties[name] = false;
679                                 priorityCount[name] = 1;
680                             } else {
681                                 priorityCount[name]++;
682                             }
683                         } else if (priorityCount[name]) {
684                             prop.unusedProperties[name] = true;
685                         }
686                     }
687                 }
688             }
689         }
690
691         updateStyleProperties();
692     } else {
693         var noStyle = document.getElementById("noStyle");
694         noStyle.textContent = "Can't style " + nodeTypeName(focusedNode) + " nodes.";
695         document.getElementById("styleRules").style.display = "none";
696         document.getElementById("styleProperties").style.display = "none";
697         noStyle.style.removeProperty("display");
698     }
699
700     styleRulesScrollArea.refresh();
701 }
702
703 function styleRuleSelect(event)
704 {
705     var row = document.getElementById("styleRulesScrollview").firstChild;
706     while (row) {
707         if (row.nodeName == "DIV")
708             row.className = "row";
709         row = row.nextSibling;
710     }
711
712     row = event.currentTarget;
713     row.className = "row focused";
714
715     selectedStyleRuleIndex = row.styleRuleIndex;
716     updateStyleProperties();
717 }
718
719 function populateStyleListItem(li, prop, name)
720 {
721     var span = document.createElement("span");
722     span.className = "property";
723     span.textContent = name + ": ";
724     li.appendChild(span);
725
726     var value = prop.style.getPropertyValue(name);
727     if (!value)
728         return;
729
730     span = document.createElement("span");
731     span.className = "value";
732     var textValue = valueNickname[value] ? valueNickname[value] : value;
733     var priority = prop.style.getPropertyPriority(name);
734     if (priority.length)
735         textValue += " !" + priority;
736     span.textContent = textValue + ";";
737     span.title = textValue;
738     li.appendChild(span);
739
740     var colors = value.match(/(rgb\([0-9]+, [0-9]+, [0-9]+\))|(rgba\([0-9]+, [0-9]+, [0-9]+, [0-9]+\))/g);
741     if (colors) {
742         for (var k = 0; k < colors.length; k++) {
743             var swatch = document.createElement("span");
744             swatch.className = "colorSwatch";
745             swatch.style.backgroundColor = colors[k];
746             li.appendChild(swatch);
747         }
748     }
749 }
750
751 function updateStyleProperties()
752 {
753     var focusedNode = Inspector.focusedDOMNode();
754     var propertiesTree = document.getElementById("stylePropertiesTree");
755     propertiesTree.innerHTML = "";
756
757     if (selectedStyleRuleIndex >= styleProperties.length) {
758         stylePropertiesScrollArea.refresh();
759         return;
760     }
761
762     var properties = styleProperties[selectedStyleRuleIndex];
763     var omitTypicalValues = styleRules[selectedStyleRuleIndex].isComputedStyle;
764     for (var i = 0; i < properties.length; i++) {
765         var prop = properties[i];
766         var name = prop.name;
767         if (omitTypicalValues && typicalStylePropertyValue[name] == prop.style.getPropertyValue(name))
768             continue;
769
770         var mainli = document.createElement("li");
771         if (prop.subProperties.length > 1) {
772             mainli.className = "hasChildren";
773             if (expandedStyleShorthands[name])
774                 mainli.className += " expanded";
775             mainli.shorthand = name;
776             var button = document.createElement("button");
777             button.addEventListener("click", toggleStyleShorthand, false);
778             mainli.appendChild(button);
779         }
780
781         populateStyleListItem(mainli, prop, name);
782         propertiesTree.appendChild(mainli);
783
784         var overloadCount = 0;
785         if (prop.subProperties && prop.subProperties.length > 1) {
786             var subTree = document.createElement("ul");
787             if (!expandedStyleShorthands[name])
788                 subTree.style.display = "none";
789
790             for (var j = 0; j < prop.subProperties.length; j++) {
791                 var name = prop.subProperties[j];
792                 var li = document.createElement("li");
793                 if (prop.style.isPropertyImplicit(name) || prop.style.getPropertyValue(name) == "initial")
794                     li.className = "implicit";
795
796                 if (prop.unusedProperties[name] || prop.unusedProperties[name]) {
797                     li.className += " overloaded";
798                     overloadCount++;
799                 }
800
801                 populateStyleListItem(li, prop, name);
802                 subTree.appendChild(li);
803             }
804
805             propertiesTree.appendChild(subTree);
806         }
807
808         if (prop.unusedProperties[name] || overloadCount == prop.subProperties.length)
809             mainli.className += " overloaded";
810     }
811
812     stylePropertiesScrollArea.refresh();
813 }
814
815 function toggleStyleShorthand(event)
816 {
817     var li = event.currentTarget.parentNode;
818     if (li.className.indexOf("expanded") != -1) {
819         li.className = li.className.replace(/ expanded/, "");
820         li.nextSibling.style.display = "none";
821         expandedStyleShorthands[li.shorthand] = false;
822     } else {
823         li.className += " expanded";
824         li.nextSibling.style.removeProperty("display");
825         expandedStyleShorthands[li.shorthand] = true;
826     }
827
828     stylePropertiesScrollArea.refresh();
829 }
830
831 function toggleShowUserAgentStyles()
832 {
833     showUserAgentStyles = !showUserAgentStyles;
834     updateStylePane();
835 }
836
837 function selectMappedStyleRule(attrName)
838 {
839     if (!paneUpdateState["style"])
840         updateStylePane();
841
842     for (var i = 0; i < styleRules.length; i++)
843         if (styleRules[i].attrName == attrName)
844             break;
845
846     selectedStyleRuleIndex = i;
847
848     var row = document.getElementById("styleRulesScrollview").firstChild;
849     while (row) {
850         if (row.nodeName == "DIV") {
851             if (row.styleRuleIndex == selectedStyleRuleIndex)
852                 row.className = "row focused";
853             else
854                 row.className = "row";
855         }
856         row = row.nextSibling;
857     }
858
859     styleRulesScrollArea.refresh();
860
861     updateStyleProperties();
862     switchPane("style");
863 }
864
865 function setMetric(style, name, suffix)
866 {
867     var value = style.getPropertyValue(name + suffix);
868     if (value == "" || value == "0px")
869         value = "\u2012";
870     else
871         value = value.replace(/px$/, "");
872     document.getElementById(name).textContent = value;
873 }
874
875 function setBoxMetrics(style, box, suffix)
876 {
877     setMetric(style, box + "-left", suffix);
878     setMetric(style, box + "-right", suffix);
879     setMetric(style, box + "-top", suffix);
880     setMetric(style, box + "-bottom", suffix);
881 }
882
883 function updateMetricsPane()
884 {
885     var style;
886     var focusedNode = Inspector.focusedDOMNode();
887     if (focusedNode.nodeType == Node.ELEMENT_NODE)
888         style = focusedNode.ownerDocument.defaultView.getComputedStyle(focusedNode);
889     if (!style || style.length == 0) {
890         document.getElementById("noMetrics").style.removeProperty("display");
891         document.getElementById("marginBoxTable").style.display = "none";
892         return;
893     }
894
895     document.getElementById("noMetrics").style.display = "none";
896     document.getElementById("marginBoxTable").style.removeProperty("display");
897
898     setBoxMetrics(style, "margin", "");
899     setBoxMetrics(style, "border", "-width");
900     setBoxMetrics(style, "padding", "");
901
902     var size = style.getPropertyValue("width").replace(/px$/, "")
903         + " \u00D7 "
904         + style.getPropertyValue("height").replace(/px$/, "");
905     document.getElementById("content").textContent = size;
906
907     if (noMarginDisplayType[style.display] == "no")
908         document.getElementById("marginBoxTable").setAttribute("hide", "yes");
909     else
910         document.getElementById("marginBoxTable").removeAttribute("hide");
911
912     if (noPaddingDisplayType[style.display] == "no")
913         document.getElementById("paddingBoxTable").setAttribute("hide", "yes");
914     else
915         document.getElementById("paddingBoxTable").removeAttribute("hide");
916 }
917
918 function updatePropertiesPane()
919 {
920     // FIXME: Like the style pane, this should have a top item that's "all properties"
921     // and separate items for each item in the prototype chain. For now, we implement
922     // only the "all properties" part, and only for enumerable properties.
923
924     var focusedNode = Inspector.focusedDOMNode();
925     var list = document.getElementById("jsPropertiesList");
926     list.innerHTML = "";
927
928     for (var name in focusedNode) {
929         var li = document.createElement("li");
930
931         var span = document.createElement("span");
932         span.className = "property";
933         span.textContent = name + ": ";
934         li.appendChild(span);
935
936         var value = focusedNode[name];
937
938         span = document.createElement("span");
939         span.className = "value";
940         span.textContent = value;
941         span.title = value;
942         li.appendChild(span);
943
944         list.appendChild(li);
945     }
946
947     jsPropertiesScrollArea.refresh();
948 }
949
950 // This is a workaround for rdar://4901491 - Dashboard AppleClasses try to set a NaN value and break the scrollbar.
951 AppleVerticalScrollbar.prototype._setObjectLength = function(object, length)
952 {
953     if (!isNaN(length))
954         object.style.height = length + "px";
955 }