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