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