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