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