The search is only performed if the field contains more than 3
[WebKit-https.git] / WebCore / page / inspector / inspector.js
1 /*
2  * Copyright (C) 2006, 2007, 2008 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     minConsoleHeight: 75,
36     minSidebarWidth: 100,
37     minElementsSidebarWidth: 200,
38     minScriptsSidebarWidth: 200,
39     showInheritedComputedStyleProperties: false,
40     showMissingLocalizedStrings: false
41 }
42
43 var WebInspector = {
44     resources: [],
45     resourceURLMap: {},
46     searchResultsHeight: 100,
47     missingLocalizedStrings: {},
48
49     get previousFocusElement()
50     {
51         return this._previousFocusElement;
52     },
53
54     get currentFocusElement()
55     {
56         return this._currentFocusElement;
57     },
58
59     set currentFocusElement(x)
60     {
61         if (!x || this._currentFocusElement === x)
62             return;
63
64         this._previousFocusElement = this._currentFocusElement;
65         this._currentFocusElement = x;
66
67         if (this._currentFocusElement) {
68             this._currentFocusElement.focus();
69
70             // Make a caret selection inside the new element if there isn't a range selection and
71             // there isn't already a caret selection inside.
72             var selection = window.getSelection();
73             if (selection.isCollapsed && !this._currentFocusElement.isInsertionCaretInside()) {
74                 var selectionRange = document.createRange();
75                 selectionRange.setStart(this._currentFocusElement, 0);
76                 selectionRange.setEnd(this._currentFocusElement, 0);
77
78                 selection.removeAllRanges();
79                 selection.addRange(selectionRange);
80             }
81         } else if (this._previousFocusElement)
82             this._previousFocusElement.blur();
83     },
84
85     get currentPanel()
86     {
87         return this._currentPanel;
88     },
89
90     set currentPanel(x)
91     {
92         if (this._currentPanel === x)
93             return;
94
95         if (this._currentPanel)
96             this._currentPanel.hide();
97
98         this._currentPanel = x;
99
100         if (x)
101             x.show();
102     },
103
104     get attached()
105     {
106         return this._attached;
107     },
108
109     set attached(x)
110     {
111         if (this._attached === x)
112             return;
113
114         this._attached = x;
115
116         var dockToggleButton = document.getElementById("dock-status-bar-item");
117         var body = document.body;
118
119         if (x) {
120             InspectorController.attach();
121             body.removeStyleClass("detached");
122             body.addStyleClass("attached");
123             dockToggleButton.title = WebInspector.UIString("Undock into separate window.");
124         } else {
125             InspectorController.detach();
126             body.removeStyleClass("attached");
127             body.addStyleClass("detached");
128             dockToggleButton.title = WebInspector.UIString("Dock to main window.");
129         }
130     },
131
132     get showingSearchResults()
133     {
134         return this._showingSearchResults;
135     },
136
137     set showingSearchResults(x)
138     {
139         if (this._showingSearchResults === x)
140             return;
141
142         this._showingSearchResults = x;
143
144         var resultsContainer = document.getElementById("searchResults");
145         var searchResultsResizer = document.getElementById("searchResultsResizer");
146
147         if (x) {
148             resultsContainer.removeStyleClass("hidden");
149             searchResultsResizer.removeStyleClass("hidden");
150
151             var animations = [
152                 {element: resultsContainer, end: {top: 0}},
153                 {element: searchResultsResizer, end: {top: WebInspector.searchResultsHeight - 3}},
154                 {element: document.getElementById("main-panels"), end: {top: WebInspector.searchResultsHeight}}
155             ];
156
157             WebInspector.animateStyle(animations, 250);
158         } else {
159             searchResultsResizer.addStyleClass("hidden");
160
161             var animations = [
162                 {element: resultsContainer, end: {top: -WebInspector.searchResultsHeight}},
163                 {element: searchResultsResizer, end: {top: 0}},
164                 {element: document.getElementById("main-panels"), end: {top: 0}}
165             ];
166
167             var animationFinished = function()
168             {
169                 resultsContainer.addStyleClass("hidden");
170                 resultsContainer.removeChildren();
171                 delete this.searchResultsTree;
172             };
173
174             WebInspector.animateStyle(animations, 250, animationFinished);
175         }
176     },
177
178     get errors()
179     {
180         return this._errors || 0;
181     },
182
183     set errors(x)
184     {
185         x = Math.max(x, 0);
186
187         if (this._errors === x)
188             return;
189         this._errors = x;
190         this._updateErrorAndWarningCounts();
191     },
192
193     get warnings()
194     {
195         return this._warnings || 0;
196     },
197
198     set warnings(x)
199     {
200         x = Math.max(x, 0);
201
202         if (this._warnings === x)
203             return;
204         this._warnings = x;
205         this._updateErrorAndWarningCounts();
206     },
207
208     _updateErrorAndWarningCounts: function()
209     {
210         var errorWarningElement = document.getElementById("error-warning-count");
211         if (!errorWarningElement)
212             return;
213
214         if (!this.errors && !this.warnings) {
215             errorWarningElement.addStyleClass("hidden");
216             return;
217         }
218
219         errorWarningElement.removeStyleClass("hidden");
220
221         errorWarningElement.removeChildren();
222
223         if (this.errors) {
224             var errorElement = document.createElement("span");
225             errorElement.id = "error-count";
226             errorElement.textContent = this.errors;
227             errorWarningElement.appendChild(errorElement);
228         }
229
230         if (this.warnings) {
231             var warningsElement = document.createElement("span");
232             warningsElement.id = "warning-count";
233             warningsElement.textContent = this.warnings;
234             errorWarningElement.appendChild(warningsElement);
235         }
236
237         if (this.errors) {
238             if (this.warnings) {
239                 if (this.errors == 1) {
240                     if (this.warnings == 1)
241                         errorWarningElement.title = WebInspector.UIString("%d error, %d warning", this.errors, this.warnings);
242                     else
243                         errorWarningElement.title = WebInspector.UIString("%d error, %d warnings", this.errors, this.warnings);
244                 } else if (this.warnings == 1)
245                     errorWarningElement.title = WebInspector.UIString("%d errors, %d warning", this.errors, this.warnings);
246                 else
247                     errorWarningElement.title = WebInspector.UIString("%d errors, %d warnings", this.errors, this.warnings);
248             } else if (this.errors == 1)
249                 errorWarningElement.title = WebInspector.UIString("%d error", this.errors);
250             else
251                 errorWarningElement.title = WebInspector.UIString("%d errors", this.errors);
252         } else if (this.warnings == 1)
253             errorWarningElement.title = WebInspector.UIString("%d warning", this.warnings);
254         else if (this.warnings)
255             errorWarningElement.title = WebInspector.UIString("%d warnings", this.warnings);
256         else
257             errorWarningElement.title = null;
258     }
259 }
260
261 WebInspector.loaded = function()
262 {
263     var platform = InspectorController.platform();
264     document.body.addStyleClass("platform-" + platform);
265
266     this.console = new WebInspector.Console();
267     this.panels = {
268         elements: new WebInspector.ElementsPanel(),
269         resources: new WebInspector.ResourcesPanel(),
270         scripts: new WebInspector.ScriptsPanel(),
271         profiles: new WebInspector.ProfilesPanel(),
272         databases: new WebInspector.DatabasesPanel()
273     };
274
275     var toolbarElement = document.getElementById("toolbar");
276     for (var panelName in this.panels) {
277         var panel = this.panels[panelName];
278         var panelToolbarItem = panel.toolbarItem;
279         panelToolbarItem.addEventListener("click", this._toolbarItemClicked.bind(this));
280         if (previousToolbarItem)
281             toolbarElement.insertBefore(panelToolbarItem, previousToolbarItem.nextSibling);
282         else
283             toolbarElement.insertBefore(panelToolbarItem, toolbarElement.firstChild);
284         var previousToolbarItem = panelToolbarItem;
285     }
286
287     this.currentPanel = this.panels.elements;
288
289     this.resourceCategories = {
290         documents: new WebInspector.ResourceCategory(WebInspector.UIString("Documents"), "documents"),
291         stylesheets: new WebInspector.ResourceCategory(WebInspector.UIString("Stylesheets"), "stylesheets"),
292         images: new WebInspector.ResourceCategory(WebInspector.UIString("Images"), "images"),
293         scripts: new WebInspector.ResourceCategory(WebInspector.UIString("Scripts"), "scripts"),
294         xhr: new WebInspector.ResourceCategory(WebInspector.UIString("XHR"), "xhr"),
295         fonts: new WebInspector.ResourceCategory(WebInspector.UIString("Fonts"), "fonts"),
296         other: new WebInspector.ResourceCategory(WebInspector.UIString("Other"), "other")
297     };
298
299     this.Tips = {
300         ResourceNotCompressed: {id: 0, message: WebInspector.UIString("You could save bandwidth by having your web server compress this transfer with gzip or zlib.")}
301     };
302
303     this.Warnings = {
304         IncorrectMIMEType: {id: 0, message: WebInspector.UIString("Resource interpreted as %s but transferred with MIME type %s.")}
305     };
306
307     this.addMainEventListeners(document);
308
309     window.addEventListener("unload", this.windowUnload.bind(this), true);
310     window.addEventListener("resize", this.windowResize.bind(this), true);
311
312     document.addEventListener("focus", this.focusChanged.bind(this), true);
313     document.addEventListener("keydown", this.documentKeyDown.bind(this), true);
314     document.addEventListener("keyup", this.documentKeyUp.bind(this), true);
315     document.addEventListener("beforecopy", this.documentCanCopy.bind(this), true);
316     document.addEventListener("copy", this.documentCopy.bind(this), true);
317
318     document.getElementById("searchResultsResizer").addEventListener("mousedown", this.searchResultsResizerDragStart, true);
319
320     var mainPanelsElement = document.getElementById("main-panels");
321     mainPanelsElement.handleKeyEvent = this.mainKeyDown.bind(this);
322     mainPanelsElement.handleKeyUpEvent = this.mainKeyUp.bind(this);
323     mainPanelsElement.handleCopyEvent = this.mainCopy.bind(this);
324
325     // Focus the mainPanelsElement in a timeout so it happens after the initial focus,
326     // so it doesn't get reset to the first toolbar button. This initial focus happens
327     // on Mac when the window is made key and the WebHTMLView becomes the first responder.
328     setTimeout(function() { WebInspector.currentFocusElement = mainPanelsElement }, 0);
329
330     var dockToggleButton = document.getElementById("dock-status-bar-item");
331     dockToggleButton.addEventListener("click", this.toggleAttach.bind(this), false);
332
333     if (this.attached)
334         dockToggleButton.title = WebInspector.UIString("Undock into separate window.");
335     else
336         dockToggleButton.title = WebInspector.UIString("Dock to main window.");
337
338     var errorWarningCount = document.getElementById("error-warning-count");
339     errorWarningCount.addEventListener("click", this.console.show.bind(this.console), false);
340     this._updateErrorAndWarningCounts();
341
342     document.getElementById("search-toolbar-label").textContent = WebInspector.UIString("Search");
343     var searchField = document.getElementById("search");
344     searchField.addEventListener("keyup", this.performSearch.bind(this), false);
345
346     if (platform === "mac-leopard")
347         document.getElementById("toolbar").addEventListener("mousedown", this.toolbarDragStart, true);
348
349     InspectorController.loaded();
350 }
351
352 var windowLoaded = function()
353 {
354     var localizedStringsURL = InspectorController.localizedStringsURL();
355     if (localizedStringsURL) {
356         var localizedStringsScriptElement = document.createElement("script");
357         localizedStringsScriptElement.addEventListener("load", WebInspector.loaded.bind(WebInspector), false);
358         localizedStringsScriptElement.type = "text/javascript";
359         localizedStringsScriptElement.src = localizedStringsURL;
360         document.getElementsByTagName("head").item(0).appendChild(localizedStringsScriptElement);
361     } else
362         WebInspector.loaded();
363
364     window.removeEventListener("load", windowLoaded, false);
365     delete windowLoaded;
366 };
367
368 window.addEventListener("load", windowLoaded, false);
369
370 WebInspector.windowUnload = function(event)
371 {
372     InspectorController.windowUnloading();
373 }
374
375 WebInspector.windowResize = function(event)
376 {
377     if (this.currentPanel && this.currentPanel.resize)
378         this.currentPanel.resize();
379 }
380
381 WebInspector.windowFocused = function(event)
382 {
383     if (event.target.nodeType === Node.DOCUMENT_NODE)
384         document.body.removeStyleClass("inactive");
385 }
386
387 WebInspector.windowBlured = function(event)
388 {
389     if (event.target.nodeType === Node.DOCUMENT_NODE)
390         document.body.addStyleClass("inactive");
391 }
392
393 WebInspector.focusChanged = function(event)
394 {
395     this.currentFocusElement = event.target;
396 }
397
398 WebInspector.documentClick = function(event)
399 {
400     var anchor = event.target.enclosingNodeOrSelfWithNodeName("a");
401     if (!anchor)
402         return;
403
404     // Prevent the link from navigating, since we don't do any navigation by following links normally.
405     event.preventDefault();
406
407     function followLink()
408     {
409         // FIXME: support webkit-html-external-link links here.
410         if (anchor.href in WebInspector.resourceURLMap) {
411             if (anchor.hasStyleClass("webkit-html-external-link")) {
412                 anchor.removeStyleClass("webkit-html-external-link");
413                 anchor.addStyleClass("webkit-html-resource-link");
414             }
415
416             WebInspector.showResourceForURL(anchor.href, anchor.lineNumber, anchor.preferredPanel);
417         }
418     }
419
420     if (WebInspector.followLinkTimeout)
421         clearTimeout(WebInspector.followLinkTimeout);
422
423     if (anchor.preventFollowOnDoubleClick) {
424         // Start a timeout if this is the first click, if the timeout is canceled
425         // before it fires, then a double clicked happened or another link was clicked.
426         if (event.detail === 1)
427             WebInspector.followLinkTimeout = setTimeout(followLink, 333);
428         return;
429     }
430
431     followLink();
432 }
433
434 WebInspector.documentKeyDown = function(event)
435 {
436     if (!this.currentFocusElement)
437         return;
438     if (this.currentFocusElement.handleKeyEvent)
439         this.currentFocusElement.handleKeyEvent(event);
440     else if (this.currentFocusElement.id && this.currentFocusElement.id.length && WebInspector[this.currentFocusElement.id + "KeyDown"])
441         WebInspector[this.currentFocusElement.id + "KeyDown"](event);
442
443     if (!event.handled) {
444         switch (event.keyIdentifier) {
445             case "U+001B": // Escape key
446                 this.console.visible = !this.console.visible;
447                 event.preventDefault();
448                 break;
449         }
450     }
451 }
452
453 WebInspector.documentKeyUp = function(event)
454 {
455     if (!this.currentFocusElement || !this.currentFocusElement.handleKeyUpEvent)
456         return;
457     this.currentFocusElement.handleKeyUpEvent(event);
458 }
459
460 WebInspector.documentCanCopy = function(event)
461 {
462     if (!this.currentFocusElement)
463         return;
464     // Calling preventDefault() will say "we support copying, so enable the Copy menu".
465     if (this.currentFocusElement.handleCopyEvent)
466         event.preventDefault();
467     else if (this.currentFocusElement.id && this.currentFocusElement.id.length && WebInspector[this.currentFocusElement.id + "Copy"])
468         event.preventDefault();
469 }
470
471 WebInspector.documentCopy = function(event)
472 {
473     if (!this.currentFocusElement)
474         return;
475     if (this.currentFocusElement.handleCopyEvent)
476         this.currentFocusElement.handleCopyEvent(event);
477     else if (this.currentFocusElement.id && this.currentFocusElement.id.length && WebInspector[this.currentFocusElement.id + "Copy"])
478         WebInspector[this.currentFocusElement.id + "Copy"](event);
479 }
480
481 WebInspector.mainKeyDown = function(event)
482 {
483     if (this.currentPanel && this.currentPanel.handleKeyEvent)
484         this.currentPanel.handleKeyEvent(event);
485 }
486
487 WebInspector.mainKeyUp = function(event)
488 {
489     if (this.currentPanel && this.currentPanel.handleKeyUpEvent)
490         this.currentPanel.handleKeyUpEvent(event);
491 }
492
493 WebInspector.mainCopy = function(event)
494 {
495     if (this.currentPanel && this.currentPanel.handleCopyEvent)
496         this.currentPanel.handleCopyEvent(event);
497 }
498
499 WebInspector.searchResultsKeyDown = function(event)
500 {
501     if (this.searchResultsTree)
502         this.searchResultsTree.handleKeyEvent(event);
503 }
504
505 WebInspector.animateStyle = function(animations, duration, callback, complete)
506 {
507     if (complete === undefined)
508         complete = 0;
509     var slice = (1000 / 30); // 30 frames per second
510
511     var defaultUnit = "px";
512     var propertyUnit = {opacity: ""};
513
514     for (var i = 0; i < animations.length; ++i) {
515         var animation = animations[i];
516         var element = null;
517         var start = null;
518         var current = null;
519         var end = null;
520         for (key in animation) {
521             if (key === "element")
522                 element = animation[key];
523             else if (key === "start")
524                 start = animation[key];
525             else if (key === "current")
526                 current = animation[key];
527             else if (key === "end")
528                 end = animation[key];
529         }
530
531         if (!element || !end)
532             continue;
533
534         var computedStyle = element.ownerDocument.defaultView.getComputedStyle(element);
535         if (!start) {
536             start = {};
537             for (key in end)
538                 start[key] = parseInt(computedStyle.getPropertyValue(key));
539             animation.start = start;
540         } else if (complete == 0)
541             for (key in start)
542                 element.style.setProperty(key, start[key] + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
543
544         if (!current) {
545             current = {};
546             for (key in start)
547                 current[key] = start[key];
548             animation.current = current;
549         }
550
551         function cubicInOut(t, b, c, d)
552         {
553             if ((t/=d/2) < 1) return c/2*t*t*t + b;
554             return c/2*((t-=2)*t*t + 2) + b;
555         }
556
557         var style = element.style;
558         for (key in end) {
559             var startValue = start[key];
560             var currentValue = current[key];
561             var endValue = end[key];
562             if ((complete + slice) < duration) {
563                 var delta = (endValue - startValue) / (duration / slice);
564                 var newValue = cubicInOut(complete, startValue, endValue - startValue, duration);
565                 style.setProperty(key, newValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
566                 current[key] = newValue;
567             } else {
568                 style.setProperty(key, endValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
569             }
570         }
571     }
572
573     if (complete < duration)
574         setTimeout(WebInspector.animateStyle, slice, animations, duration, callback, complete + slice);
575     else if (callback)
576         callback();
577 }
578
579 WebInspector.toggleAttach = function()
580 {
581     this.attached = !this.attached;
582 }
583
584 WebInspector.toolbarDragStart = function(event)
585 {
586     if (WebInspector.attached)
587         return;
588
589     var target = event.target;
590     if (target.hasStyleClass("toolbar-item") && target.hasStyleClass("toggleable"))
591         return;
592
593     var toolbar = document.getElementById("toolbar");
594     if (target !== toolbar && !target.hasStyleClass("toolbar-item"))
595         return;
596
597     toolbar.lastScreenX = event.screenX;
598     toolbar.lastScreenY = event.screenY;
599
600     document.addEventListener("mousemove", WebInspector.toolbarDrag, true);
601     document.addEventListener("mouseup", WebInspector.toolbarDragEnd, true);
602     document.body.style.cursor = "default";
603
604     event.preventDefault();
605 }
606
607 WebInspector.toolbarDragEnd = function(event)
608 {
609     var toolbar = document.getElementById("toolbar");
610     delete toolbar.lastScreenX;
611     delete toolbar.lastScreenY;
612
613     document.removeEventListener("mousemove", WebInspector.toolbarDrag, true);
614     document.removeEventListener("mouseup", WebInspector.toolbarDragEnd, true);
615     document.body.style.removeProperty("cursor");
616
617     event.preventDefault();
618 }
619
620 WebInspector.toolbarDrag = function(event)
621 {
622     var toolbar = document.getElementById("toolbar");
623
624     var x = event.screenX - toolbar.lastScreenX;
625     var y = event.screenY - toolbar.lastScreenY;
626
627     toolbar.lastScreenX = event.screenX;
628     toolbar.lastScreenY = event.screenY;
629
630     // We cannot call window.moveBy here because it restricts the movement of the window
631     // at the edges.
632     InspectorController.moveByUnrestricted(x, y);
633
634     event.preventDefault();
635 }
636
637 WebInspector.searchResultsResizerDragStart = function(event)
638 {
639     WebInspector.elementDragStart(document.getElementById("searchResults"), WebInspector.searchResultsResizerDrag, WebInspector.searchResultsResizerDragEnd, event, "row-resize");
640 }
641
642 WebInspector.searchResultsResizerDragEnd = function(event)
643 {
644     WebInspector.elementDragEnd(event);
645 }
646
647 WebInspector.searchResultsResizerDrag = function(event)
648 {
649     var y = event.pageY - document.getElementById("main").offsetTop;
650     var newHeight = Number.constrain(y, 100, window.innerHeight - 100);
651
652     WebInspector.searchResultsHeight = newHeight;
653
654     document.getElementById("searchResults").style.height = WebInspector.searchResultsHeight + "px";
655     document.getElementById("main-panels").style.top = newHeight + "px";
656     document.getElementById("searchResultsResizer").style.top = (newHeight - 3) + "px";
657
658     event.preventDefault();
659 }
660
661 WebInspector.elementDragStart = function(element, dividerDrag, elementDragEnd, event, cursor) 
662 {
663     if (this._elementDraggingEventListener || this._elementEndDraggingEventListener)
664         this.elementDragEnd(event);
665
666     this._elementDraggingEventListener = dividerDrag;
667     this._elementEndDraggingEventListener = elementDragEnd;
668
669     document.addEventListener("mousemove", dividerDrag, true);
670     document.addEventListener("mouseup", elementDragEnd, true);
671
672     document.body.style.cursor = cursor;
673
674     event.preventDefault();
675 }
676
677 WebInspector.elementDragEnd = function(event)
678 {
679     document.removeEventListener("mousemove", this._elementDraggingEventListener, true);
680     document.removeEventListener("mouseup", this._elementEndDraggingEventListener, true);
681
682     document.body.style.removeProperty("cursor");
683
684     delete this._elementDraggingEventListener;
685     delete this._elementEndDraggingEventListener;
686
687     event.preventDefault();
688 }
689
690 WebInspector.showConsole = function()
691 {
692     this.console.show();
693 }
694
695 WebInspector.showElementsPanel = function()
696 {
697     this.currentPanel = this.panels.elements;
698 }
699
700 WebInspector.showResourcesPanel = function()
701 {
702     this.currentPanel = this.panels.resources;
703 }
704
705 WebInspector.showScriptsPanel = function()
706 {
707     this.currentPanel = this.panels.scripts;
708 }
709
710 WebInspector.showProfilesPanel = function()
711 {
712     this.currentPanel = this.panels.profiles;
713 }
714
715 WebInspector.showDatabasesPanel = function()
716 {
717     this.currentPanel = this.panels.databases;
718 }
719
720 WebInspector.addResource = function(resource)
721 {
722     this.resources.push(resource);
723     this.resourceURLMap[resource.url] = resource;
724
725     if (resource.mainResource) {
726         this.mainResource = resource;
727         this.panels.elements.reset();
728     }
729
730     this.panels.resources.addResource(resource);
731 }
732
733 WebInspector.removeResource = function(resource)
734 {
735     resource.category.removeResource(resource);
736     delete this.resourceURLMap[resource.url];
737
738     var resourcesLength = this.resources.length;
739     for (var i = 0; i < resourcesLength; ++i) {
740         if (this.resources[i] === resource) {
741             this.resources.splice(i, 1);
742             break;
743         }
744     }
745
746     this.panels.resources.removeResource(resource);
747 }
748
749 WebInspector.addDatabase = function(database)
750 {
751     this.panels.databases.addDatabase(database);
752 }
753
754 WebInspector.debuggerAttached = function()
755 {
756     this.panels.scripts.debuggerAttached();
757 }
758
759 WebInspector.debuggerDetached = function()
760 {
761     this.panels.scripts.debuggerDetached();
762 }
763
764 WebInspector.parsedScriptSource = function(sourceID, sourceURL, source, startingLine)
765 {
766     this.panels.scripts.addScript(sourceID, sourceURL, source, startingLine);
767 }
768
769 WebInspector.failedToParseScriptSource = function(sourceURL, source, startingLine, errorLine, errorMessage)
770 {
771     this.panels.scripts.addScript(null, sourceURL, source, startingLine, errorLine, errorMessage);
772 }
773
774 WebInspector.pausedScript = function()
775 {
776     this.panels.scripts.debuggerPaused();
777 }
778
779 WebInspector.populateInterface = function()
780 {
781     for (var panelName in this.panels) {
782         var panel = this.panels[panelName];
783         if ("populateInterface" in panel)
784             panel.populateInterface();
785     }
786 }
787
788 WebInspector.reset = function()
789 {
790     for (var panelName in this.panels) {
791         var panel = this.panels[panelName];
792         if ("reset" in panel)
793             panel.reset();
794     }
795
796     for (var category in this.resourceCategories)
797         this.resourceCategories[category].removeAllResources();
798
799     this.resources = [];
800     this.resourceURLMap = {};
801
802     delete this.mainResource;
803
804     this.console.clearMessages();
805 }
806
807 WebInspector.inspectedWindowCleared = function(inspectedWindow)
808 {
809     this.panels.elements.inspectedWindowCleared(inspectedWindow);
810 }
811
812 WebInspector.resourceURLChanged = function(resource, oldURL)
813 {
814     delete this.resourceURLMap[oldURL];
815     this.resourceURLMap[resource.url] = resource;
816 }
817
818 WebInspector.addMessageToConsole = function(msg)
819 {
820     this.console.addMessage(msg);
821 }
822
823 WebInspector.startGroupInConsole = function()
824 {
825     this.console.startGroup();
826 }
827
828 WebInspector.endGroupInConsole = function()
829 {
830     this.console.endGroup();
831 }
832
833 WebInspector.addProfile = function(profile)
834 {
835     this.panels.profiles.addProfile(profile);
836 }
837
838 WebInspector.drawLoadingPieChart = function(canvas, percent) {
839     var g = canvas.getContext("2d");
840     var darkColor = "rgb(122, 168, 218)";
841     var lightColor = "rgb(228, 241, 251)";
842     var cx = 8;
843     var cy = 8;
844     var r = 7;
845
846     g.beginPath();
847     g.arc(cx, cy, r, 0, Math.PI * 2, false); 
848     g.closePath();
849
850     g.lineWidth = 1;
851     g.strokeStyle = darkColor;
852     g.fillStyle = lightColor;
853     g.fill();
854     g.stroke();
855
856     var startangle = -Math.PI / 2;
857     var endangle = startangle + (percent * Math.PI * 2);
858
859     g.beginPath();
860     g.moveTo(cx, cy);
861     g.arc(cx, cy, r, startangle, endangle, false); 
862     g.closePath();
863
864     g.fillStyle = darkColor;
865     g.fill();
866 }
867
868 WebInspector.updateFocusedNode = function(node)
869 {
870     if (!node)
871         // FIXME: Should we deselect if null is passed in?
872         return;
873
874     this.currentPanel = this.panels.elements;
875     this.panels.elements.focusedDOMNode = node;
876 }
877
878 WebInspector.displayNameForURL = function(url)
879 {
880     var resource = this.resourceURLMap[url];
881     if (resource)
882         return resource.displayName;
883     return url.trimURL(WebInspector.mainResource ? WebInspector.mainResource.domain : "");
884 }
885
886 WebInspector.resourceForURL = function(url)
887 {
888     if (url in this.resourceURLMap)
889         return this.resourceURLMap[url];
890
891     // No direct match found. Search for resources that contain
892     // a substring of the URL.
893     for (var resourceURL in this.resourceURLMap) {
894         if (resourceURL.hasSubstring(url))
895             return this.resourceURLMap[resourceURL];
896     }
897
898     return null;
899 }
900
901 WebInspector.showResourceForURL = function(url, line, preferredPanel)
902 {
903     var resource = this.resourceForURL(url);
904     if (!resource)
905         return false;
906
907     if (preferredPanel && preferredPanel in WebInspector.panels) {
908         var panel = this.panels[preferredPanel];
909         if (!("showResource" in panel))
910             panel = null;
911         else if ("canShowResource" in panel && !panel.canShowResource(resource))
912             panel = null;
913     }
914
915     this.currentPanel = panel || this.panels.resources;
916     this.currentPanel.showResource(resource, line);
917     return true;
918 }
919
920 WebInspector.linkifyStringAsFragment = function(string)
921 {
922     var container = document.createDocumentFragment();
923     var linkStringRegEx = new RegExp("(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}://|www\\.)[\\w$\\-_+*'=\\|/\\\\(){}[\\]%@&#~,:;.!?]{4,}[\\w$\\-_+*=\\|/\\\\({%@&#~]");
924
925     while (string) {
926         var linkString = linkStringRegEx.exec(string);
927         if (!linkString)
928             break;
929
930         linkString = linkString[0];
931         var linkIndex = string.indexOf(linkString);
932         var nonLink = string.substring(0, linkIndex);
933         container.appendChild(document.createTextNode(nonLink));
934         var realURL = (linkString.indexOf("www.") === 0 ? "http://" + linkString : linkString);
935         container.appendChild(WebInspector.linkifyURLAsNode(realURL, linkString, null, (realURL in WebInspector.resourceURLMap)));
936         string = string.substring(linkIndex + linkString.length, string.length);
937     }
938
939     if (string)
940         container.appendChild(document.createTextNode(string));
941     
942     return container;
943 }
944
945 WebInspector.linkifyURLAsNode = function(url, linkText, classes, isExternal)
946 {
947     if (!linkText)
948         linkText = url;
949     classes = (classes ? classes + " " : "");
950     classes += isExternal ? "webkit-html-external-link" : "webkit-html-resource-link";
951
952     var a = document.createElement("a");
953     a.href = url;
954     a.className = classes;
955     a.title = url;
956     a.target = "_blank";
957     a.textContent = linkText;
958     
959     return a;
960 }
961
962 WebInspector.linkifyURL = function(url, linkText, classes, isExternal)
963 {
964     if (!linkText)
965         linkText = url.escapeHTML();
966     classes = (classes ? classes + " " : "");
967     classes += isExternal ? "webkit-html-external-link" : "webkit-html-resource-link";
968     var link = "<a href=\"" + url + "\" class=\"" + classes + "\" title=\"" + url + "\" target=\"_blank\">" + linkText + "</a>";
969     return link;
970 }
971
972 WebInspector.addMainEventListeners = function(doc)
973 {
974     doc.defaultView.addEventListener("focus", this.windowFocused.bind(this), true);
975     doc.defaultView.addEventListener("blur", this.windowBlured.bind(this), true);
976     doc.addEventListener("click", this.documentClick.bind(this), true);
977 }
978
979 WebInspector.performSearch = function(event)
980 {
981     var query = event.target.value;
982
983     if (!query || !query.length) {
984         this.showingSearchResults = false;
985         return;
986     }
987
988     var forceSearch = event.keyIdentifier === "Enter";
989     if(!forceSearch && query.length < 3)
990         return;
991
992     if (!forceSearch && this.lastQuery && this.lastQuery === query)
993         return;
994     this.lastQuery = query;
995
996     var resultsContainer = document.getElementById("searchResults");
997     resultsContainer.removeChildren();
998
999     var isXPath = query.indexOf("/") !== -1;
1000
1001     var xpathQuery;
1002     if (isXPath)
1003         xpathQuery = query;
1004     else {
1005         var escapedQuery = query.escapeCharacters("'");
1006         xpathQuery = "//*[contains(name(),'" + escapedQuery + "') or contains(@*,'" + escapedQuery + "')] | //text()[contains(.,'" + escapedQuery + "')] | //comment()[contains(.,'" + escapedQuery + "')]";
1007     }
1008
1009     var resourcesToSearch = [].concat(this.resourceCategories.documents.resources, this.resourceCategories.stylesheets.resources, this.resourceCategories.scripts.resources, this.resourceCategories.other.resources);
1010
1011     var files = [];
1012     for (var i = 0; i < resourcesToSearch.length; ++i) {
1013         var resource = resourcesToSearch[i];
1014
1015         var sourceResults = [];
1016         if (!isXPath) {
1017             var sourceFrame = this.panels.resources.sourceFrameForResource(resource);
1018             if (sourceFrame)
1019                 sourceResults = InspectorController.search(sourceFrame.element.contentDocument, query);
1020         }
1021
1022         var domResults = [];
1023         const searchResultsProperty = "__includedInInspectorSearchResults";
1024         function addNodesToDOMResults(nodes, length, getItem)
1025         {
1026             for (var i = 0; i < length; ++i) {
1027                 var node = getItem(nodes, i);
1028                 if (searchResultsProperty in node)
1029                     continue;
1030                 node[searchResultsProperty] = true;
1031                 domResults.push(node);
1032             }
1033         }
1034
1035         function cleanUpDOMResultsNodes()
1036         {
1037             for (var i = 0; i < domResults.length; ++i)
1038                 delete domResults[i][searchResultsProperty];
1039         }
1040
1041         if (resource.category === this.resourceCategories.documents) {
1042             var doc = resource.documentNode;
1043             try {
1044                 var result = InspectorController.inspectedWindow().Document.prototype.evaluate.call(doc, xpathQuery, doc, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE);
1045                 addNodesToDOMResults(result, result.snapshotLength, function(l, i) { return l.snapshotItem(i); });
1046             } catch(err) {
1047                 // ignore any exceptions. the query might be malformed, but we allow that.
1048             }
1049
1050             try {
1051                 var result = InspectorController.inspectedWindow().Document.prototype.querySelectorAll.call(doc, query);
1052                 addNodesToDOMResults(result, result.length, function(l, i) { return l.item(i); });
1053             } catch(err) {
1054                 // ignore any exceptions. the query isn't necessarily a valid selector.
1055             }
1056
1057             cleanUpDOMResultsNodes();
1058         }
1059
1060         if ((!sourceResults || !sourceResults.length) && !domResults.length)
1061             continue;
1062
1063         files.push({resource: resource, sourceResults: sourceResults, domResults: domResults});
1064     }
1065
1066     if (!files.length)
1067         return;
1068
1069     this.showingSearchResults = true;
1070
1071     var fileList = document.createElement("ol");
1072     fileList.className = "outline-disclosure";
1073     resultsContainer.appendChild(fileList);
1074
1075     this.searchResultsTree = new TreeOutline(fileList);
1076     this.searchResultsTree.expandTreeElementsWhenArrowing = true;
1077
1078     var sourceResultSelected = function(element)
1079     {
1080         var selection = window.getSelection();
1081         selection.removeAllRanges();
1082         selection.addRange(element.representedObject.range);
1083
1084         var oldFocusElement = this.currentFocusElement;
1085         this.currentPanel = this.panels.resources;
1086         this.currentFocusElement = oldFocusElement;
1087
1088         this.panels.resources.showResource(element.representedObject.resource);
1089
1090         element.representedObject.line.scrollIntoViewIfNeeded(true);
1091         element.listItemElement.scrollIntoViewIfNeeded(false);
1092     }
1093
1094     var domResultSelected = function(element)
1095     {
1096         var oldFocusElement = this.currentFocusElement;
1097         this.currentPanel = this.panels.elements;
1098         this.currentFocusElement = oldFocusElement;
1099
1100         this.panels.elements.focusedDOMNode = element.representedObject.node;
1101         element.listItemElement.scrollIntoViewIfNeeded(false);
1102     }
1103
1104     for (var i = 0; i < files.length; ++i) {
1105         var file = files[i];
1106
1107         var fileItem = new TreeElement(file.resource.displayName, {}, true);
1108         fileItem.expanded = true;
1109         fileItem.selectable = false;
1110         this.searchResultsTree.appendChild(fileItem);
1111
1112         if (file.sourceResults && file.sourceResults.length) {
1113             for (var j = 0; j < file.sourceResults.length; ++j) {
1114                 var range = file.sourceResults[j];
1115                 var sourceDocument = range.startContainer.ownerDocument;
1116
1117                 var line = range.startContainer;
1118                 while (line.parentNode && line.nodeName.toLowerCase() != "tr")
1119                     line = line.parentNode;
1120                 var lineRange = sourceDocument.createRange();
1121                 lineRange.selectNodeContents(line);
1122
1123                 // Don't include any error bubbles in the search result
1124                 var end = line.lastChild.lastChild;
1125                 if (end.nodeName.toLowerCase() == "div" && end.hasStyleClass("webkit-html-message-bubble")) {
1126                     while (end && end.nodeName.toLowerCase() == "div" && end.hasStyleClass("webkit-html-message-bubble"))
1127                         end = end.previousSibling;
1128                     lineRange.setEndAfter(end);
1129                 }
1130
1131                 var beforeRange = sourceDocument.createRange();
1132                 beforeRange.setStart(lineRange.startContainer, lineRange.startOffset);
1133                 beforeRange.setEnd(range.startContainer, range.startOffset);
1134
1135                 var afterRange = sourceDocument.createRange();
1136                 afterRange.setStart(range.endContainer, range.endOffset);
1137                 afterRange.setEnd(lineRange.endContainer, lineRange.endOffset);
1138
1139                 var beforeText = beforeRange.toString().trimLeadingWhitespace();
1140                 var text = range.toString();
1141                 var afterText = afterRange.toString().trimTrailingWhitespace();
1142
1143                 var length = beforeText.length + text.length + afterText.length;
1144                 if (length > Preferences.maxTextSearchResultLength) {
1145                     var beforeAfterLength = (Preferences.maxTextSearchResultLength - text.length) / 2;
1146                     if (beforeText.length > beforeAfterLength)
1147                         beforeText = "\u2026" + beforeText.substr(-beforeAfterLength);
1148                     if (afterText.length > beforeAfterLength)
1149                         afterText = afterText.substr(0, beforeAfterLength) + "\u2026";
1150                 }
1151
1152                 var title = "<div class=\"selection selected\"></div>";
1153                 if (j == 0)
1154                     title += "<div class=\"search-results-section\">" + WebInspector.UIString("Source") + "</div>";
1155                 title += beforeText.escapeHTML() + "<span class=\"search-matched-string\">" + text.escapeHTML() + "</span>" + afterText.escapeHTML();
1156                 var item = new TreeElement(title, {resource: file.resource, line: line, range: range}, false);
1157                 item.onselect = sourceResultSelected.bind(this);
1158                 fileItem.appendChild(item);
1159             }
1160         }
1161
1162         if (file.domResults.length) {
1163             for (var j = 0; j < file.domResults.length; ++j) {
1164                 var node = file.domResults[j];
1165                 var title = "<div class=\"selection selected\"></div>";
1166                 if (j == 0)
1167                     title += "<div class=\"search-results-section\">" + WebInspector.UIString("DOM") + "</div>";
1168                 title += nodeTitleInfo.call(node).title;
1169                 var item = new TreeElement(title, {resource: file.resource, node: node}, false);
1170                 item.onselect = domResultSelected.bind(this);
1171                 fileItem.appendChild(item);
1172             }
1173         }
1174     }
1175 }
1176
1177 WebInspector.UIString = function(string)
1178 {
1179     if (window.localizedStrings && string in window.localizedStrings)
1180         string = window.localizedStrings[string];
1181     else {
1182         if (!(string in this.missingLocalizedStrings)) {
1183             console.error("Localized string \"" + string + "\" not found.");
1184             this.missingLocalizedStrings[string] = true;
1185         }
1186
1187         if (Preferences.showMissingLocalizedStrings)
1188             string += " (not localized)";
1189     }
1190
1191     return String.vsprintf(string, Array.prototype.slice.call(arguments, 1));
1192 }
1193
1194 WebInspector.isBeingEdited = function(element)
1195 {
1196     return element.__editing;
1197 }
1198
1199 WebInspector.startEditing = function(element, committedCallback, cancelledCallback, context)
1200 {
1201     if (element.__editing)
1202         return;
1203     element.__editing = true;
1204
1205     var oldText = element.textContent;
1206     var oldHandleKeyEvent = element.handleKeyEvent;
1207
1208     element.addStyleClass("editing");
1209
1210     var oldTabIndex = element.tabIndex;
1211     if (element.tabIndex < 0)
1212         element.tabIndex = 0;
1213
1214     function blurEventListener() {
1215         editingCommitted.call(element);
1216     }
1217
1218     function cleanUpAfterEditing() {
1219         delete this.__editing;
1220
1221         this.removeStyleClass("editing");
1222         this.tabIndex = oldTabIndex;
1223         this.scrollTop = 0;
1224         this.scrollLeft = 0;
1225
1226         this.handleKeyEvent = oldHandleKeyEvent;
1227         element.removeEventListener("blur", blurEventListener, false);
1228
1229         if (element === WebInspector.currentFocusElement || element.isAncestor(WebInspector.currentFocusElement))
1230             WebInspector.currentFocusElement = WebInspector.previousFocusElement;
1231     }
1232
1233     function editingCancelled() {
1234         this.innerText = oldText;
1235
1236         cleanUpAfterEditing.call(this);
1237
1238         cancelledCallback(this, context);
1239     }
1240
1241     function editingCommitted() {
1242         cleanUpAfterEditing.call(this);
1243
1244         committedCallback(this, this.textContent, oldText, context);
1245     }
1246
1247     element.handleKeyEvent = function(event) {
1248         if (oldHandleKeyEvent)
1249             oldHandleKeyEvent(event);
1250         if (event.handled)
1251             return;
1252
1253         if (event.keyIdentifier === "Enter") {
1254             editingCommitted.call(element);
1255             event.preventDefault();
1256         } else if (event.keyCode === 27) { // Escape key
1257             editingCancelled.call(element);
1258             event.preventDefault();
1259             event.handled = true;
1260         }
1261     }
1262
1263     element.addEventListener("blur", blurEventListener, false);
1264
1265     WebInspector.currentFocusElement = element;
1266 }
1267
1268 WebInspector._toolbarItemClicked = function(event)
1269 {
1270     var toolbarItem = event.currentTarget;
1271     this.currentPanel = toolbarItem.panel;
1272 }
1273
1274 // This table maps MIME types to the Resource.Types which are valid for them.
1275 // The following line:
1276 //    "text/html":                {0: 1},
1277 // means that text/html is a valid MIME type for resources that have type
1278 // WebInspector.Resource.Type.Document (which has a value of 0).
1279 WebInspector.MIMETypes = {
1280     "text/html":                   {0: true},
1281     "text/xml":                    {0: true},
1282     "text/plain":                  {0: true},
1283     "application/xhtml+xml":       {0: true},
1284     "text/css":                    {1: true},
1285     "text/xsl":                    {1: true},
1286     "image/jpeg":                  {2: true},
1287     "image/png":                   {2: true},
1288     "image/gif":                   {2: true},
1289     "image/bmp":                   {2: true},
1290     "image/x-icon":                {2: true},
1291     "image/x-xbitmap":             {2: true},
1292     "font/ttf":                    {3: true},
1293     "font/opentype":               {3: true},
1294     "application/x-font-type1":    {3: true},
1295     "application/x-font-ttf":      {3: true},
1296     "application/x-truetype-font": {3: true},
1297     "text/javascript":             {4: true},
1298     "text/ecmascript":             {4: true},
1299     "application/javascript":      {4: true},
1300     "application/ecmascript":      {4: true},
1301     "application/x-javascript":    {4: true},
1302     "text/javascript1.1":          {4: true},
1303     "text/javascript1.2":          {4: true},
1304     "text/javascript1.3":          {4: true},
1305     "text/jscript":                {4: true},
1306     "text/livescript":             {4: true},
1307 }