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