cac01946c3487970e7af4a197ebb7963633d80a1
[WebKit-https.git] / WebCore / page / inspector / inspector.js
1 /*
2  * Copyright (C) 2006, 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 var Preferences = {
30     ignoreWhitespace: true,
31     showUserAgentStyles: true,
32     maxInlineTextChildLength: 80,
33     maxTextSearchResultLength: 80
34 }
35
36 var WebInspector = {
37     resources: [],
38     resourceURLMap: {},
39     backForwardList: [],
40
41     get consolePanel()
42     {
43         if (!this._consolePanel)
44             this._consolePanel = new WebInspector.ConsolePanel();
45
46         return this._consolePanel;
47     },
48
49     get networkPanel()
50     {
51         if (!this._networkPanel)
52             this._networkPanel = new WebInspector.NetworkPanel();
53
54         return this._networkPanel;
55     },
56
57     get currentBackForwardIndex()
58     {
59         if (this._currentBackForwardIndex === undefined)
60             this._currentBackForwardIndex = -1;
61
62         return this._currentBackForwardIndex;
63     },
64
65     set currentBackForwardIndex(x)
66     {
67         if (this._currentBackForwardIndex === x)
68             return;
69
70         this._currentBackForwardIndex = x;
71         this.updateBackForwardButtons();
72     },
73
74     get currentFocusElement()
75     {
76         return this._currentFocusElement;
77     },
78
79     set currentFocusElement(x)
80     {
81         if (!x || this._currentFocusElement === x)
82             return;
83
84         if (this._currentFocusElement) {
85             this._currentFocusElement.removeStyleClass("focused");
86             this._currentFocusElement.addStyleClass("blurred");
87         }
88
89         this._currentFocusElement = x;
90
91         if (x) {
92             x.addStyleClass("focused");
93             x.removeStyleClass("blurred");
94         }
95     },
96
97     get currentPanel()
98     {
99         return this._currentPanel;
100     },
101
102     set currentPanel(x)
103     {
104         if (this._currentPanel === x)
105             return;
106
107         if (this._currentPanel)
108             this._currentPanel.hide();
109
110         this._currentPanel = x;
111         this.updateViewButtons();
112
113         if (x)
114             x.show();
115     },
116
117     get attached()
118     {
119         return this._attached;
120     },
121
122     set attached(x)
123     {
124         if (this._attached === x)
125             return;
126
127         this._attached = x;
128
129         var body = document.body;
130         if (x) {
131             InspectorController.attach();
132             body.removeStyleClass("detached");
133             body.addStyleClass("attached");
134         } else {
135             InspectorController.detach();
136             body.removeStyleClass("attached");
137             body.addStyleClass("detached");
138         }
139     },
140
141     get showingStatusArea()
142     {
143         return this._showingStatusArea;
144     },
145
146     set showingStatusArea(x)
147     {
148         if (this._showingStatusArea === x)
149             return;
150
151         this._showingStatusArea = x;
152
153         var list = document.getElementById("list");
154         var status = document.getElementById("status");
155         var statusButton = document.getElementById("statusToggle");
156
157         if (x) {
158             statusButton.addStyleClass("hide");
159             WebInspector.animateStyle([{element: list, end: {bottom: 99}}, {element: status, end: {bottom: 21}}], 250);
160         } else {
161             statusButton.removeStyleClass("hide");
162             WebInspector.animateStyle([{element: list, end: {bottom: 21}}, {element: status, end: {bottom: -57}}], 250);
163         }
164     },
165
166     get showingSearchResults()
167     {
168         return this._showingSearchResults;
169     },
170
171     set showingSearchResults(x)
172     {
173         if (this._showingSearchResults === x)
174             return;
175
176         this._showingSearchResults = x;
177
178         var resultsContainer = document.getElementById("searchResults");
179         if (x) {
180             var animations = [
181                 {element: resultsContainer, end: {top: 28}},
182                 {element: document.getElementById("main"), end: {top: 129}}
183             ];
184             WebInspector.animateStyle(animations, 250);
185         } else {
186             var animations = [
187                 {element: resultsContainer, end: {top: -73}},
188                 {element: document.getElementById("main"), end: {top: 28}}
189             ];
190             WebInspector.animateStyle(animations, 250, function() { resultsContainer.removeChildren(); delete this.searchResultsTree; });
191         }
192     }
193 }
194
195 WebInspector.loaded = function(event)
196 {
197     this.fileOutline = new TreeOutline(document.getElementById("list"));
198     this.statusOutline = new TreeOutline(document.getElementById("status"));
199
200     this.resourceCategories = {
201         documents: new WebInspector.ResourceCategory("documents"),
202         stylesheets: new WebInspector.ResourceCategory("stylesheets"),
203         images: new WebInspector.ResourceCategory("images"),
204         scripts: new WebInspector.ResourceCategory("scripts"),
205         other: new WebInspector.ResourceCategory("other")
206     };
207
208     this.consoleListItem = new WebInspector.ConsoleStatusTreeElement();
209     this.consoleListItem.item.onselect = function(element) { WebInspector.StatusTreeElement.selected(element); WebInspector.navigateToPanel(WebInspector.consolePanel) };
210     this.consoleListItem.item.ondeselect = function(element) { WebInspector.consolePanel.hide() };
211     this.statusOutline.appendChild(this.consoleListItem.item);
212
213     this.networkListItem = new WebInspector.StatusTreeElement("Network");
214     this.networkListItem.onselect = function(element) { WebInspector.StatusTreeElement.selected(element); WebInspector.navigateToPanel(WebInspector.networkPanel); };
215     this.networkListItem.ondeselect = function(element) { WebInspector.networkPanel.hide() };
216     this.statusOutline.appendChild(this.networkListItem);
217
218     this.resourceCategories.documents.listItem.expand();
219
220     this.currentFocusElement = document.getElementById("sidebar");
221
222     this.addMainEventListeners(document);
223
224     window.addEventListener("unload", function(event) { WebInspector.windowUnload(event) }, true);
225     document.addEventListener("mousedown", function(event) { WebInspector.changeFocus(event) }, true);
226     document.addEventListener("focus", function(event) { WebInspector.changeFocus(event) }, true);
227     document.addEventListener("keypress", function(event) { WebInspector.documentKeypress(event) }, true);
228
229     document.getElementById("back").title = "Show previous panel.";
230     document.getElementById("forward").title = "Show next panel.";
231
232     document.getElementById("back").addEventListener("click", function(event) { WebInspector.back() }, true);
233     document.getElementById("forward").addEventListener("click", function(event) { WebInspector.forward() }, true);
234     this.updateBackForwardButtons();
235
236     document.getElementById("attachToggle").addEventListener("click", function(event) { WebInspector.toggleAttach() }, true);
237     document.getElementById("statusToggle").addEventListener("click", function(event) { WebInspector.toggleStatusArea() }, true);
238
239     document.body.addStyleClass("detached");
240
241     window.removeEventListener("load", this.loaded, false);
242     delete this.loaded;
243
244     InspectorController.loaded();
245 }
246
247 window.addEventListener("load", function(event) { WebInspector.loaded(event) }, false);
248
249 WebInspector.windowUnload = function(event)
250 {
251     InspectorController.windowUnloading();
252 }
253
254 WebInspector.windowFocused = function(event)
255 {
256     if (event.target.nodeType === Node.DOCUMENT_NODE)
257         document.body.removeStyleClass("inactive");
258 }
259
260 WebInspector.windowBlured = function(event)
261 {
262     if (event.target.nodeType === Node.DOCUMENT_NODE)
263         document.body.addStyleClass("inactive");
264 }
265
266 WebInspector.changeFocus = function(event)
267 {
268     var nextFocusElement;
269
270     var current = event.target;
271     while (current) {
272         if (current.nodeName.toLowerCase() === "input")
273             nextFocusElement = current;
274         current = current.parentNode;
275     }
276
277     if (!nextFocusElement)
278         nextFocusElement = event.target.firstParentWithClass("focusable");
279
280     this.currentFocusElement = nextFocusElement;
281 }
282
283 WebInspector.documentClick = function(event)
284 {
285     var anchor = event.target.firstParentOrSelfWithNodeName("a");
286     if (!anchor || !anchor.hasStyleClass("webkit-html-resource-link"))
287         return;
288
289     if (WebInspector.showResourceForURL(anchor.getAttribute("href"))) {
290         event.preventDefault();
291         event.stopPropagation();
292     }
293 }
294
295 WebInspector.documentKeypress = function(event)
296 {
297     if (!this.currentFocusElement || !this.currentFocusElement.id || !this.currentFocusElement.id.length)
298         return;
299     if (this.currentFocusElement.id + "Keypress" in WebInspector)
300         WebInspector[this.currentFocusElement.id + "Keypress"](event);
301 }
302
303 WebInspector.sidebarKeypress = function(event)
304 {
305     var nextSelectedElement;
306
307     if (this.fileOutline.selectedTreeElement) {
308         if (!this.fileOutline.handleKeyEvent(event) && event.keyIdentifier === "Down" && !event.altKey && this.showingStatusArea) {
309             var nextSelectedElement = this.statusOutline.children[0];
310             while (nextSelectedElement && !nextSelectedElement.selectable)
311                 nextSelectedElement = nextSelectedElement.traverseNextTreeElement(false);
312         }
313     } else if (this.statusOutline.selectedTreeElement) {
314         if (!this.showingStatusArea || (!this.statusOutline.handleKeyEvent(event) && event.keyIdentifier === "Up" && !event.altKey)) {
315             var nextSelectedElement = this.fileOutline.children[0];
316             var lastSelectable = null;
317
318             while (nextSelectedElement) {
319                 if (nextSelectedElement.selectable)
320                     lastSelectable = nextSelectedElement;
321                 nextSelectedElement = nextSelectedElement.traverseNextTreeElement(false);
322             }
323
324             nextSelectedElement = lastSelectable;
325         }
326     }
327
328     if (nextSelectedElement) {
329         nextSelectedElement.reveal();
330         nextSelectedElement.select();
331
332         event.preventDefault();
333         event.stopPropagation();
334     }
335 }
336
337 WebInspector.mainKeypress = function(event)
338 {
339     if (this.currentPanel && this.currentPanel.handleKeyEvent)
340         this.currentPanel.handleKeyEvent(event);
341 }
342
343 WebInspector.searchResultsKeypress = function(event)
344 {
345     if (this.searchResultsTree)
346         this.searchResultsTree.handleKeyEvent(event);
347 }
348
349 WebInspector.animateStyle = function(animations, duration, callback, complete)
350 {
351     if (complete === undefined)
352         complete = 0;
353     var slice = (1000 / 30); // 30 frames per second
354
355     var defaultUnit = "px";
356     var propertyUnit = {opacity: ""};
357
358     for (var i = 0; i < animations.length; ++i) {
359         var animation = animations[i];
360         var element = null;
361         var start = null;
362         var current = null;
363         var end = null;
364         for (key in animation) {
365             if (key === "element")
366                 element = animation[key];
367             else if (key === "start")
368                 start = animation[key];
369             else if (key == "current")
370                 current = animation[key];
371             else if (key === "end")
372                 end = animation[key];
373         }
374
375         if (!element || !end)
376             continue;
377
378         var computedStyle = element.ownerDocument.defaultView.getComputedStyle(element);
379         if (!start) {
380             start = {};
381             for (key in end)
382                 start[key] = parseInt(computedStyle.getPropertyValue(key));
383             animation.start = start;
384         } else if (complete == 0)
385             for (key in start)
386                 element.style.setProperty(key, start[key] + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
387
388         if (!current) {
389             current = {};
390             for (key in start)
391                 current[key] = start[key];
392             animation.current = current;
393         }
394
395         function cubicInOut(t, b, c, d)
396         {
397             if ((t/=d/2) < 1) return c/2*t*t*t + b;
398             return c/2*((t-=2)*t*t + 2) + b;
399         }
400
401         var style = element.style;
402         for (key in end) {
403             var startValue = start[key];
404             var currentValue = current[key];
405             var endValue = end[key];
406             if ((complete + slice) < duration) {
407                 var delta = (endValue - startValue) / (duration / slice);
408                 var newValue = cubicInOut(complete, startValue, endValue - startValue, duration);
409                 style.setProperty(key, newValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
410                 current[key] = newValue;
411             } else {
412                 style.setProperty(key, endValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
413             }
414         }
415     }
416
417     if (complete < duration)
418         setTimeout(WebInspector.animateStyle, slice, animations, duration, callback, complete + slice);
419     else if (callback)
420         callback();
421 }
422
423 WebInspector.toggleAttach = function()
424 {
425     this.attached = !this.attached;
426 }
427
428 WebInspector.toggleStatusArea = function()
429 {
430     this.showingStatusArea = !this.showingStatusArea;
431 }
432
433 WebInspector.back = function()
434 {
435     if (this.currentBackForwardIndex <= 0) {
436         alert("Can't go back from index " + this.currentBackForwardIndex);
437         return;
438     }
439
440     this.navigateToPanel(this.backForwardList[--this.currentBackForwardIndex], true);
441 }
442
443 WebInspector.forward = function()
444 {
445     if (this.currentBackForwardIndex >= this.backForwardList.length - 1) {
446         alert("Can't go forward from index " + this.currentBackForwardIndex);
447         return;
448     }
449
450     this.navigateToPanel(this.backForwardList[++this.currentBackForwardIndex], true);
451 }
452
453 WebInspector.updateBackForwardButtons = function()
454 {
455     var index = this.currentBackForwardIndex;
456
457     document.getElementById("back").disabled = index <= 0;
458     document.getElementById("forward").disabled = index >= this.backForwardList.length - 1;
459 }
460
461 WebInspector.updateViewButtons = function()
462 {
463     var buttonContainer = document.getElementById("viewbuttons");
464     buttonContainer.removeChildren();
465
466     if (!this.currentPanel || !this.currentPanel.viewButtons)
467         return;
468
469     var buttons = this.currentPanel.viewButtons;
470     if (buttons.length < 2)
471         return;
472
473     for (var i = 0; i < buttons.length; ++i) {
474         var button = buttons[i];
475
476         if (i === 0)
477             button.addStyleClass("first");
478         else if (i === (buttons.length - 1))
479             button.addStyleClass("last");
480
481         if (i) {
482             var divider = document.createElement("img");
483             divider.className = "split-button-divider";
484             buttonContainer.appendChild(divider);
485         }
486
487         button.addStyleClass("split-button");
488         button.addStyleClass("view-button-" + button.title.toLowerCase());
489
490         buttonContainer.appendChild(button);
491     }
492 }
493
494 WebInspector.addResource = function(resource)
495 {
496     this.resources.push(resource);
497     this.resourceURLMap[resource.url] = resource;
498
499     if (resource.mainResource)
500         this.mainResource = resource;
501
502     this.networkPanel.addResourceToTimeline(resource);
503 }
504
505 WebInspector.removeResource = function(resource)
506 {
507     resource.detach();
508
509     resource.category.removeResource(resource);
510
511     delete this.resourceURLMap[resource.url];
512
513     var resourcesLength = this.resources.length;
514     for (var i = 0; i < resourcesLength; ++i) {
515         if (this.resources[i] === resource) {
516             this.resources.splice(i, 1);
517             break;
518         }
519     }
520 }
521
522 WebInspector.clearResources = function()
523 {
524     for (var category in this.resourceCategories)
525         this.resourceCategories[category].removeAllResources();
526     this.resources = [];
527     this.backForwardList = [];
528     this.currentBackForwardIndex = -1;
529     delete this.mainResource;
530 }
531
532 WebInspector.resourceURLChanged = function(resource, oldURL)
533 {
534     delete this.resourceURLMap[oldURL];
535     this.resourceURLMap[resource.url] = resource;
536 }
537
538 WebInspector.addMessageToConsole = function(msg)
539 {
540     this.consolePanel.addMessage(msg);
541     switch (msg.level) {
542         case WebInspector.ConsoleMessage.WarningMessageLevel:
543             ++this.consoleListItem.warnings;
544             this.showingStatusArea = true;
545             break;
546         case WebInspector.ConsoleMessage.ErrorMessageLevel:
547             ++this.consoleListItem.errors;
548             this.showingStatusArea = true;
549             break;
550     }
551 }
552
553 WebInspector.clearConsoleMessages = function()
554 {
555     this.consolePanel.clearMessages();
556     this.consoleListItem.warnings = this.consoleListItem.errors = 0;
557 }
558
559 WebInspector.clearNetworkTimeline = function()
560 {
561     if (this._networkPanel)
562         this._networkPanel.clearTimeline();
563 }
564
565 WebInspector.drawLoadingPieChart = function(canvas, percent) {
566     var g = canvas.getContext("2d");
567     var darkColor = "rgb(122, 168, 218)";
568     var lightColor = "rgb(228, 241, 251)";
569     var cx = 8;
570     var cy = 8;
571     var r = 7;
572
573     g.beginPath();
574     g.arc(cx, cy, r, 0, Math.PI * 2, false); 
575     g.closePath();
576
577     g.lineWidth = 1;
578     g.strokeStyle = darkColor;
579     g.fillStyle = lightColor;
580     g.fill();
581     g.stroke();
582
583     var startangle = -Math.PI / 2;
584     var endangle = startangle + (percent * Math.PI * 2);
585
586     g.beginPath();
587     g.moveTo(cx, cy);
588     g.arc(cx, cy, r, startangle, endangle, false); 
589     g.closePath();
590
591     g.fillStyle = darkColor;
592     g.fill();
593 }
594
595 WebInspector.updateFocusedNode = function(node)
596 {
597     if (!node)
598         // FIXME: Should we deselect if null is passed in?
599         return;
600
601     for (var i = 0; i < this.resourceCategories.documents.resources.length; ++i) {
602         var resource = this.resourceCategories.documents.resources[i];
603         if (resource.documentNode !== node.ownerDocument)
604             continue;
605
606         resource.panel.navigateToView("dom");
607         resource.panel.focusedDOMNode = node;
608
609         this.currentFocusElement = document.getElementById("main");
610
611         break;
612     }
613 }
614
615 WebInspector.resourceForURL = function(url)
616 {
617     for (var resourceURL in this.resourceURLMap) {
618         if (resourceURL.hasSubstring(url))
619             return this.resourceURLMap[resourceURL];
620     }
621
622     return null;
623 }
624
625 WebInspector.showResourceForURL = function(url)
626 {
627     var resource = this.resourceForURL(url);
628     if (!resource)
629         return false;
630
631     this.navigateToResource(resource);
632     return true;
633 }
634
635 WebInspector.linkifyURL = function(url, linkText, isExternal)
636 {
637     if (linkText === undefined)
638         linkText = url.escapeHTML();
639     var className = isExternal ? "webkit-html-external-link" : "webkit-html-resource-link";
640     var link = "<a href=\"" + url + "\" class=\"" + className + "\" target=\"_blank\">" + linkText + "</a>";
641     return link;
642 }
643
644 WebInspector.addMainEventListeners = function(doc)
645 {
646     doc.defaultView.addEventListener("focus", function(event) { WebInspector.windowFocused(event) }, true);
647     doc.defaultView.addEventListener("blur", function(event) { WebInspector.windowBlured(event) }, true);
648     doc.addEventListener("click", function(event) { WebInspector.documentClick(event) }, true);
649 }
650
651 WebInspector.navigateToView = function(view)
652 {
653     if (!view) {
654         alert("Called navigateToView(null)");
655         return;
656     }
657
658     view.panel.currentView = view;
659     this.navigateToPanel(view.panel);
660 }
661
662 WebInspector.performSearch = function(query)
663 {
664     if (!query || !query.length) {
665         this.showingSearchResults = false;
666         return;
667     }
668
669     var resultsContainer = document.getElementById("searchResults");
670     resultsContainer.removeChildren();
671
672     var isXPath = query.indexOf("/") !== -1;
673
674     var xpathQuery;
675     if (isXPath)
676         xpathQuery = query;
677     else {
678         var escapedQuery = query.escapeCharacters("'");
679         xpathQuery = "//*[contains(name(),'" + escapedQuery + "') or contains(@*,'" + escapedQuery + "')] | //text()[contains(.,'" + escapedQuery + "')] | //comment()[contains(.,'" + escapedQuery + "')]";
680     }
681
682     var resourcesToSearch = [].concat(this.resourceCategories.documents.resources, this.resourceCategories.stylesheets.resources, this.resourceCategories.scripts.resources, this.resourceCategories.other.resources);
683
684     var files = [];
685     for (var i = 0; i < resourcesToSearch.length; ++i) {
686         var resource = resourcesToSearch[i];
687
688         var sourceResults = [];
689         if (!isXPath) {
690             resource.panel.refreshIfNeeded();
691             if ("sourceFrame" in resource.panel)
692                 sourceResults = InspectorController.search(resource.panel.sourceFrame.contentDocument, query);
693         }
694
695         var domResults = [];
696         if (resource.category === this.resourceCategories.documents) {
697             try {
698                 var doc = resource.documentNode;
699                 var nodeList = doc.evaluate(xpathQuery, doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
700                 for (var j = 0; j < nodeList.snapshotLength; ++j)
701                     domResults.push(nodeList.snapshotItem(i));
702             } catch(err) {
703                 // ignore any exceptions. the query might be malformed, but we allow that.
704             }
705         }
706
707         if ((!sourceResults || !sourceResults.length) && !domResults.length)
708             continue;
709
710         files.push({resource: resource, sourceResults: sourceResults, domResults: domResults});
711     }
712
713     if (!files.length)
714         return;
715
716     this.showingSearchResults = true;
717
718     var fileList = document.createElement("ol");
719     fileList.className = "outline-disclosure";
720     resultsContainer.appendChild(fileList);
721     this.searchResultsTree = new TreeOutline(fileList);
722
723     var sourceResultSelected = function(element)
724     {
725         var selection = window.getSelection();
726         selection.removeAllRanges();
727         selection.addRange(element.representedObject.range);
728
729         element.representedObject.panel.navigateToView("source");
730         element.representedObject.line.scrollIntoView(true);
731         resultsContainer.scrollToElement(element._listItemNode);
732     }
733
734     var domResultSelected = function(element)
735     {
736         element.representedObject.panel.navigateToView("dom");
737         element.representedObject.panel.focusedDOMNode = element.representedObject.node;
738         resultsContainer.scrollToElement(element._listItemNode);
739     }
740
741     for (var i = 0; i < files.length; ++i) {
742         var file = files[i];
743
744         var fileItem = new TreeElement(file.resource.displayName, {}, true);
745         fileItem.expanded = true;
746         fileItem.selectable = false;
747         this.searchResultsTree.appendChild(fileItem);
748
749         if (file.sourceResults.length) {
750             for (var j = 0; j < file.sourceResults.length; ++j) {
751                 var range = file.sourceResults[j];
752
753                 var line = range.startContainer;
754                 while (line.parentNode && line.nodeName.toLowerCase() != "tr")
755                     line = line.parentNode;
756                 var lineRange = file.resource.panel.sourceFrame.contentDocument.createRange();
757                 lineRange.selectNodeContents(line);
758
759                 // Don't include any error bubbles in the search result
760                 var end = line.lastChild.lastChild;
761                 if (end.nodeName.toLowerCase() == "div" && end.hasStyleClass("webkit-html-message-bubble")) {
762                     while (end && end.nodeName.toLowerCase() == "div" && end.hasStyleClass("webkit-html-message-bubble"))
763                         end = end.previousSibling;
764                     lineRange.setEndAfter(end);
765                 }
766
767                 var beforeRange = file.resource.panel.sourceFrame.contentDocument.createRange();
768                 beforeRange.setStart(lineRange.startContainer, lineRange.startOffset);
769                 beforeRange.setEnd(range.startContainer, range.startOffset);
770
771                 var afterRange = file.resource.panel.sourceFrame.contentDocument.createRange();
772                 afterRange.setStart(range.endContainer, range.endOffset);
773                 afterRange.setEnd(lineRange.endContainer, lineRange.endOffset);
774
775                 var beforeText = beforeRange.toString().trimLeadingWhitespace();
776                 var text = range.toString();
777                 var afterText = afterRange.toString().trimTrailingWhitespace();
778
779                 var length = beforeText.length + text.length + afterText.length;
780                 if (length > Preferences.maxTextSearchResultLength) {
781                     var beforeAfterLength = (Preferences.maxTextSearchResultLength - text.length) / 2;
782                     if (beforeText.length > beforeAfterLength)
783                         beforeText = "\u2026" + beforeText.substr(-beforeAfterLength);
784                     if (afterText.length > beforeAfterLength)
785                         afterText = afterText.substr(0, beforeAfterLength) + "\u2026";
786                 }
787
788                 var title = "<div class=\"selection selected\"></div>";
789                 if (j == 0)
790                     title += "<div class=\"search-results-section\">Source</div>";
791                 title += beforeText.escapeHTML() + "<span class=\"search-matched-string\">" + text.escapeHTML() + "</span>" + afterText.escapeHTML();
792                 var item = new TreeElement(title, {panel: file.resource.panel, line: line, range: range}, false);
793                 item.onselect = sourceResultSelected;
794                 fileItem.appendChild(item);
795             }
796         }
797
798         if (file.domResults.length) {
799             for (var j = 0; j < file.domResults.length; ++j) {
800                 var node = file.domResults[j];
801                 var title = "<div class=\"selection selected\"></div>";
802                 if (j == 0)
803                     title += "<div class=\"search-results-section\">DOM</div>";
804                 title += nodeTitleInfo.call(node).title;
805                 var item = new TreeElement(title, {panel: file.resource.panel, node: node}, false);
806                 item.onselect = domResultSelected;
807                 fileItem.appendChild(item);
808             }
809         }
810     }
811 }
812
813 WebInspector.navigateToResource = function(resource)
814 {
815     this.navigateToPanel(resource.panel);
816 }
817
818 WebInspector.navigateToPanel = function(panel, fromBackForwardAction)
819 {
820     if (this.currentPanel == panel)
821         return;
822
823     if (!fromBackForwardAction) {
824         var oldIndex = this.currentBackForwardIndex;
825         if (oldIndex >= 0)
826             this.backForwardList.splice(oldIndex + 1, this.backForwardList.length - oldIndex);
827         this.currentBackForwardIndex++;
828         this.backForwardList.push(panel);
829     }
830
831     this.currentPanel = panel;
832 }
833
834 WebInspector.Panel = function()
835 {
836     this.element = document.createElement("div");
837     this.element.className = "panel";
838     this.attach();
839
840     this._needsRefresh = true;
841     this.refresh();
842 }
843
844 WebInspector.Panel.prototype = {
845     show: function()
846     {
847         this.visible = true;
848     },
849
850     hide: function()
851     {
852         this.visible = false;
853     },
854
855     attach: function()
856     {
857         document.getElementById("main").appendChild(this.element);
858     },
859
860     detach: function()
861     {
862         if (WebInspector.currentPanel === this)
863             WebInspector.currentPanel = null;
864         if (this.element && this.element.parentNode)
865             this.element.parentNode.removeChild(this.element);
866     },
867
868     refresh: function()
869     {
870     },
871
872     refreshIfNeeded: function()
873     {
874         if (this.needsRefresh)
875             this.refresh();
876     },
877
878     get visible()
879     {
880         return this._visible;
881     },
882
883     set visible(x)
884     {
885         if (this._visible === x)
886             return;
887
888         this._visible = x;
889
890         if (x) {
891             this.element.addStyleClass("selected");
892             this.refreshIfNeeded();
893         } else {
894             this.element.removeStyleClass("selected");
895         }
896     },
897
898     get needsRefresh()
899     {
900         return this._needsRefresh;
901     },
902
903     set needsRefresh(x)
904     {
905         if (this._needsRefresh === x)
906             return;
907         this._needsRefresh = x;
908         if (x && this.visible)
909             this.refresh();
910     }
911 }
912
913 WebInspector.StatusTreeElement = function(title)
914 {
915     var item = new TreeElement("<span class=\"title only\">" + title + "</span><span class=\"icon " + title.toLowerCase() + "\"></span>", {}, false);
916     item.onselect = WebInspector.StatusTreeElement.selected;
917     return item;
918 }
919
920 WebInspector.StatusTreeElement.selected = function(element)
921 {
922     var selectedElement = WebInspector.fileOutline.selectedTreeElement;
923     if (selectedElement)
924         selectedElement.deselect();
925 }
926
927 WebInspector.ConsoleStatusTreeElement = function()
928 {
929     this.item = WebInspector.StatusTreeElement.call(this, "Console");
930 }
931
932 WebInspector.ConsoleStatusTreeElement.prototype = {
933     get warnings()
934     {
935         if (!("_warnings" in this))
936             this._warnings = 0;
937
938         return this._warnings;
939     },
940
941     set warnings(x)
942     {
943         if (this._warnings === x)
944             return;
945
946         this._warnings = x;
947
948         this._updateTitle();
949     },
950
951     get errors()
952     {
953         if (!("_errors" in this))
954             this._errors = 0;
955
956         return this._errors;
957     },
958
959     set errors(x)
960     {
961         if (this._errors === x)
962             return;
963
964         this._errors = x;
965
966         this._updateTitle();
967     },
968
969     _updateTitle: function()
970     {
971         var title = "<span class=\"title";
972         if (!this.warnings && !this.errors)
973             title += " only";
974         title += "\">Console</span><span class=\"icon console\"></span>";
975
976         if (this.warnings || this.errors) {
977             title += "<span class=\"info\">";
978             if (this.errors) {
979                 title += this.errors + " error";
980                 if (this.errors > 1)
981                     title += "s";
982             }
983             if (this.warnings) {
984                 if (this.errors)
985                     title += ", ";
986                 title += this.warnings + " warning";
987                 if (this.warnings > 1)
988                     title += "s";
989             }
990             title += "</span>";
991         }
992
993         this.item.title = title;
994     }
995 }
996
997 WebInspector.ConsoleStatusTreeElement.prototype.__proto__ = WebInspector.StatusTreeElement.prototype;
998
999 WebInspector.Tips = {
1000     ResourceNotCompressed: {id: 0, message: "You could save bandwidth by having your web server compress this transfer with gzip or zlib."}
1001 }
1002
1003 WebInspector.Warnings = {
1004     IncorrectMIMEType: {id: 0, message: "Resource interpreted as %s but transferred with MIME type %s."}
1005 }
1006
1007 // This table maps MIME types to the Resource.Types which are valid for them.
1008 // The following line:
1009 //    "text/html":                {0: 1},
1010 // means that text/html is a valid MIME type for resources that have type
1011 // WebInspector.Resource.Type.Document (which has a value of 0).
1012 WebInspector.MIMETypes = {
1013     "text/html":                {0: 1},
1014     "text/xml":                 {0: 1},
1015     "text/plain":               {0: 1},
1016     "application/xhtml+xml":    {0: 1},
1017     "text/css":                 {1: 1},
1018     "text/xsl":                 {1: 1},
1019     "image/jpeg":               {2: 1},
1020     "image/png":                {2: 1},
1021     "image/gif":                {2: 1},
1022     "image/bmp":                {2: 1},
1023     "image/x-icon":             {2: 1},
1024     "image/x-xbitmap":          {2: 1},
1025     "text/javascript":          {3: 1},
1026     "text/ecmascript":          {3: 1},
1027     "application/javascript":   {3: 1},
1028     "application/ecmascript":   {3: 1},
1029     "application/x-javascript": {3: 1},
1030     "text/javascript1.1":       {3: 1},
1031     "text/javascript1.2":       {3: 1},
1032     "text/javascript1.3":       {3: 1},
1033     "text/jscript":             {3: 1},
1034     "text/livescript":          {3: 1},
1035 }