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