2fd076c0a15e9122545ed0da62437a2660676f0e
[WebKit-https.git] / WebCore / inspector / front-end / inspector.js
1 /*
2  * Copyright (C) 2006, 2007, 2008 Apple Inc.  All rights reserved.
3  * Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com).
4  * Copyright (C) 2009 Joseph Pecoraro
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1.  Redistributions of source code must retain the above copyright
11  *     notice, this list of conditions and the following disclaimer.
12  * 2.  Redistributions in binary form must reproduce the above copyright
13  *     notice, this list of conditions and the following disclaimer in the
14  *     documentation and/or other materials provided with the distribution.
15  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
16  *     its contributors may be used to endorse or promote products derived
17  *     from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 var Preferences = {
32     showUserAgentStyles: true,
33     maxInlineTextChildLength: 80,
34     minConsoleHeight: 75,
35     minSidebarWidth: 100,
36     minElementsSidebarWidth: 200,
37     minScriptsSidebarWidth: 200,
38     showInheritedComputedStyleProperties: false,
39     styleRulesExpandedState: {},
40     showMissingLocalizedStrings: false,
41     heapProfilerPresent: false,
42     samplingCPUProfiler: false,
43     showColorNicknames: true,
44     colorFormat: "hex",
45     eventListenersFilter: "all",
46     resourcesLargeRows: true
47 }
48
49 function preloadImages()
50 {
51     (new Image()).src = "Images/clearConsoleButtonGlyph.png";
52     (new Image()).src = "Images/consoleButtonGlyph.png";
53     (new Image()).src = "Images/dockButtonGlyph.png";
54     (new Image()).src = "Images/enableOutlineButtonGlyph.png";
55     (new Image()).src = "Images/enableSolidButtonGlyph.png";
56     (new Image()).src = "Images/excludeButtonGlyph.png";
57     (new Image()).src = "Images/focusButtonGlyph.png";
58     (new Image()).src = "Images/largerResourcesButtonGlyph.png";
59     (new Image()).src = "Images/nodeSearchButtonGlyph.png";
60     (new Image()).src = "Images/pauseOnExceptionButtonGlyph.png";
61     (new Image()).src = "Images/percentButtonGlyph.png";
62     (new Image()).src = "Images/recordButtonGlyph.png";
63     (new Image()).src = "Images/recordToggledButtonGlyph.png";
64     (new Image()).src = "Images/reloadButtonGlyph.png";
65     (new Image()).src = "Images/undockButtonGlyph.png";
66 }
67
68 preloadImages();
69
70 var WebInspector = {
71     resources: {},
72     resourceURLMap: {},
73     cookieDomains: {},
74     missingLocalizedStrings: {},
75     pendingDispatches: 0,
76
77     get platform()
78     {
79         if (!("_platform" in this))
80             this._platform = InspectorFrontendHost.platform();
81
82         return this._platform;
83     },
84
85     get port()
86     {
87         if (!("_port" in this))
88             this._port = InspectorFrontendHost.port();
89
90         return this._port;
91     },
92
93     get previousFocusElement()
94     {
95         return this._previousFocusElement;
96     },
97
98     get currentFocusElement()
99     {
100         return this._currentFocusElement;
101     },
102
103     set currentFocusElement(x)
104     {
105         if (this._currentFocusElement !== x)
106             this._previousFocusElement = this._currentFocusElement;
107         this._currentFocusElement = x;
108
109         if (this._currentFocusElement) {
110             this._currentFocusElement.focus();
111
112             // Make a caret selection inside the new element if there isn't a range selection and
113             // there isn't already a caret selection inside.
114             var selection = window.getSelection();
115             if (selection.isCollapsed && !this._currentFocusElement.isInsertionCaretInside()) {
116                 var selectionRange = this._currentFocusElement.ownerDocument.createRange();
117                 selectionRange.setStart(this._currentFocusElement, 0);
118                 selectionRange.setEnd(this._currentFocusElement, 0);
119
120                 selection.removeAllRanges();
121                 selection.addRange(selectionRange);
122             }
123         } else if (this._previousFocusElement)
124             this._previousFocusElement.blur();
125     },
126
127     get currentPanel()
128     {
129         return this._currentPanel;
130     },
131
132     set currentPanel(x)
133     {
134         if (this._currentPanel === x)
135             return;
136
137         if (this._currentPanel)
138             this._currentPanel.hide();
139
140         this._currentPanel = x;
141
142         this.updateSearchLabel();
143
144         if (x) {
145             x.show();
146
147             if (this.currentQuery) {
148                 if (x.performSearch) {
149                     function performPanelSearch()
150                     {
151                         this.updateSearchMatchesCount();
152
153                         x.currentQuery = this.currentQuery;
154                         x.performSearch(this.currentQuery);
155                     }
156
157                     // Perform the search on a timeout so the panel switches fast.
158                     setTimeout(performPanelSearch.bind(this), 0);
159                 } else {
160                     // Update to show Not found for panels that can't be searched.
161                     this.updateSearchMatchesCount();
162                 }
163             }
164         }
165
166         for (var panelName in WebInspector.panels) {
167             if (WebInspector.panels[panelName] == x)
168                 InspectorBackend.storeLastActivePanel(panelName);
169         }
170     },
171
172     _createPanels: function()
173     {
174         var hiddenPanels = (InspectorFrontendHost.hiddenPanels() || "").split(',');
175         if (hiddenPanels.indexOf("elements") === -1)
176             this.panels.elements = new WebInspector.ElementsPanel();
177         if (hiddenPanels.indexOf("resources") === -1)
178             this.panels.resources = new WebInspector.ResourcesPanel();
179         if (hiddenPanels.indexOf("scripts") === -1)
180             this.panels.scripts = new WebInspector.ScriptsPanel();
181         if (hiddenPanels.indexOf("timeline") === -1)
182             this.panels.timeline = new WebInspector.TimelinePanel();
183         if (hiddenPanels.indexOf("profiles") === -1) {
184             this.panels.profiles = new WebInspector.ProfilesPanel();
185             this.panels.profiles.registerProfileType(new WebInspector.CPUProfileType());
186         }
187
188         if (hiddenPanels.indexOf("storage") === -1 && hiddenPanels.indexOf("databases") === -1)
189             this.panels.storage = new WebInspector.StoragePanel();
190         if (hiddenPanels.indexOf("console") === -1)
191             this.panels.console = new WebInspector.ConsolePanel();
192     },
193
194     _loadPreferences: function()
195     {
196         var colorFormat = InspectorFrontendHost.setting("color-format");
197         if (colorFormat)
198             Preferences.colorFormat = colorFormat;
199
200         var eventListenersFilter = InspectorFrontendHost.setting("event-listeners-filter");
201         if (eventListenersFilter)
202             Preferences.eventListenersFilter = eventListenersFilter;
203
204         var resourcesLargeRows = InspectorFrontendHost.setting("resources-large-rows");
205         if (typeof resourcesLargeRows !== "undefined")
206             Preferences.resourcesLargeRows = resourcesLargeRows;
207     },
208
209     get attached()
210     {
211         return this._attached;
212     },
213
214     set attached(x)
215     {
216         if (this._attached === x)
217             return;
218
219         this._attached = x;
220
221         this.updateSearchLabel();
222
223         var dockToggleButton = document.getElementById("dock-status-bar-item");
224         var body = document.body;
225
226         if (x) {
227             InspectorFrontendHost.attach();
228             body.removeStyleClass("detached");
229             body.addStyleClass("attached");
230             dockToggleButton.title = WebInspector.UIString("Undock into separate window.");
231         } else {
232             InspectorFrontendHost.detach();
233             body.removeStyleClass("attached");
234             body.addStyleClass("detached");
235             dockToggleButton.title = WebInspector.UIString("Dock to main window.");
236         }
237     },
238
239     get errors()
240     {
241         return this._errors || 0;
242     },
243
244     set errors(x)
245     {
246         x = Math.max(x, 0);
247
248         if (this._errors === x)
249             return;
250         this._errors = x;
251         this._updateErrorAndWarningCounts();
252     },
253
254     get warnings()
255     {
256         return this._warnings || 0;
257     },
258
259     set warnings(x)
260     {
261         x = Math.max(x, 0);
262
263         if (this._warnings === x)
264             return;
265         this._warnings = x;
266         this._updateErrorAndWarningCounts();
267     },
268
269     _updateErrorAndWarningCounts: function()
270     {
271         var errorWarningElement = document.getElementById("error-warning-count");
272         if (!errorWarningElement)
273             return;
274
275         if (!this.errors && !this.warnings) {
276             errorWarningElement.addStyleClass("hidden");
277             return;
278         }
279
280         errorWarningElement.removeStyleClass("hidden");
281
282         errorWarningElement.removeChildren();
283
284         if (this.errors) {
285             var errorElement = document.createElement("span");
286             errorElement.id = "error-count";
287             errorElement.textContent = this.errors;
288             errorWarningElement.appendChild(errorElement);
289         }
290
291         if (this.warnings) {
292             var warningsElement = document.createElement("span");
293             warningsElement.id = "warning-count";
294             warningsElement.textContent = this.warnings;
295             errorWarningElement.appendChild(warningsElement);
296         }
297
298         if (this.errors) {
299             if (this.warnings) {
300                 if (this.errors == 1) {
301                     if (this.warnings == 1)
302                         errorWarningElement.title = WebInspector.UIString("%d error, %d warning", this.errors, this.warnings);
303                     else
304                         errorWarningElement.title = WebInspector.UIString("%d error, %d warnings", this.errors, this.warnings);
305                 } else if (this.warnings == 1)
306                     errorWarningElement.title = WebInspector.UIString("%d errors, %d warning", this.errors, this.warnings);
307                 else
308                     errorWarningElement.title = WebInspector.UIString("%d errors, %d warnings", this.errors, this.warnings);
309             } else if (this.errors == 1)
310                 errorWarningElement.title = WebInspector.UIString("%d error", this.errors);
311             else
312                 errorWarningElement.title = WebInspector.UIString("%d errors", this.errors);
313         } else if (this.warnings == 1)
314             errorWarningElement.title = WebInspector.UIString("%d warning", this.warnings);
315         else if (this.warnings)
316             errorWarningElement.title = WebInspector.UIString("%d warnings", this.warnings);
317         else
318             errorWarningElement.title = null;
319     },
320
321     get styleChanges()
322     {
323         return this._styleChanges;
324     },
325
326     set styleChanges(x)
327     {
328         x = Math.max(x, 0);
329
330         if (this._styleChanges === x)
331             return;
332         this._styleChanges = x;
333         this._updateChangesCount();
334     },
335
336     _updateChangesCount: function()
337     {
338         // TODO: Remove immediate return when enabling the Changes Panel
339         return;
340
341         var changesElement = document.getElementById("changes-count");
342         if (!changesElement)
343             return;
344
345         if (!this.styleChanges) {
346             changesElement.addStyleClass("hidden");
347             return;
348         }
349
350         changesElement.removeStyleClass("hidden");
351         changesElement.removeChildren();
352
353         if (this.styleChanges) {
354             var styleChangesElement = document.createElement("span");
355             styleChangesElement.id = "style-changes-count";
356             styleChangesElement.textContent = this.styleChanges;
357             changesElement.appendChild(styleChangesElement);
358         }
359
360         if (this.styleChanges) {
361             if (this.styleChanges === 1)
362                 changesElement.title = WebInspector.UIString("%d style change", this.styleChanges);
363             else
364                 changesElement.title = WebInspector.UIString("%d style changes", this.styleChanges);
365         }
366     },
367
368     get hoveredDOMNode()
369     {
370         return this._hoveredDOMNode;
371     },
372
373     set hoveredDOMNode(x)
374     {
375         if (this._hoveredDOMNode === x)
376             return;
377
378         this._hoveredDOMNode = x;
379
380         if (this._hoveredDOMNode)
381             this._updateHoverHighlightSoon(this.showingDOMNodeHighlight ? 50 : 500);
382         else
383             this._updateHoverHighlight();
384     },
385
386     _updateHoverHighlightSoon: function(delay)
387     {
388         if ("_updateHoverHighlightTimeout" in this)
389             clearTimeout(this._updateHoverHighlightTimeout);
390         this._updateHoverHighlightTimeout = setTimeout(this._updateHoverHighlight.bind(this), delay);
391     },
392
393     _updateHoverHighlight: function()
394     {
395         if ("_updateHoverHighlightTimeout" in this) {
396             clearTimeout(this._updateHoverHighlightTimeout);
397             delete this._updateHoverHighlightTimeout;
398         }
399
400         if (this._hoveredDOMNode) {
401             InspectorBackend.highlightDOMNode(this._hoveredDOMNode.id);
402             this.showingDOMNodeHighlight = true;
403         } else {
404             InspectorBackend.hideDOMNodeHighlight();
405             this.showingDOMNodeHighlight = false;
406         }
407     }
408 }
409
410 WebInspector.loaded = function()
411 {
412     var platform = WebInspector.platform;
413     document.body.addStyleClass("platform-" + platform);
414     var port = WebInspector.port;
415     document.body.addStyleClass("port-" + port);
416
417     this._loadPreferences();
418
419     this.drawer = new WebInspector.Drawer();
420     this.console = new WebInspector.ConsoleView(this.drawer);
421     // TODO: Uncomment when enabling the Changes Panel
422     // this.changes = new WebInspector.ChangesView(this.drawer);
423     // TODO: Remove class="hidden" from inspector.html on button#changes-status-bar-item
424     this.drawer.visibleView = this.console;
425     this.domAgent = new WebInspector.DOMAgent();
426
427     this.resourceCategories = {
428         documents: new WebInspector.ResourceCategory("documents", WebInspector.UIString("Documents"), "rgb(47,102,236)"),
429         stylesheets: new WebInspector.ResourceCategory("stylesheets", WebInspector.UIString("Stylesheets"), "rgb(157,231,119)"),
430         images: new WebInspector.ResourceCategory("images", WebInspector.UIString("Images"), "rgb(164,60,255)"),
431         scripts: new WebInspector.ResourceCategory("scripts", WebInspector.UIString("Scripts"), "rgb(255,121,0)"),
432         xhr: new WebInspector.ResourceCategory("xhr", WebInspector.UIString("XHR"), "rgb(231,231,10)"),
433         fonts: new WebInspector.ResourceCategory("fonts", WebInspector.UIString("Fonts"), "rgb(255,82,62)"),
434         other: new WebInspector.ResourceCategory("other", WebInspector.UIString("Other"), "rgb(186,186,186)")
435     };
436
437     this.panels = {};
438     this._createPanels();
439
440     var toolbarElement = document.getElementById("toolbar");
441     var previousToolbarItem = toolbarElement.children[0];
442
443     this.panelOrder = [];
444     for (var panelName in this.panels) {
445         var panel = this.panels[panelName];
446         var panelToolbarItem = panel.toolbarItem;
447         this.panelOrder.push(panel);
448         panelToolbarItem.addEventListener("click", this._toolbarItemClicked.bind(this));
449         if (previousToolbarItem)
450             toolbarElement.insertBefore(panelToolbarItem, previousToolbarItem.nextSibling);
451         else
452             toolbarElement.insertBefore(panelToolbarItem, toolbarElement.firstChild);
453         previousToolbarItem = panelToolbarItem;
454     }
455
456     this.Tips = {
457         ResourceNotCompressed: {id: 0, message: WebInspector.UIString("You could save bandwidth by having your web server compress this transfer with gzip or zlib.")}
458     };
459
460     this.Warnings = {
461         IncorrectMIMEType: {id: 0, message: WebInspector.UIString("Resource interpreted as %s but transferred with MIME type %s.")}
462     };
463
464     this.addMainEventListeners(document);
465
466     window.addEventListener("unload", this.windowUnload.bind(this), true);
467     window.addEventListener("resize", this.windowResize.bind(this), true);
468
469     document.addEventListener("focus", this.focusChanged.bind(this), true);
470     document.addEventListener("keydown", this.documentKeyDown.bind(this), true);
471     document.addEventListener("keyup", this.documentKeyUp.bind(this), true);
472     document.addEventListener("beforecopy", this.documentCanCopy.bind(this), true);
473     document.addEventListener("copy", this.documentCopy.bind(this), true);
474     document.addEventListener("contextmenu", this.contextMenuEventFired.bind(this), true);
475
476     var mainPanelsElement = document.getElementById("main-panels");
477     mainPanelsElement.handleKeyEvent = this.mainKeyDown.bind(this);
478     mainPanelsElement.handleKeyUpEvent = this.mainKeyUp.bind(this);
479     mainPanelsElement.handleCopyEvent = this.mainCopy.bind(this);
480
481     // Focus the mainPanelsElement in a timeout so it happens after the initial focus,
482     // so it doesn't get reset to the first toolbar button. This initial focus happens
483     // on Mac when the window is made key and the WebHTMLView becomes the first responder.
484     setTimeout(function() { WebInspector.currentFocusElement = mainPanelsElement }, 0);
485
486     var dockToggleButton = document.getElementById("dock-status-bar-item");
487     dockToggleButton.addEventListener("click", this.toggleAttach.bind(this), false);
488
489     if (this.attached)
490         dockToggleButton.title = WebInspector.UIString("Undock into separate window.");
491     else
492         dockToggleButton.title = WebInspector.UIString("Dock to main window.");
493
494     var errorWarningCount = document.getElementById("error-warning-count");
495     errorWarningCount.addEventListener("click", this.showConsole.bind(this), false);
496     this._updateErrorAndWarningCounts();
497
498     this.styleChanges = 0;
499     // TODO: Uncomment when enabling the Changes Panel
500     // var changesElement = document.getElementById("changes-count");
501     // changesElement.addEventListener("click", this.showChanges.bind(this), false);
502     // this._updateErrorAndWarningCounts();
503
504     var searchField = document.getElementById("search");
505     searchField.addEventListener("search", this.performSearch.bind(this), false); // when the search is emptied
506     searchField.addEventListener("mousedown", this.searchFieldManualFocus.bind(this), false); // when the search field is manually selected
507
508     toolbarElement.addEventListener("mousedown", this.toolbarDragStart, true);
509     document.getElementById("close-button-left").addEventListener("click", this.close, true);
510     document.getElementById("close-button-right").addEventListener("click", this.close, true);
511
512     InspectorFrontendHost.loaded();
513 }
514
515 var windowLoaded = function()
516 {
517     var localizedStringsURL = InspectorFrontendHost.localizedStringsURL();
518     if (localizedStringsURL) {
519         var localizedStringsScriptElement = document.createElement("script");
520         localizedStringsScriptElement.addEventListener("load", WebInspector.loaded.bind(WebInspector), false);
521         localizedStringsScriptElement.type = "text/javascript";
522         localizedStringsScriptElement.src = localizedStringsURL;
523         document.head.appendChild(localizedStringsScriptElement);
524     } else
525         WebInspector.loaded();
526
527     window.removeEventListener("load", windowLoaded, false);
528     delete windowLoaded;
529 };
530
531 window.addEventListener("load", windowLoaded, false);
532
533 WebInspector.dispatch = function() {
534     var methodName = arguments[0];
535     var parameters = Array.prototype.slice.call(arguments, 1);
536
537     // We'd like to enforce asynchronous interaction between the inspector controller and the frontend.
538     // This is important to LayoutTests.
539     function delayDispatch()
540     {
541         WebInspector[methodName].apply(WebInspector, parameters);
542         WebInspector.pendingDispatches--;
543     }
544     WebInspector.pendingDispatches++;
545     setTimeout(delayDispatch, 0);
546 }
547
548 WebInspector.windowUnload = function(event)
549 {
550     InspectorFrontendHost.windowUnloading();
551 }
552
553 WebInspector.windowResize = function(event)
554 {
555     if (this.currentPanel && this.currentPanel.resize)
556         this.currentPanel.resize();
557     this.drawer.resize();
558 }
559
560 WebInspector.windowFocused = function(event)
561 {
562     // Fires after blur, so when focusing on either the main inspector
563     // or an <iframe> within the inspector we should always remove the
564     // "inactive" class.
565     if (event.target.document.nodeType === Node.DOCUMENT_NODE)
566         document.body.removeStyleClass("inactive");
567 }
568
569 WebInspector.windowBlurred = function(event)
570 {
571     // Leaving the main inspector or an <iframe> within the inspector.
572     // We can add "inactive" now, and if we are moving the focus to another
573     // part of the inspector then windowFocused will correct this.
574     if (event.target.document.nodeType === Node.DOCUMENT_NODE)
575         document.body.addStyleClass("inactive");
576 }
577
578 WebInspector.focusChanged = function(event)
579 {
580     this.currentFocusElement = event.target;
581 }
582
583 WebInspector.setAttachedWindow = function(attached)
584 {
585     this.attached = attached;
586 }
587
588 WebInspector.close = function(event)
589 {
590     InspectorFrontendHost.closeWindow();
591 }
592
593 WebInspector.documentClick = function(event)
594 {
595     var anchor = event.target.enclosingNodeOrSelfWithNodeName("a");
596     if (!anchor)
597         return;
598
599     // Prevent the link from navigating, since we don't do any navigation by following links normally.
600     event.preventDefault();
601
602     function followLink()
603     {
604         // FIXME: support webkit-html-external-link links here.
605         if (anchor.href in WebInspector.resourceURLMap) {
606             if (anchor.hasStyleClass("webkit-html-external-link")) {
607                 anchor.removeStyleClass("webkit-html-external-link");
608                 anchor.addStyleClass("webkit-html-resource-link");
609             }
610
611             WebInspector.showResourceForURL(anchor.href, anchor.lineNumber, anchor.preferredPanel);
612         } else {
613             var profileString = WebInspector.ProfileType.URLRegExp.exec(anchor.href);
614             if (profileString)
615                 WebInspector.showProfileForURL(anchor.href);
616         }
617     }
618
619     if (WebInspector.followLinkTimeout)
620         clearTimeout(WebInspector.followLinkTimeout);
621
622     if (anchor.preventFollowOnDoubleClick) {
623         // Start a timeout if this is the first click, if the timeout is canceled
624         // before it fires, then a double clicked happened or another link was clicked.
625         if (event.detail === 1)
626             WebInspector.followLinkTimeout = setTimeout(followLink, 333);
627         return;
628     }
629
630     followLink();
631 }
632
633 WebInspector.documentKeyDown = function(event)
634 {
635     if (!this.currentFocusElement)
636         return;
637     if (this.currentFocusElement.handleKeyEvent)
638         this.currentFocusElement.handleKeyEvent(event);
639     else if (this.currentFocusElement.id && this.currentFocusElement.id.length && WebInspector[this.currentFocusElement.id + "KeyDown"])
640         WebInspector[this.currentFocusElement.id + "KeyDown"](event);
641
642     if (!event.handled) {
643         var isMac = WebInspector.isMac();
644
645         switch (event.keyIdentifier) {
646             case "U+001B": // Escape key
647                 event.preventDefault();
648                 if (this.drawer.fullPanel)
649                     return;
650
651                 this.drawer.visible = !this.drawer.visible;
652                 break;
653
654             case "U+0046": // F key
655                 if (isMac)
656                     var isFindKey = event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey;
657                 else
658                     var isFindKey = event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey;
659
660                 if (isFindKey) {
661                     var searchField = document.getElementById("search");
662                     searchField.focus();
663                     searchField.select();
664                     event.preventDefault();
665                 }
666
667                 break;
668
669             case "U+0047": // G key
670                 if (isMac)
671                     var isFindAgainKey = event.metaKey && !event.ctrlKey && !event.altKey;
672                 else
673                     var isFindAgainKey = event.ctrlKey && !event.metaKey && !event.altKey;
674
675                 if (isFindAgainKey) {
676                     if (event.shiftKey) {
677                         if (this.currentPanel.jumpToPreviousSearchResult)
678                             this.currentPanel.jumpToPreviousSearchResult();
679                     } else if (this.currentPanel.jumpToNextSearchResult)
680                         this.currentPanel.jumpToNextSearchResult();
681                     event.preventDefault();
682                 }
683
684                 break;
685
686             // Windows and Mac have two different definitions of [, so accept both.
687             case "U+005B":
688             case "U+00DB": // [ key
689                 if (isMac)
690                     var isRotateLeft = event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey;
691                 else
692                     var isRotateLeft = event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey;
693
694                 if (isRotateLeft) {
695                     var index = this.panelOrder.indexOf(this.currentPanel);
696                     index = (index === 0) ? this.panelOrder.length - 1 : index - 1;
697                     this.panelOrder[index].toolbarItem.click();
698                     event.preventDefault();
699                 }
700
701                 break;
702
703             // Windows and Mac have two different definitions of ], so accept both.
704             case "U+005D":
705             case "U+00DD":  // ] key
706                 if (isMac)
707                     var isRotateRight = event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey;
708                 else
709                     var isRotateRight = event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey;
710
711                 if (isRotateRight) {
712                     var index = this.panelOrder.indexOf(this.currentPanel);
713                     index = (index + 1) % this.panelOrder.length;
714                     this.panelOrder[index].toolbarItem.click();
715                     event.preventDefault();
716                 }
717
718                 break;
719         }
720     }
721 }
722
723 WebInspector.documentKeyUp = function(event)
724 {
725     if (!this.currentFocusElement || !this.currentFocusElement.handleKeyUpEvent)
726         return;
727     this.currentFocusElement.handleKeyUpEvent(event);
728 }
729
730 WebInspector.documentCanCopy = function(event)
731 {
732     if (!this.currentFocusElement)
733         return;
734     // Calling preventDefault() will say "we support copying, so enable the Copy menu".
735     if (this.currentFocusElement.handleCopyEvent)
736         event.preventDefault();
737     else if (this.currentFocusElement.id && this.currentFocusElement.id.length && WebInspector[this.currentFocusElement.id + "Copy"])
738         event.preventDefault();
739 }
740
741 WebInspector.documentCopy = function(event)
742 {
743     if (!this.currentFocusElement)
744         return;
745     if (this.currentFocusElement.handleCopyEvent)
746         this.currentFocusElement.handleCopyEvent(event);
747     else if (this.currentFocusElement.id && this.currentFocusElement.id.length && WebInspector[this.currentFocusElement.id + "Copy"])
748         WebInspector[this.currentFocusElement.id + "Copy"](event);
749 }
750
751 WebInspector.contextMenuEventFired = function(event)
752 {
753     if (event.handled || event.target.hasStyleClass("popup-glasspane"))
754         event.preventDefault();
755
756     if (!this.contextMenu)
757         this.contextMenu = new WebInspector.ContextMenu();
758     this.contextMenu.show(event);
759 }
760
761 WebInspector.mainKeyDown = function(event)
762 {
763     if (this.currentPanel && this.currentPanel.handleKeyEvent)
764         this.currentPanel.handleKeyEvent(event);
765 }
766
767 WebInspector.mainKeyUp = function(event)
768 {
769     if (this.currentPanel && this.currentPanel.handleKeyUpEvent)
770         this.currentPanel.handleKeyUpEvent(event);
771 }
772
773 WebInspector.mainCopy = function(event)
774 {
775     if (this.currentPanel && this.currentPanel.handleCopyEvent)
776         this.currentPanel.handleCopyEvent(event);
777 }
778
779 WebInspector.animateStyle = function(animations, duration, callback)
780 {
781     var interval;
782     var complete = 0;
783
784     const intervalDuration = (1000 / 30); // 30 frames per second.
785     const animationsLength = animations.length;
786     const propertyUnit = {opacity: ""};
787     const defaultUnit = "px";
788
789     function cubicInOut(t, b, c, d)
790     {
791         if ((t/=d/2) < 1) return c/2*t*t*t + b;
792         return c/2*((t-=2)*t*t + 2) + b;
793     }
794
795     // Pre-process animations.
796     for (var i = 0; i < animationsLength; ++i) {
797         var animation = animations[i];
798         var element = null, start = null, end = null, key = null;
799         for (key in animation) {
800             if (key === "element")
801                 element = animation[key];
802             else if (key === "start")
803                 start = animation[key];
804             else if (key === "end")
805                 end = animation[key];
806         }
807
808         if (!element || !end)
809             continue;
810
811         if (!start) {
812             var computedStyle = element.ownerDocument.defaultView.getComputedStyle(element);
813             start = {};
814             for (key in end)
815                 start[key] = parseInt(computedStyle.getPropertyValue(key));
816             animation.start = start;
817         } else
818             for (key in start)
819                 element.style.setProperty(key, start[key] + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
820     }
821
822     function animateLoop()
823     {
824         // Advance forward.
825         complete += intervalDuration;
826         var next = complete + intervalDuration;
827
828         // Make style changes.
829         for (var i = 0; i < animationsLength; ++i) {
830             var animation = animations[i];
831             var element = animation.element;
832             var start = animation.start;
833             var end = animation.end;
834             if (!element || !end)
835                 continue;
836
837             var style = element.style;
838             for (key in end) {
839                 var endValue = end[key];
840                 if (next < duration) {
841                     var startValue = start[key];
842                     var newValue = cubicInOut(complete, startValue, endValue - startValue, duration);
843                     style.setProperty(key, newValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
844                 } else
845                     style.setProperty(key, endValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
846             }
847         }
848
849         // End condition.
850         if (complete >= duration) {
851             clearInterval(interval);
852             if (callback)
853                 callback();
854         }
855     }
856
857     interval = setInterval(animateLoop, intervalDuration);
858     return interval;
859 }
860
861 WebInspector.updateSearchLabel = function()
862 {
863     if (!this.currentPanel)
864         return;
865
866     var newLabel = WebInspector.UIString("Search %s", this.currentPanel.toolbarItemLabel);
867     if (this.attached)
868         document.getElementById("search").setAttribute("placeholder", newLabel);
869     else {
870         document.getElementById("search").removeAttribute("placeholder");
871         document.getElementById("search-toolbar-label").textContent = newLabel;
872     }
873 }
874
875 WebInspector.toggleAttach = function()
876 {
877     this.attached = !this.attached;
878     this.drawer.resize();
879 }
880
881 WebInspector.toolbarDragStart = function(event)
882 {
883     if ((!WebInspector.attached && WebInspector.platform !== "mac-leopard") || WebInspector.port == "qt")
884         return;
885
886     var target = event.target;
887     if (target.hasStyleClass("toolbar-item") && target.hasStyleClass("toggleable"))
888         return;
889
890     var toolbar = document.getElementById("toolbar");
891     if (target !== toolbar && !target.hasStyleClass("toolbar-item"))
892         return;
893
894     toolbar.lastScreenX = event.screenX;
895     toolbar.lastScreenY = event.screenY;
896
897     WebInspector.elementDragStart(toolbar, WebInspector.toolbarDrag, WebInspector.toolbarDragEnd, event, (WebInspector.attached ? "row-resize" : "default"));
898 }
899
900 WebInspector.toolbarDragEnd = function(event)
901 {
902     var toolbar = document.getElementById("toolbar");
903
904     WebInspector.elementDragEnd(event);
905
906     delete toolbar.lastScreenX;
907     delete toolbar.lastScreenY;
908 }
909
910 WebInspector.toolbarDrag = function(event)
911 {
912     var toolbar = document.getElementById("toolbar");
913
914     if (WebInspector.attached) {
915         var height = window.innerHeight - (event.screenY - toolbar.lastScreenY);
916
917         InspectorFrontendHost.setAttachedWindowHeight(height);
918     } else {
919         var x = event.screenX - toolbar.lastScreenX;
920         var y = event.screenY - toolbar.lastScreenY;
921
922         // We cannot call window.moveBy here because it restricts the movement
923         // of the window at the edges.
924         InspectorFrontendHost.moveWindowBy(x, y);
925     }
926
927     toolbar.lastScreenX = event.screenX;
928     toolbar.lastScreenY = event.screenY;
929
930     event.preventDefault();
931 }
932
933 WebInspector.elementDragStart = function(element, dividerDrag, elementDragEnd, event, cursor)
934 {
935     if (this._elementDraggingEventListener || this._elementEndDraggingEventListener)
936         this.elementDragEnd(event);
937
938     this._elementDraggingEventListener = dividerDrag;
939     this._elementEndDraggingEventListener = elementDragEnd;
940
941     document.addEventListener("mousemove", dividerDrag, true);
942     document.addEventListener("mouseup", elementDragEnd, true);
943
944     document.body.style.cursor = cursor;
945
946     event.preventDefault();
947 }
948
949 WebInspector.elementDragEnd = function(event)
950 {
951     document.removeEventListener("mousemove", this._elementDraggingEventListener, true);
952     document.removeEventListener("mouseup", this._elementEndDraggingEventListener, true);
953
954     document.body.style.removeProperty("cursor");
955
956     delete this._elementDraggingEventListener;
957     delete this._elementEndDraggingEventListener;
958
959     event.preventDefault();
960 }
961
962 WebInspector.showConsole = function()
963 {
964     this.drawer.showView(this.console);
965 }
966
967 WebInspector.showChanges = function()
968 {
969     this.drawer.showView(this.changes);
970 }
971
972 WebInspector.showElementsPanel = function()
973 {
974     this.currentPanel = this.panels.elements;
975 }
976
977 WebInspector.showResourcesPanel = function()
978 {
979     this.currentPanel = this.panels.resources;
980 }
981
982 WebInspector.showScriptsPanel = function()
983 {
984     this.currentPanel = this.panels.scripts;
985 }
986
987 WebInspector.showTimelinePanel = function()
988 {
989     this.currentPanel = this.panels.timeline;
990 }
991
992 WebInspector.showProfilesPanel = function()
993 {
994     this.currentPanel = this.panels.profiles;
995 }
996
997 WebInspector.showStoragePanel = function()
998 {
999     this.currentPanel = this.panels.storage;
1000 }
1001
1002 WebInspector.showConsolePanel = function()
1003 {
1004     this.currentPanel = this.panels.console;
1005 }
1006
1007 WebInspector.addResource = function(identifier, payload)
1008 {
1009     var resource = new WebInspector.Resource(
1010         payload.requestHeaders,
1011         payload.requestURL,
1012         payload.host,
1013         payload.path,
1014         payload.lastPathComponent,
1015         identifier,
1016         payload.isMainResource,
1017         payload.cached,
1018         payload.requestMethod,
1019         payload.requestFormData);
1020     this.resources[identifier] = resource;
1021     this.resourceURLMap[resource.url] = resource;
1022
1023     if (resource.mainResource)
1024         this.mainResource = resource;
1025
1026     if (this.panels.resources)
1027         this.panels.resources.addResource(resource);
1028
1029     var match = payload.documentURL.match(/^(http[s]?|file):\/\/([\/]*[^\/]+)/i);
1030     if (match)
1031         this.addCookieDomain(match[1].toLowerCase() === "file" ? "" : match[2]);
1032 }
1033
1034 WebInspector.clearConsoleMessages = function()
1035 {
1036     WebInspector.console.clearMessages(false);
1037 }
1038
1039 WebInspector.selectDatabase = function(o)
1040 {
1041     WebInspector.showStoragePanel();
1042     WebInspector.panels.storage.selectDatabase(o);
1043 }
1044
1045 WebInspector.selectDOMStorage = function(o)
1046 {
1047     WebInspector.showStoragePanel();
1048     WebInspector.panels.storage.selectDOMStorage(o);
1049 }
1050
1051 WebInspector.updateResource = function(identifier, payload)
1052 {
1053     var resource = this.resources[identifier];
1054     if (!resource)
1055         return;
1056
1057     if (payload.didRequestChange) {
1058         resource.url = payload.url;
1059         resource.domain = payload.domain;
1060         resource.path = payload.path;
1061         resource.lastPathComponent = payload.lastPathComponent;
1062         resource.requestHeaders = payload.requestHeaders;
1063         resource.mainResource = payload.mainResource;
1064         resource.requestMethod = payload.requestMethod;
1065         resource.requestFormData = payload.requestFormData;
1066     }
1067
1068     if (payload.didResponseChange) {
1069         resource.mimeType = payload.mimeType;
1070         resource.suggestedFilename = payload.suggestedFilename;
1071         resource.expectedContentLength = payload.expectedContentLength;
1072         resource.statusCode = payload.statusCode;
1073         resource.suggestedFilename = payload.suggestedFilename;
1074         resource.responseHeaders = payload.responseHeaders;
1075     }
1076
1077     if (payload.didTypeChange) {
1078         resource.type = payload.type;
1079     }
1080
1081     if (payload.didLengthChange) {
1082         resource.contentLength = payload.contentLength;
1083     }
1084
1085     if (payload.didCompletionChange) {
1086         resource.failed = payload.failed;
1087         resource.finished = payload.finished;
1088     }
1089
1090     if (payload.didTimingChange) {
1091         if (payload.startTime)
1092             resource.startTime = payload.startTime;
1093         if (payload.responseReceivedTime)
1094             resource.responseReceivedTime = payload.responseReceivedTime;
1095         if (payload.endTime)
1096             resource.endTime = payload.endTime;
1097
1098         if (payload.loadEventTime) {
1099             // This loadEventTime is for the main resource, and we want to show it
1100             // for all resources on this page. This means we want to set it as a member
1101             // of the resources panel instead of the individual resource.
1102             if (this.panels.resources)
1103                 this.panels.resources.mainResourceLoadTime = payload.loadEventTime;
1104         }
1105
1106         if (payload.domContentEventTime) {
1107             // This domContentEventTime is for the main resource, so it should go in
1108             // the resources panel for the same reasons as above.
1109             if (this.panels.resources)
1110                 this.panels.resources.mainResourceDOMContentTime = payload.domContentEventTime;
1111         }
1112     }
1113 }
1114
1115 WebInspector.removeResource = function(identifier)
1116 {
1117     var resource = this.resources[identifier];
1118     if (!resource)
1119         return;
1120
1121     resource.category.removeResource(resource);
1122     delete this.resourceURLMap[resource.url];
1123     delete this.resources[identifier];
1124
1125     if (this.panels.resources)
1126         this.panels.resources.removeResource(resource);
1127 }
1128
1129 WebInspector.addDatabase = function(payload)
1130 {
1131     if (!this.panels.storage)
1132         return;
1133     var database = new WebInspector.Database(
1134         payload.id,
1135         payload.domain,
1136         payload.name,
1137         payload.version);
1138     this.panels.storage.addDatabase(database);
1139 }
1140
1141 WebInspector.addCookieDomain = function(domain)
1142 {
1143     // Eliminate duplicate domains from the list.
1144     if (domain in this.cookieDomains)
1145         return;
1146     this.cookieDomains[domain] = true;
1147
1148     if (!this.panels.storage)
1149         return;
1150     this.panels.storage.addCookieDomain(domain);
1151 }
1152
1153 WebInspector.addDOMStorage = function(payload)
1154 {
1155     if (!this.panels.storage)
1156         return;
1157     var domStorage = new WebInspector.DOMStorage(
1158         payload.id,
1159         payload.host,
1160         payload.isLocalStorage);
1161     this.panels.storage.addDOMStorage(domStorage);
1162 }
1163
1164 WebInspector.updateDOMStorage = function(storageId)
1165 {
1166     if (!this.panels.storage)
1167         return;
1168     this.panels.storage.updateDOMStorage(storageId);
1169 }
1170
1171 WebInspector.resourceTrackingWasEnabled = function()
1172 {
1173     this.panels.resources.resourceTrackingWasEnabled();
1174 }
1175
1176 WebInspector.resourceTrackingWasDisabled = function()
1177 {
1178     this.panels.resources.resourceTrackingWasDisabled();
1179 }
1180
1181 WebInspector.attachDebuggerWhenShown = function()
1182 {
1183     this.panels.scripts.attachDebuggerWhenShown();
1184 }
1185
1186 WebInspector.debuggerWasEnabled = function()
1187 {
1188     this.panels.scripts.debuggerWasEnabled();
1189 }
1190
1191 WebInspector.debuggerWasDisabled = function()
1192 {
1193     this.panels.scripts.debuggerWasDisabled();
1194 }
1195
1196 WebInspector.profilerWasEnabled = function()
1197 {
1198     this.panels.profiles.profilerWasEnabled();
1199 }
1200
1201 WebInspector.profilerWasDisabled = function()
1202 {
1203     this.panels.profiles.profilerWasDisabled();
1204 }
1205
1206 WebInspector.parsedScriptSource = function(sourceID, sourceURL, source, startingLine)
1207 {
1208     this.panels.scripts.addScript(sourceID, sourceURL, source, startingLine);
1209 }
1210
1211 WebInspector.failedToParseScriptSource = function(sourceURL, source, startingLine, errorLine, errorMessage)
1212 {
1213     this.panels.scripts.addScript(null, sourceURL, source, startingLine, errorLine, errorMessage);
1214 }
1215
1216 WebInspector.pausedScript = function(callFrames)
1217 {
1218     this.panels.scripts.debuggerPaused(callFrames);
1219 }
1220
1221 WebInspector.resumedScript = function()
1222 {
1223     this.panels.scripts.debuggerResumed();
1224 }
1225
1226 WebInspector.populateInterface = function()
1227 {
1228     for (var panelName in this.panels) {
1229         var panel = this.panels[panelName];
1230         if ("populateInterface" in panel)
1231             panel.populateInterface();
1232     }
1233 }
1234
1235 WebInspector.reset = function()
1236 {
1237     for (var panelName in this.panels) {
1238         var panel = this.panels[panelName];
1239         if ("reset" in panel)
1240             panel.reset();
1241     }
1242
1243     for (var category in this.resourceCategories)
1244         this.resourceCategories[category].removeAllResources();
1245
1246     this.resources = {};
1247     this.resourceURLMap = {};
1248     this.cookieDomains = {};
1249     this.hoveredDOMNode = null;
1250
1251     delete this.mainResource;
1252
1253     this.console.clearMessages();
1254 }
1255
1256 WebInspector.resourceURLChanged = function(resource, oldURL)
1257 {
1258     delete this.resourceURLMap[oldURL];
1259     this.resourceURLMap[resource.url] = resource;
1260 }
1261
1262 WebInspector.didCommitLoad = function()
1263 {
1264     // Cleanup elements panel early on inspected page refresh.
1265     WebInspector.setDocument(null);
1266 }
1267
1268 WebInspector.addConsoleMessage = function(payload)
1269 {
1270     var consoleMessage = new WebInspector.ConsoleMessage(
1271         payload.source,
1272         payload.type,
1273         payload.level,
1274         payload.line,
1275         payload.url,
1276         payload.groupLevel,
1277         payload.repeatCount);
1278     consoleMessage.setMessageBody(Array.prototype.slice.call(arguments, 1));
1279     this.console.addMessage(consoleMessage);
1280 }
1281
1282 WebInspector.updateConsoleMessageRepeatCount = function(count)
1283 {
1284     this.console.updateMessageRepeatCount(count);
1285 }
1286
1287 WebInspector.log = function(message)
1288 {
1289     // remember 'this' for setInterval() callback
1290     var self = this;
1291
1292     // return indication if we can actually log a message
1293     function isLogAvailable()
1294     {
1295         return WebInspector.ConsoleMessage && WebInspector.ObjectProxy && self.console;
1296     }
1297
1298     // flush the queue of pending messages
1299     function flushQueue()
1300     {
1301         var queued = WebInspector.log.queued;
1302         if (!queued)
1303             return;
1304
1305         for (var i = 0; i < queued.length; ++i)
1306             logMessage(queued[i]);
1307
1308         delete WebInspector.log.queued;
1309     }
1310
1311     // flush the queue if it console is available
1312     // - this function is run on an interval
1313     function flushQueueIfAvailable()
1314     {
1315         if (!isLogAvailable())
1316             return;
1317
1318         clearInterval(WebInspector.log.interval);
1319         delete WebInspector.log.interval;
1320
1321         flushQueue();
1322     }
1323
1324     // actually log the message
1325     function logMessage(message)
1326     {
1327         var repeatCount = 1;
1328         if (message == WebInspector.log.lastMessage)
1329             repeatCount = WebInspector.log.repeatCount + 1;
1330
1331         WebInspector.log.lastMessage = message;
1332         WebInspector.log.repeatCount = repeatCount;
1333
1334         // ConsoleMessage expects a proxy object
1335         message = new WebInspector.ObjectProxy(null, [], 0, message, false);
1336
1337         // post the message
1338         var msg = new WebInspector.ConsoleMessage(
1339             WebInspector.ConsoleMessage.MessageSource.Other,
1340             WebInspector.ConsoleMessage.MessageType.Log,
1341             WebInspector.ConsoleMessage.MessageLevel.Debug,
1342             -1,
1343             null,
1344             null,
1345             repeatCount,
1346             message);
1347
1348         self.console.addMessage(msg);
1349     }
1350
1351     // if we can't log the message, queue it
1352     if (!isLogAvailable()) {
1353         if (!WebInspector.log.queued)
1354             WebInspector.log.queued = [];
1355
1356         WebInspector.log.queued.push(message);
1357
1358         if (!WebInspector.log.interval)
1359             WebInspector.log.interval = setInterval(flushQueueIfAvailable, 1000);
1360
1361         return;
1362     }
1363
1364     // flush the pending queue if any
1365     flushQueue();
1366
1367     // log the message
1368     logMessage(message);
1369 }
1370
1371 WebInspector.addProfileHeader = function(profile)
1372 {
1373     this.panels.profiles.addProfileHeader(profile);
1374 }
1375
1376 WebInspector.setRecordingProfile = function(isProfiling)
1377 {
1378     this.panels.profiles.getProfileType(WebInspector.CPUProfileType.TypeId).setRecordingProfile(isProfiling);
1379     this.panels.profiles.updateProfileTypeButtons();
1380 }
1381
1382 WebInspector.drawLoadingPieChart = function(canvas, percent) {
1383     var g = canvas.getContext("2d");
1384     var darkColor = "rgb(122, 168, 218)";
1385     var lightColor = "rgb(228, 241, 251)";
1386     var cx = 8;
1387     var cy = 8;
1388     var r = 7;
1389
1390     g.beginPath();
1391     g.arc(cx, cy, r, 0, Math.PI * 2, false);
1392     g.closePath();
1393
1394     g.lineWidth = 1;
1395     g.strokeStyle = darkColor;
1396     g.fillStyle = lightColor;
1397     g.fill();
1398     g.stroke();
1399
1400     var startangle = -Math.PI / 2;
1401     var endangle = startangle + (percent * Math.PI * 2);
1402
1403     g.beginPath();
1404     g.moveTo(cx, cy);
1405     g.arc(cx, cy, r, startangle, endangle, false);
1406     g.closePath();
1407
1408     g.fillStyle = darkColor;
1409     g.fill();
1410 }
1411
1412 WebInspector.updateFocusedNode = function(nodeId)
1413 {
1414     var node = WebInspector.domAgent.nodeForId(nodeId);
1415     if (!node)
1416         // FIXME: Should we deselect if null is passed in?
1417         return;
1418
1419     this.currentPanel = this.panels.elements;
1420     this.panels.elements.focusedDOMNode = node;
1421 }
1422
1423 WebInspector.displayNameForURL = function(url)
1424 {
1425     if (!url)
1426         return "";
1427     var resource = this.resourceURLMap[url];
1428     if (resource)
1429         return resource.displayName;
1430     return url.trimURL(WebInspector.mainResource ? WebInspector.mainResource.domain : "");
1431 }
1432
1433 WebInspector.resourceForURL = function(url)
1434 {
1435     if (url in this.resourceURLMap)
1436         return this.resourceURLMap[url];
1437
1438     // No direct match found. Search for resources that contain
1439     // a substring of the URL.
1440     for (var resourceURL in this.resourceURLMap) {
1441         if (resourceURL.hasSubstring(url))
1442             return this.resourceURLMap[resourceURL];
1443     }
1444
1445     return null;
1446 }
1447
1448 WebInspector.showResourceForURL = function(url, line, preferredPanel)
1449 {
1450     var resource = this.resourceForURL(url);
1451     if (!resource)
1452         return false;
1453
1454     if (preferredPanel && preferredPanel in WebInspector.panels) {
1455         var panel = this.panels[preferredPanel];
1456         if (!("showResource" in panel))
1457             panel = null;
1458         else if ("canShowResource" in panel && !panel.canShowResource(resource))
1459             panel = null;
1460     }
1461
1462     this.currentPanel = panel || this.panels.resources;
1463     if (!this.currentPanel)
1464         return false;
1465     this.currentPanel.showResource(resource, line);
1466     return true;
1467 }
1468
1469 WebInspector.linkifyStringAsFragment = function(string)
1470 {
1471     var container = document.createDocumentFragment();
1472     var linkStringRegEx = /(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\/\/|www\.)[\w$\-_+*'=\|\/\\(){}[\]%@&#~,:;.!?]{2,}[\w$\-_+*=\|\/\\({%@&#~]/;
1473
1474     while (string) {
1475         var linkString = linkStringRegEx.exec(string);
1476         if (!linkString)
1477             break;
1478
1479         linkString = linkString[0];
1480         var title = linkString;
1481         var linkIndex = string.indexOf(linkString);
1482         var nonLink = string.substring(0, linkIndex);
1483         container.appendChild(document.createTextNode(nonLink));
1484
1485         var profileStringMatches = WebInspector.ProfileType.URLRegExp.exec(title);
1486         if (profileStringMatches)
1487             title = WebInspector.panels.profiles.displayTitleForProfileLink(profileStringMatches[2], profileStringMatches[1]);
1488
1489         var realURL = (linkString.indexOf("www.") === 0 ? "http://" + linkString : linkString);
1490         container.appendChild(WebInspector.linkifyURLAsNode(realURL, title, null, (realURL in WebInspector.resourceURLMap)));
1491         string = string.substring(linkIndex + linkString.length, string.length);
1492     }
1493
1494     if (string)
1495         container.appendChild(document.createTextNode(string));
1496
1497     return container;
1498 }
1499
1500 WebInspector.showProfileForURL = function(url)
1501 {
1502     WebInspector.showProfilesPanel();
1503     WebInspector.panels.profiles.showProfileForURL(url);
1504 }
1505
1506 WebInspector.linkifyURLAsNode = function(url, linkText, classes, isExternal, tooltipText)
1507 {
1508     if (!linkText)
1509         linkText = url;
1510     classes = (classes ? classes + " " : "");
1511     classes += isExternal ? "webkit-html-external-link" : "webkit-html-resource-link";
1512
1513     var a = document.createElement("a");
1514     a.href = url;
1515     a.className = classes;
1516     a.title = tooltipText || url;
1517     a.target = "_blank";
1518     a.textContent = linkText;
1519
1520     return a;
1521 }
1522
1523 WebInspector.linkifyURL = function(url, linkText, classes, isExternal, tooltipText)
1524 {
1525     // Use the DOM version of this function so as to avoid needing to escape attributes.
1526     // FIXME:  Get rid of linkifyURL entirely.
1527     return WebInspector.linkifyURLAsNode(url, linkText, classes, isExternal, tooltipText).outerHTML;
1528 }
1529
1530 WebInspector.addMainEventListeners = function(doc)
1531 {
1532     doc.defaultView.addEventListener("focus", this.windowFocused.bind(this), false);
1533     doc.defaultView.addEventListener("blur", this.windowBlurred.bind(this), false);
1534     doc.addEventListener("click", this.documentClick.bind(this), true);
1535 }
1536
1537 WebInspector.searchFieldManualFocus = function(event)
1538 {
1539     this.currentFocusElement = event.target;
1540     this._previousFocusElement = event.target;
1541 }
1542
1543 WebInspector.searchKeyDown = function(event)
1544 {
1545     // Escape Key will clear the field and clear the search results
1546     if (event.keyCode === WebInspector.KeyboardShortcut.KeyCodes.Esc) {
1547         event.preventDefault();
1548         event.handled = true;
1549         event.target.value = "";
1550
1551         this.performSearch(event);
1552         this.currentFocusElement = this.previousFocusElement;
1553         if (this.currentFocusElement === event.target)
1554             this.currentFocusElement.select();
1555
1556         return false;
1557     }
1558
1559     if (!isEnterKey(event))
1560         return false;
1561
1562     // Select all of the text so the user can easily type an entirely new query.
1563     event.target.select();
1564
1565     // Only call performSearch if the Enter key was pressed. Otherwise the search
1566     // performance is poor because of searching on every key. The search field has
1567     // the incremental attribute set, so we still get incremental searches.
1568     this.performSearch(event);
1569
1570     // Call preventDefault since this was the Enter key. This prevents a "search" event
1571     // from firing for key down. This stops performSearch from being called twice in a row.
1572     event.preventDefault();
1573 }
1574
1575 WebInspector.performSearch = function(event)
1576 {
1577     var query = event.target.value;
1578     var forceSearch = event.keyIdentifier === "Enter";
1579     var isShortSearch = (query.length < 3);
1580
1581     // Clear a leftover short search flag due to a non-conflicting forced search.
1582     if (isShortSearch && this.shortSearchWasForcedByKeyEvent && this.currentQuery !== query)
1583         delete this.shortSearchWasForcedByKeyEvent;
1584
1585     // Indicate this was a forced search on a short query.
1586     if (isShortSearch && forceSearch)
1587         this.shortSearchWasForcedByKeyEvent = true;
1588
1589     if (!query || !query.length || (!forceSearch && isShortSearch)) {
1590         // Prevent clobbering a short search forced by the user.
1591         if (this.shortSearchWasForcedByKeyEvent) {
1592             delete this.shortSearchWasForcedByKeyEvent;
1593             return;
1594         }
1595
1596         delete this.currentQuery;
1597
1598         for (var panelName in this.panels) {
1599             var panel = this.panels[panelName];
1600             if (panel.currentQuery && panel.searchCanceled)
1601                 panel.searchCanceled();
1602             delete panel.currentQuery;
1603         }
1604
1605         this.updateSearchMatchesCount();
1606
1607         return;
1608     }
1609
1610     if (query === this.currentPanel.currentQuery && this.currentPanel.currentQuery === this.currentQuery) {
1611         // When this is the same query and a forced search, jump to the next
1612         // search result for a good user experience.
1613         if (forceSearch && this.currentPanel.jumpToNextSearchResult)
1614             this.currentPanel.jumpToNextSearchResult();
1615         return;
1616     }
1617
1618     this.currentQuery = query;
1619
1620     this.updateSearchMatchesCount();
1621
1622     if (!this.currentPanel.performSearch)
1623         return;
1624
1625     this.currentPanel.currentQuery = query;
1626     this.currentPanel.performSearch(query);
1627 }
1628
1629 WebInspector.addNodesToSearchResult = function(nodeIds)
1630 {
1631     WebInspector.panels.elements.addNodesToSearchResult(nodeIds);
1632 }
1633
1634 WebInspector.updateSearchMatchesCount = function(matches, panel)
1635 {
1636     if (!panel)
1637         panel = this.currentPanel;
1638
1639     panel.currentSearchMatches = matches;
1640
1641     if (panel !== this.currentPanel)
1642         return;
1643
1644     if (!this.currentPanel.currentQuery) {
1645         document.getElementById("search-results-matches").addStyleClass("hidden");
1646         return;
1647     }
1648
1649     if (matches) {
1650         if (matches === 1)
1651             var matchesString = WebInspector.UIString("1 match");
1652         else
1653             var matchesString = WebInspector.UIString("%d matches", matches);
1654     } else
1655         var matchesString = WebInspector.UIString("Not Found");
1656
1657     var matchesToolbarElement = document.getElementById("search-results-matches");
1658     matchesToolbarElement.removeStyleClass("hidden");
1659     matchesToolbarElement.textContent = matchesString;
1660 }
1661
1662 WebInspector.UIString = function(string)
1663 {
1664     if (window.localizedStrings && string in window.localizedStrings)
1665         string = window.localizedStrings[string];
1666     else {
1667         if (!(string in this.missingLocalizedStrings)) {
1668             if (!WebInspector.InspectorBackendStub)
1669                 console.error("Localized string \"" + string + "\" not found.");
1670             this.missingLocalizedStrings[string] = true;
1671         }
1672
1673         if (Preferences.showMissingLocalizedStrings)
1674             string += " (not localized)";
1675     }
1676
1677     return String.vsprintf(string, Array.prototype.slice.call(arguments, 1));
1678 }
1679
1680 WebInspector.isMac = function()
1681 {
1682     if (!("_isMac" in this))
1683         this._isMac = WebInspector.platform.indexOf("mac-") === 0;
1684
1685     return this._isMac;
1686 }
1687
1688 WebInspector.isBeingEdited = function(element)
1689 {
1690     return element.__editing;
1691 }
1692
1693 WebInspector.startEditing = function(element, committedCallback, cancelledCallback, context)
1694 {
1695     if (element.__editing)
1696         return;
1697     element.__editing = true;
1698
1699     var oldText = getContent(element);
1700     var oldHandleKeyEvent = element.handleKeyEvent;
1701     var moveDirection = "";
1702
1703     element.addStyleClass("editing");
1704
1705     var oldTabIndex = element.tabIndex;
1706     if (element.tabIndex < 0)
1707         element.tabIndex = 0;
1708
1709     function blurEventListener() {
1710         editingCommitted.call(element);
1711     }
1712
1713     function getContent(element) {
1714         if (element.tagName === "INPUT" && element.type === "text")
1715             return element.value;
1716         else
1717             return element.textContent;
1718     }
1719
1720     function cleanUpAfterEditing() {
1721         delete this.__editing;
1722
1723         this.removeStyleClass("editing");
1724         this.tabIndex = oldTabIndex;
1725         this.scrollTop = 0;
1726         this.scrollLeft = 0;
1727
1728         this.handleKeyEvent = oldHandleKeyEvent;
1729         element.removeEventListener("blur", blurEventListener, false);
1730
1731         if (element === WebInspector.currentFocusElement || element.isAncestor(WebInspector.currentFocusElement))
1732             WebInspector.currentFocusElement = WebInspector.previousFocusElement;
1733     }
1734
1735     function editingCancelled() {
1736         if (this.tagName === "INPUT" && this.type === "text")
1737             this.value = oldText;
1738         else
1739             this.textContent = oldText;
1740
1741         cleanUpAfterEditing.call(this);
1742
1743         if (cancelledCallback)
1744             cancelledCallback(this, context);
1745     }
1746
1747     function editingCommitted() {
1748         cleanUpAfterEditing.call(this);
1749
1750         if (committedCallback)
1751             committedCallback(this, getContent(this), oldText, context, moveDirection);
1752     }
1753
1754     element.handleKeyEvent = function(event) {
1755         if (oldHandleKeyEvent)
1756             oldHandleKeyEvent(event);
1757         if (event.handled)
1758             return;
1759
1760         if (isEnterKey(event)) {
1761             editingCommitted.call(element);
1762             event.preventDefault();
1763             event.stopPropagation();
1764             event.handled = true;
1765         } else if (event.keyCode === 27) { // Escape key
1766             editingCancelled.call(element);
1767             event.preventDefault();
1768             event.handled = true;
1769         } else if (event.keyIdentifier === "U+0009") // Tab key
1770             moveDirection = (event.shiftKey ? "backward" : "forward");
1771     }
1772
1773     element.addEventListener("blur", blurEventListener, false);
1774
1775     WebInspector.currentFocusElement = element;
1776 }
1777
1778 WebInspector._toolbarItemClicked = function(event)
1779 {
1780     var toolbarItem = event.currentTarget;
1781     this.currentPanel = toolbarItem.panel;
1782 }
1783
1784 // This table maps MIME types to the Resource.Types which are valid for them.
1785 // The following line:
1786 //    "text/html":                {0: 1},
1787 // means that text/html is a valid MIME type for resources that have type
1788 // WebInspector.Resource.Type.Document (which has a value of 0).
1789 WebInspector.MIMETypes = {
1790     "text/html":                   {0: true},
1791     "text/xml":                    {0: true},
1792     "text/plain":                  {0: true},
1793     "application/xhtml+xml":       {0: true},
1794     "text/css":                    {1: true},
1795     "text/xsl":                    {1: true},
1796     "image/jpeg":                  {2: true},
1797     "image/png":                   {2: true},
1798     "image/gif":                   {2: true},
1799     "image/bmp":                   {2: true},
1800     "image/vnd.microsoft.icon":    {2: true},
1801     "image/x-icon":                {2: true},
1802     "image/x-xbitmap":             {2: true},
1803     "font/ttf":                    {3: true},
1804     "font/opentype":               {3: true},
1805     "application/x-font-type1":    {3: true},
1806     "application/x-font-ttf":      {3: true},
1807     "application/x-truetype-font": {3: true},
1808     "text/javascript":             {4: true},
1809     "text/ecmascript":             {4: true},
1810     "application/javascript":      {4: true},
1811     "application/ecmascript":      {4: true},
1812     "application/x-javascript":    {4: true},
1813     "text/javascript1.1":          {4: true},
1814     "text/javascript1.2":          {4: true},
1815     "text/javascript1.3":          {4: true},
1816     "text/jscript":                {4: true},
1817     "text/livescript":             {4: true},
1818 }