Web Inspector: Embeddable Web Inspector
[WebKit-https.git] / Source / 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 WebInspector = {
32     _panelDescriptors: function()
33     {
34         this.panels = {};
35         WebInspector.inspectorView = new WebInspector.InspectorView();
36         var parentElement = document.getElementById("main");
37         WebInspector.inspectorView.show(parentElement);
38         WebInspector.inspectorView.addEventListener(WebInspector.InspectorView.Events.PanelSelected, this._panelSelected, this);
39
40         var elements = new WebInspector.PanelDescriptor("elements", WebInspector.UIString("Elements"), "ElementsPanel", "ElementsPanel.js");
41         var resources = new WebInspector.PanelDescriptor("resources", WebInspector.UIString("Resources"), "ResourcesPanel", "ResourcesPanel.js");
42         var network = new WebInspector.PanelDescriptor("network", WebInspector.UIString("Network"), "NetworkPanel", "NetworkPanel.js");
43         var scripts = new WebInspector.PanelDescriptor("scripts", WebInspector.UIString("Sources"), "ScriptsPanel", "ScriptsPanel.js");
44         var timeline = new WebInspector.PanelDescriptor("timeline", WebInspector.UIString("Timeline"), "TimelinePanel", "TimelinePanel.js");
45         var profiles = new WebInspector.PanelDescriptor("profiles", WebInspector.UIString("Profiles"), "ProfilesPanel", "ProfilesPanel.js");
46         var audits = new WebInspector.PanelDescriptor("audits", WebInspector.UIString("Audits"), "AuditsPanel", "AuditsPanel.js");
47         var console = new WebInspector.PanelDescriptor("console", WebInspector.UIString("Console"), "ConsolePanel");
48         var allDescriptors = [elements, resources, network, scripts, timeline, profiles, audits, console];
49
50         var panelDescriptors = [];
51         if (WebInspector.WorkerManager.isWorkerFrontend()) {
52             panelDescriptors.push(scripts);
53             panelDescriptors.push(timeline);
54             panelDescriptors.push(profiles);
55             panelDescriptors.push(console);
56             return panelDescriptors;
57         }
58         var allDescriptors = [elements, resources, network, scripts, timeline, profiles, audits, console];
59         var hiddenPanels = InspectorFrontendHost.hiddenPanels();
60         for (var i = 0; i < allDescriptors.length; ++i) {
61             if (hiddenPanels.indexOf(allDescriptors[i].name) === -1)
62                 panelDescriptors.push(allDescriptors[i]);
63         }
64         return panelDescriptors;
65     },
66
67     _panelSelected: function()
68     {
69         this._toggleConsoleButton.disabled = WebInspector.inspectorView.currentPanel().name === "console";
70     },
71
72     _createGlobalStatusBarItems: function()
73     {
74         var bottomStatusBarContainer = document.getElementById("bottom-status-bar-container");
75         this._dockToggleButton = new WebInspector.StatusBarButton("", "dock-status-bar-item", 3);
76         this._dockToggleButton.makeLongClickEnabled(this._createDockOptions.bind(this));
77         this._dockToggleButton.addEventListener("click", this._toggleAttach.bind(this), false);
78         this._updateDockButtonState();
79
80         var mainStatusBar = document.getElementById("main-status-bar");
81         mainStatusBar.insertBefore(this._dockToggleButton.element, bottomStatusBarContainer);
82
83         this._toggleConsoleButton = new WebInspector.StatusBarButton(WebInspector.UIString("Show console."), "console-status-bar-item");
84         this._toggleConsoleButton.addEventListener("click", this._toggleConsoleButtonClicked.bind(this), false);
85         mainStatusBar.insertBefore(this._toggleConsoleButton.element, bottomStatusBarContainer);
86
87         if (!WebInspector.WorkerManager.isWorkerFrontend()) {
88             this._nodeSearchButton = new WebInspector.StatusBarButton(WebInspector.UIString("Select an element in the page to inspect it."), "node-search-status-bar-item");
89             this._nodeSearchButton.addEventListener("click", this._toggleSearchingForNode, this);
90             mainStatusBar.insertBefore(this._nodeSearchButton.element, bottomStatusBarContainer);
91         }
92
93         mainStatusBar.appendChild(this.settingsController.statusBarItem);
94     },
95
96     _createDockOptions: function()
97     {
98         var alternateDockToggleButton1 = new WebInspector.StatusBarButton("Dock to main window.", "dock-status-bar-item", 3);
99         var alternateDockToggleButton2 = new WebInspector.StatusBarButton("Undock into separate window.", "dock-status-bar-item", 3);
100
101         if (this.attached) {
102             alternateDockToggleButton1.state = WebInspector.settings.dockToRight.get() ? "bottom" : "right";
103             alternateDockToggleButton2.state = "undock";
104         } else {
105             alternateDockToggleButton1.state = WebInspector.settings.dockToRight.get() ? "bottom" : "right";
106             alternateDockToggleButton2.state = WebInspector.settings.dockToRight.get() ? "right" : "bottom";
107         }
108
109         alternateDockToggleButton1.addEventListener("click", onClick.bind(this), false);
110         alternateDockToggleButton2.addEventListener("click", onClick.bind(this), false);
111
112         function onClick(e)
113         {
114             var state = e.target.state;
115             if (state === "undock")
116                 this._toggleAttach();
117             else if (state === "right") {
118                 if (!this.attached)
119                     this._toggleAttach();
120                 WebInspector.settings.dockToRight.set(true);
121             } else if (state === "bottom") {
122                 if (!this.attached)
123                     this._toggleAttach();
124                 WebInspector.settings.dockToRight.set(false);
125             }
126         }
127
128         return [alternateDockToggleButton1, alternateDockToggleButton2];
129     },
130
131     _updateDockButtonState: function()
132     {
133         if (!this._dockToggleButton)
134             return;
135
136         if (this.attached) {
137             this._dockToggleButton.disabled = false;
138             this._dockToggleButton.state = "undock";
139             this._dockToggleButton.title = WebInspector.UIString("Undock into separate window.");
140         } else {
141             this._dockToggleButton.disabled = this._isDockingUnavailable;
142             this._dockToggleButton.state = WebInspector.settings.dockToRight.get() ? "right" : "bottom";
143             this._dockToggleButton.title = WebInspector.UIString("Dock to main window.");
144         }
145     },
146
147     _toggleAttach: function()
148     {
149         if (!this._attached) {
150             InspectorFrontendHost.requestAttachWindow();
151             WebInspector.userMetrics.WindowDocked.record();
152         } else {
153             InspectorFrontendHost.requestDetachWindow();
154             WebInspector.userMetrics.WindowUndocked.record();
155         }
156     },
157
158     _toggleConsoleButtonClicked: function()
159     {
160         if (this._toggleConsoleButton.disabled)
161             return;
162
163         this._toggleConsoleButton.toggled = !this._toggleConsoleButton.toggled;
164
165         var animationType = window.event && window.event.shiftKey ? WebInspector.Drawer.AnimationType.Slow : WebInspector.Drawer.AnimationType.Normal;
166         if (this._toggleConsoleButton.toggled) {
167             this._toggleConsoleButton.title = WebInspector.UIString("Hide console.");
168             this.drawer.show(this.consoleView, animationType);
169             this._consoleWasShown = true;
170         } else {
171             this._toggleConsoleButton.title = WebInspector.UIString("Show console.");
172             this.drawer.hide(animationType);
173             delete this._consoleWasShown;
174         }
175     },
176
177     /**
178      * @param {Element} statusBarElement
179      * @param {WebInspector.View} view
180      * @param {function()=} onclose
181      */
182     showViewInDrawer: function(statusBarElement, view, onclose)
183     {
184         this._toggleConsoleButton.title = WebInspector.UIString("Hide console.");
185         this._toggleConsoleButton.toggled = false;
186         this._closePreviousDrawerView();
187
188         var drawerStatusBarHeader = document.createElement("div");
189         drawerStatusBarHeader.className = "drawer-header status-bar-item";
190         drawerStatusBarHeader.appendChild(statusBarElement);
191         drawerStatusBarHeader.onclose = onclose;
192
193         var closeButton = drawerStatusBarHeader.createChild("span");
194         closeButton.textContent = WebInspector.UIString("\u00D7");
195         closeButton.addStyleClass("drawer-header-close-button");
196         closeButton.addEventListener("click", this.closeViewInDrawer.bind(this), false);
197
198         document.getElementById("panel-status-bar").firstElementChild.appendChild(drawerStatusBarHeader);
199         this._drawerStatusBarHeader = drawerStatusBarHeader;
200         this.drawer.show(view, WebInspector.Drawer.AnimationType.Immediately);
201     },
202
203     closeViewInDrawer: function()
204     {
205         if (this._drawerStatusBarHeader) {
206             this._closePreviousDrawerView();
207
208             // Once drawer is closed console should be shown if it was shown before current view replaced it in drawer. 
209             if (!this._consoleWasShown)
210                 this.drawer.hide(WebInspector.Drawer.AnimationType.Immediately);
211             else
212                 this._toggleConsoleButtonClicked();
213         }
214     },
215
216     _closePreviousDrawerView: function()
217     {
218         if (this._drawerStatusBarHeader) {
219             this._drawerStatusBarHeader.parentElement.removeChild(this._drawerStatusBarHeader);
220             if (this._drawerStatusBarHeader.onclose)
221                 this._drawerStatusBarHeader.onclose();
222             delete this._drawerStatusBarHeader;
223         }
224     },
225
226     get attached()
227     {
228         return this._attached;
229     },
230
231     set attached(x)
232     {
233         if (this._attached === x)
234             return;
235
236         this._attached = x;
237
238         if (x)
239             document.body.removeStyleClass("detached");
240         else
241             document.body.addStyleClass("detached");
242
243         this._setCompactMode(x && !WebInspector.settings.dockToRight.get());
244         this._updateDockButtonState();
245     },
246
247     isCompactMode: function()
248     {
249         return this.attached && !WebInspector.settings.dockToRight.get();
250     },
251
252     _setCompactMode: function(x)
253     {
254         var body = document.body;
255         if (x)
256             body.addStyleClass("compact");
257         else
258             body.removeStyleClass("compact");
259         WebInspector.windowResize();
260     },
261
262     _updateErrorAndWarningCounts: function()
263     {
264         var errorWarningElement = document.getElementById("error-warning-count");
265         if (!errorWarningElement)
266             return;
267
268         var errors = WebInspector.console.errors;
269         var warnings = WebInspector.console.warnings;
270         if (!errors && !warnings) {
271             errorWarningElement.addStyleClass("hidden");
272             return;
273         }
274
275         errorWarningElement.removeStyleClass("hidden");
276
277         errorWarningElement.removeChildren();
278
279         if (errors) {
280             var errorImageElement = document.createElement("img");
281             errorImageElement.id = "error-count-img";
282             errorWarningElement.appendChild(errorImageElement);
283             var errorElement = document.createElement("span");
284             errorElement.id = "error-count";
285             errorElement.textContent = errors;
286             errorWarningElement.appendChild(errorElement);
287         }
288
289         if (warnings) {
290             var warningsImageElement = document.createElement("img");
291             warningsImageElement.id = "warning-count-img";
292             errorWarningElement.appendChild(warningsImageElement);
293             var warningsElement = document.createElement("span");
294             warningsElement.id = "warning-count";
295             warningsElement.textContent = warnings;
296             errorWarningElement.appendChild(warningsElement);
297         }
298
299         if (errors) {
300             if (warnings) {
301                 if (errors == 1) {
302                     if (warnings == 1)
303                         errorWarningElement.title = WebInspector.UIString("%d error, %d warning", errors, warnings);
304                     else
305                         errorWarningElement.title = WebInspector.UIString("%d error, %d warnings", errors, warnings);
306                 } else if (warnings == 1)
307                     errorWarningElement.title = WebInspector.UIString("%d errors, %d warning", errors, warnings);
308                 else
309                     errorWarningElement.title = WebInspector.UIString("%d errors, %d warnings", errors, warnings);
310             } else if (errors == 1)
311                 errorWarningElement.title = WebInspector.UIString("%d error", errors);
312             else
313                 errorWarningElement.title = WebInspector.UIString("%d errors", errors);
314         } else if (warnings == 1)
315             errorWarningElement.title = WebInspector.UIString("%d warning", warnings);
316         else if (warnings)
317             errorWarningElement.title = WebInspector.UIString("%d warnings", warnings);
318         else
319             errorWarningElement.title = null;
320     },
321
322     get inspectedPageDomain()
323     {
324         var parsedURL = WebInspector.inspectedPageURL && WebInspector.inspectedPageURL.asParsedURL();
325         return parsedURL ? parsedURL.host : "";
326     },
327
328     _initializeCapability: function(name, callback, error, result)
329     {
330         Capabilities[name] = result;
331         if (callback)
332             callback();
333     },
334
335     _zoomIn: function()
336     {
337         ++this._zoomLevel;
338         this._requestZoom();
339     },
340
341     _zoomOut: function()
342     {
343         --this._zoomLevel;
344         this._requestZoom();
345     },
346
347     _resetZoom: function()
348     {
349         this._zoomLevel = 0;
350         this._requestZoom();
351     },
352
353     _requestZoom: function()
354     {
355         WebInspector.settings.zoomLevel.set(this._zoomLevel);
356         InspectorFrontendHost.setZoomFactor(Math.pow(1.2, this._zoomLevel));
357     },
358
359     _toggleSearchingForNode: function()
360     {
361         var enabled = !this._nodeSearchButton.toggled;
362         /**
363          * @param {?Protocol.Error} error
364          */
365         function callback(error)
366         {
367             if (!error)
368                 this._nodeSearchButton.toggled = enabled;
369         }
370         WebInspector.domAgent.setInspectModeEnabled(enabled, callback.bind(this));
371     },
372
373     _profilesLinkifier: function(title)
374     {
375         var profileStringMatches = WebInspector.ProfileURLRegExp.exec(title);
376         if (profileStringMatches) {
377             var profilesPanel = /** @ type {WebInspector.ProfilesPanel} */ WebInspector.panel("profiles");
378             title = WebInspector.ProfilesPanel._instance.displayTitleForProfileLink(profileStringMatches[2], profileStringMatches[1]);
379         }
380         return title;
381     },
382
383     _debuggerPaused: function()
384     {
385         // Create scripts panel upon demand.
386         WebInspector.panel("scripts");
387     }
388 }
389
390 WebInspector.Events = {
391     InspectorClosing: "InspectorClosing"
392 }
393
394 {(function parseQueryParameters()
395 {
396     WebInspector.queryParamsObject = {};
397     var queryParams = window.location.search;
398     if (!queryParams)
399         return;
400     var params = queryParams.substring(1).split("&");
401     for (var i = 0; i < params.length; ++i) {
402         var pair = params[i].split("=");
403         WebInspector.queryParamsObject[pair[0]] = pair[1];
404     }
405 })();}
406
407 WebInspector.loaded = function()
408 {
409     InspectorBackend.loadFromJSONIfNeeded();
410
411     if (WebInspector.WorkerManager.isDedicatedWorkerFrontend()) {
412         // Do not create socket for the worker front-end.
413         WebInspector.doLoadedDone();
414         return;
415     }
416
417     var ws;
418     if ("ws" in WebInspector.queryParamsObject)
419         ws = "ws://" + WebInspector.queryParamsObject.ws;
420     else if ("page" in WebInspector.queryParamsObject) {
421         var page = WebInspector.queryParamsObject.page;
422         var host = "host" in WebInspector.queryParamsObject ? WebInspector.queryParamsObject.host : window.location.host;
423         ws = "ws://" + host + "/devtools/page/" + page;
424     }
425
426     if (ws) {
427         WebInspector.socket = new WebSocket(ws);
428         WebInspector.socket.onmessage = function(message) { InspectorBackend.dispatch(message.data); }
429         WebInspector.socket.onerror = function(error) { console.error(error); }
430         WebInspector.socket.onopen = function() {
431             InspectorFrontendHost.sendMessageToBackend = WebInspector.socket.send.bind(WebInspector.socket);
432             WebInspector.doLoadedDone();
433         }
434         return;
435     }
436
437     WebInspector.doLoadedDone();
438
439     // In case of loading as a web page with no bindings / harness, kick off initialization manually.
440     if (InspectorFrontendHost.isStub) {
441         InspectorFrontendAPI.dispatchQueryParameters();
442         WebInspector._doLoadedDoneWithCapabilities();
443     }
444 }
445
446 WebInspector.doLoadedDone = function()
447 {
448     // Install styles and themes
449     WebInspector.installPortStyles();
450     if (WebInspector.socket)
451         document.body.addStyleClass("remote");
452
453     if (WebInspector.queryParamsObject.toolbarColor && WebInspector.queryParamsObject.textColor)
454         WebInspector.setToolbarColors(WebInspector.queryParamsObject.toolbarColor, WebInspector.queryParamsObject.textColor);
455
456     InspectorFrontendHost.loaded();
457     WebInspector.WorkerManager.loaded();
458
459     DebuggerAgent.causesRecompilation(WebInspector._initializeCapability.bind(WebInspector, "debuggerCausesRecompilation", null));
460     DebuggerAgent.supportsSeparateScriptCompilationAndExecution(WebInspector._initializeCapability.bind(WebInspector, "separateScriptCompilationAndExecutionEnabled", null));
461     ProfilerAgent.causesRecompilation(WebInspector._initializeCapability.bind(WebInspector, "profilerCausesRecompilation", null));
462     ProfilerAgent.isSampling(WebInspector._initializeCapability.bind(WebInspector, "samplingCPUProfiler", null));
463     ProfilerAgent.hasHeapProfiler(WebInspector._initializeCapability.bind(WebInspector, "heapProfilerPresent", null));
464     TimelineAgent.supportsFrameInstrumentation(WebInspector._initializeCapability.bind(WebInspector, "timelineSupportsFrameInstrumentation", null));
465     PageAgent.canOverrideDeviceMetrics(WebInspector._initializeCapability.bind(WebInspector, "canOverrideDeviceMetrics", null));
466     PageAgent.canOverrideGeolocation(WebInspector._initializeCapability.bind(WebInspector, "canOverrideGeolocation", null));
467     PageAgent.canOverrideDeviceOrientation(WebInspector._initializeCapability.bind(WebInspector, "canOverrideDeviceOrientation", WebInspector._doLoadedDoneWithCapabilities.bind(WebInspector)));
468 }
469
470 WebInspector._doLoadedDoneWithCapabilities = function()
471 {
472     WebInspector.shortcutsScreen = new WebInspector.ShortcutsScreen();
473     this._registerShortcuts();
474
475     // set order of some sections explicitly
476     WebInspector.shortcutsScreen.section(WebInspector.UIString("Console"));
477     WebInspector.shortcutsScreen.section(WebInspector.UIString("Elements Panel"));
478
479     this.console = new WebInspector.ConsoleModel();
480     this.console.addEventListener(WebInspector.ConsoleModel.Events.ConsoleCleared, this._updateErrorAndWarningCounts, this);
481     this.console.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, this._updateErrorAndWarningCounts, this);
482     this.console.addEventListener(WebInspector.ConsoleModel.Events.RepeatCountUpdated, this._updateErrorAndWarningCounts, this);
483
484     WebInspector.CSSCompletions.requestCSSNameCompletions();
485
486     this.drawer = new WebInspector.Drawer();
487     this.consoleView = new WebInspector.ConsoleView(WebInspector.WorkerManager.isWorkerFrontend());
488
489     this.networkManager = new WebInspector.NetworkManager();
490     this.resourceTreeModel = new WebInspector.ResourceTreeModel(this.networkManager);
491     this.debuggerModel = new WebInspector.DebuggerModel();
492     this.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.DebuggerPaused, this._debuggerPaused, this);
493     this.networkLog = new WebInspector.NetworkLog();
494     this.domAgent = new WebInspector.DOMAgent();
495     this.javaScriptContextManager = new WebInspector.JavaScriptContextManager(this.resourceTreeModel, this.consoleView);
496
497     this.scriptSnippetModel = new WebInspector.ScriptSnippetModel();
498
499     InspectorBackend.registerInspectorDispatcher(this);
500
501     this.cssModel = new WebInspector.CSSStyleModel();
502     this.timelineManager = new WebInspector.TimelineManager();
503     this.userAgentSupport = new WebInspector.UserAgentSupport();
504     InspectorBackend.registerDatabaseDispatcher(new WebInspector.DatabaseDispatcher());
505     InspectorBackend.registerDOMStorageDispatcher(new WebInspector.DOMStorageDispatcher());
506
507     this.searchController = new WebInspector.SearchController();
508     this.advancedSearchController = new WebInspector.AdvancedSearchController();
509     this.settingsController = new WebInspector.SettingsController();
510
511     this.domBreakpointsSidebarPane = new WebInspector.DOMBreakpointsSidebarPane();
512
513     this._zoomLevel = WebInspector.settings.zoomLevel.get();
514     if (this._zoomLevel)
515         this._requestZoom();
516
517     var autoselectPanel = WebInspector.UIString("a panel chosen automatically");
518     var openAnchorLocationSetting = WebInspector.settings.createSetting("openLinkHandler", autoselectPanel);
519     this.openAnchorLocationRegistry = new WebInspector.HandlerRegistry(openAnchorLocationSetting);
520     this.openAnchorLocationRegistry.registerHandler(autoselectPanel, function() { return false; });
521
522     this.workspace = new WebInspector.Workspace();
523     this.breakpointManager = new WebInspector.BreakpointManager(WebInspector.settings.breakpoints, this.debuggerModel, this.workspace);
524
525     this._createGlobalStatusBarItems();
526
527     WebInspector._installDockToRight();
528
529     this.toolbar = new WebInspector.Toolbar();
530     this.toolbar.setCoalescingUpdate(true);
531     var panelDescriptors = this._panelDescriptors();
532     for (var i = 0; i < panelDescriptors.length; ++i)
533         WebInspector.inspectorView.addPanel(panelDescriptors[i]);
534     this.toolbar.setCoalescingUpdate(false);
535
536     this.addMainEventListeners(document);
537     WebInspector.registerLinkifierPlugin(this._profilesLinkifier.bind(this));
538
539     window.addEventListener("resize", this.windowResize.bind(this), true);
540
541     var errorWarningCount = document.getElementById("error-warning-count");
542     errorWarningCount.addEventListener("click", this.showConsole.bind(this), false);
543     this._updateErrorAndWarningCounts();
544
545     this.extensionServer.initExtensions();
546
547     this.console.enableAgent();
548
549     function showInitialPanel()
550     {
551         if (!WebInspector.inspectorView.currentPanel())
552             WebInspector.showPanel(WebInspector.settings.lastActivePanel.get());
553     }
554
555     InspectorAgent.enable(showInitialPanel);
556     DatabaseAgent.enable();
557     DOMStorageAgent.enable();
558
559     if (!Capabilities.profilerCausesRecompilation || WebInspector.settings.profilerEnabled.get())
560         ProfilerAgent.enable();
561
562     if (WebInspector.settings.showPaintRects.get())
563         PageAgent.setShowPaintRects(true);
564
565     if (WebInspector.settings.javaScriptDisabled.get())
566         PageAgent.setScriptExecutionDisabled(true);
567
568     this.domAgent._emulateTouchEventsChanged();
569
570     WebInspector.WorkerManager.loadCompleted();
571     InspectorFrontendAPI.loadCompleted();
572 }
573
574 WebInspector._installDockToRight = function()
575 {
576     // Re-use Settings infrastructure for the dock-to-right settings UI
577     WebInspector.settings.dockToRight.set(WebInspector.queryParamsObject.dockSide === "right");
578
579     if (WebInspector.settings.dockToRight.get())
580         document.body.addStyleClass("dock-to-right");
581
582     if (WebInspector.attached)
583         WebInspector._setCompactMode(!WebInspector.settings.dockToRight.get());
584
585     WebInspector.settings.dockToRight.addChangeListener(listener.bind(this));
586
587     function listener(event)
588     {
589         var value = WebInspector.settings.dockToRight.get();
590         if (value) {
591             InspectorFrontendHost.requestSetDockSide("right");
592             document.body.addStyleClass("dock-to-right");
593         } else {
594             InspectorFrontendHost.requestSetDockSide("bottom");
595             document.body.removeStyleClass("dock-to-right");
596         }
597         if (WebInspector.attached)
598             WebInspector._setCompactMode(!value);
599         else
600             WebInspector._updateDockButtonState();
601     }
602 }
603
604 var windowLoaded = function()
605 {
606     var localizedStringsURL = InspectorFrontendHost.localizedStringsURL();
607     if (localizedStringsURL) {
608         var localizedStringsScriptElement = document.createElement("script");
609         localizedStringsScriptElement.addEventListener("load", WebInspector.loaded.bind(WebInspector), false);
610         localizedStringsScriptElement.type = "text/javascript";
611         localizedStringsScriptElement.src = localizedStringsURL;
612         document.head.appendChild(localizedStringsScriptElement);
613     } else
614         WebInspector.loaded();
615
616     WebInspector.attached = WebInspector.queryParamsObject.docked === "true";
617
618     window.removeEventListener("DOMContentLoaded", windowLoaded, false);
619     delete windowLoaded;
620 };
621
622 window.addEventListener("DOMContentLoaded", windowLoaded, false);
623
624 // We'd like to enforce asynchronous interaction between the inspector controller and the frontend.
625 // It is needed to prevent re-entering the backend code.
626 // Also, native dispatches do not guarantee setTimeouts to be serialized, so we
627 // enforce serialization using 'messagesToDispatch' queue. It is also important that JSC debugger
628 // tests require that each command was dispatch within individual timeout callback, so we don't batch them.
629
630 var messagesToDispatch = [];
631
632 WebInspector.dispatchQueueIsEmpty = function() {
633     return messagesToDispatch.length == 0;
634 }
635
636 WebInspector.dispatch = function(message) {
637     messagesToDispatch.push(message);
638     setTimeout(function() {
639         InspectorBackend.dispatch(messagesToDispatch.shift());
640     }, 0);
641 }
642
643 WebInspector.dispatchMessageFromBackend = function(messageObject)
644 {
645     WebInspector.dispatch(messageObject);
646 }
647
648 WebInspector.windowResize = function(event)
649 {
650     if (WebInspector.inspectorView)
651         WebInspector.inspectorView.doResize();
652     if (WebInspector.drawer)
653         WebInspector.drawer.resize();
654     if (WebInspector.toolbar)
655         WebInspector.toolbar.resize();
656     if (WebInspector.settingsController)
657         WebInspector.settingsController.resize();
658 }
659
660 WebInspector.setDockingUnavailable = function(unavailable)
661 {
662     this._isDockingUnavailable = unavailable;
663     this._updateDockButtonState();
664 }
665
666 WebInspector.close = function(event)
667 {
668     if (this._isClosing)
669         return;
670     this._isClosing = true;
671     this.notifications.dispatchEventToListeners(WebInspector.Events.InspectorClosing);
672     InspectorFrontendHost.closeWindow();
673 }
674
675 WebInspector.documentClick = function(event)
676 {
677     var anchor = event.target.enclosingNodeOrSelfWithNodeName("a");
678     if (!anchor || anchor.target === "_blank")
679         return;
680
681     // Prevent the link from navigating, since we don't do any navigation by following links normally.
682     event.consume(true);
683
684     function followLink()
685     {
686         if (WebInspector.isBeingEdited(event.target) || WebInspector._showAnchorLocation(anchor))
687             return;
688
689         const profileMatch = WebInspector.ProfileURLRegExp.exec(anchor.href);
690         if (profileMatch) {
691             WebInspector.showProfileForURL(anchor.href);
692             return;
693         }
694
695         var parsedURL = anchor.href.asParsedURL();
696         if (parsedURL && parsedURL.scheme === "webkit-link-action") {
697             if (parsedURL.host === "show-panel") {
698                 var panel = parsedURL.path.substring(1);
699                 if (WebInspector.panel(panel))
700                     WebInspector.showPanel(panel);
701             }
702             return;
703         }
704
705         InspectorFrontendHost.openInNewTab(anchor.href);
706     }
707
708     if (WebInspector.followLinkTimeout)
709         clearTimeout(WebInspector.followLinkTimeout);
710
711     if (anchor.preventFollowOnDoubleClick) {
712         // Start a timeout if this is the first click, if the timeout is canceled
713         // before it fires, then a double clicked happened or another link was clicked.
714         if (event.detail === 1)
715             WebInspector.followLinkTimeout = setTimeout(followLink, 333);
716         return;
717     }
718
719     followLink();
720 }
721
722 WebInspector.openResource = function(resourceURL, inResourcesPanel)
723 {
724     var resource = WebInspector.resourceForURL(resourceURL);
725     if (inResourcesPanel && resource)
726         WebInspector.showPanel("resources").showResource(resource);
727     else
728         InspectorFrontendHost.openInNewTab(resourceURL);
729 }
730
731 WebInspector._registerShortcuts = function()
732 {
733     var shortcut = WebInspector.KeyboardShortcut;
734     var section = WebInspector.shortcutsScreen.section(WebInspector.UIString("All Panels"));
735     var keys = [
736         shortcut.shortcutToString("]", shortcut.Modifiers.CtrlOrMeta),
737         shortcut.shortcutToString("[", shortcut.Modifiers.CtrlOrMeta)
738     ];
739     section.addRelatedKeys(keys, WebInspector.UIString("Go to the panel to the left/right"));
740
741     var keys = [
742         shortcut.shortcutToString("[", shortcut.Modifiers.CtrlOrMeta | shortcut.Modifiers.Alt),
743         shortcut.shortcutToString("]", shortcut.Modifiers.CtrlOrMeta | shortcut.Modifiers.Alt)
744     ];
745     section.addRelatedKeys(keys, WebInspector.UIString("Go back/forward in panel history"));
746
747     section.addKey(shortcut.shortcutToString(shortcut.Keys.Esc), WebInspector.UIString("Toggle console"));
748     section.addKey(shortcut.shortcutToString("f", shortcut.Modifiers.CtrlOrMeta), WebInspector.UIString("Search"));
749
750     var advancedSearchShortcut = WebInspector.AdvancedSearchController.createShortcut();
751     section.addKey(advancedSearchShortcut.name, WebInspector.UIString("Search across all sources"));
752
753     var openResourceShortcut = WebInspector.KeyboardShortcut.makeDescriptor("o", WebInspector.KeyboardShortcut.Modifiers.CtrlOrMeta);
754     section.addKey(openResourceShortcut.name, WebInspector.UIString("Go to source"));
755
756     if (WebInspector.isMac()) {
757         keys = [
758             shortcut.shortcutToString("g", shortcut.Modifiers.Meta),
759             shortcut.shortcutToString("g", shortcut.Modifiers.Meta | shortcut.Modifiers.Shift)
760         ];
761         section.addRelatedKeys(keys, WebInspector.UIString("Find next/previous"));
762     }
763
764     var goToShortcut = WebInspector.GoToLineDialog.createShortcut();
765     section.addKey(goToShortcut.name, WebInspector.UIString("Go to line"));
766 }
767
768 WebInspector.documentKeyDown = function(event)
769 {
770     const helpKey = WebInspector.isMac() ? "U+003F" : "U+00BF"; // "?" for both platforms
771
772     if (event.keyIdentifier === "F1" ||
773         (event.keyIdentifier === helpKey && event.shiftKey && (!WebInspector.isBeingEdited(event.target) || event.metaKey))) {
774         this.settingsController.showSettingsScreen(WebInspector.SettingsScreen.Tabs.Shortcuts);
775         event.consume(true);
776         return;
777     }
778
779     if (WebInspector.currentFocusElement() && WebInspector.currentFocusElement().handleKeyEvent) {
780         WebInspector.currentFocusElement().handleKeyEvent(event);
781         if (event.handled) {
782             event.consume(true);
783             return;
784         }
785     }
786
787     if (WebInspector.inspectorView.currentPanel()) {
788         WebInspector.inspectorView.currentPanel().handleShortcut(event);
789         if (event.handled) {
790             event.consume(true);
791             return;
792         }
793     }
794
795     if (WebInspector.searchController.handleShortcut(event))
796         return;
797     if (WebInspector.advancedSearchController.handleShortcut(event))
798         return;
799
800     switch (event.keyIdentifier) {
801         case "U+004F": // O key
802             if (!event.shiftKey && !event.altKey && WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event)) {
803                 WebInspector.showPanel("scripts").showGoToSourceDialog();
804                 event.consume(true);
805             }
806             break;
807         case "U+0052": // R key
808             if (WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event)) {
809                 PageAgent.reload(event.shiftKey);
810                 event.consume(true);
811             }
812             break;
813         case "F5":
814             if (!WebInspector.isMac()) {
815                 PageAgent.reload(event.ctrlKey || event.shiftKey);
816                 event.consume(true);
817             }
818             break;
819     }
820
821     var isValidZoomShortcut = WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event) &&
822         !event.altKey &&
823         !InspectorFrontendHost.isStub;
824     switch (event.keyCode) {
825         case 107: // +
826         case 187: // +
827             if (isValidZoomShortcut) {
828                 WebInspector._zoomIn();
829                 event.consume(true);
830             }
831             break;
832         case 109: // -
833         case 189: // -
834             if (isValidZoomShortcut) {
835                 WebInspector._zoomOut();
836                 event.consume(true);
837             }
838             break;
839         case 48: // 0
840             // Zoom reset shortcut does not allow "Shift" when handled by the browser.
841             if (isValidZoomShortcut && !event.shiftKey) {
842                 WebInspector._resetZoom();
843                 event.consume(true);
844             }
845             break;
846     }
847
848     // Cmd/Control + Shift + C should be a shortcut to clicking the Node Search Button.
849     // This shortcut matches Firebug.
850     if (event.keyIdentifier === "U+0043") { // C key
851         if (WebInspector.isMac())
852             var isNodeSearchKey = event.metaKey && !event.ctrlKey && !event.altKey && event.shiftKey;
853         else
854             var isNodeSearchKey = event.ctrlKey && !event.metaKey && !event.altKey && event.shiftKey;
855
856         if (isNodeSearchKey) {
857             this._toggleSearchingForNode();
858             event.consume(true);
859             return;
860         }
861         return;
862     }
863 }
864
865 WebInspector.postDocumentKeyDown = function(event)
866 {
867     if (event.handled)
868         return;
869
870     if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) {
871         // If drawer is open with some view other than console then close it.
872         if (!this._toggleConsoleButton.toggled && WebInspector.drawer.visible)
873             this.closeViewInDrawer();
874         else
875             this._toggleConsoleButtonClicked();
876     }
877 }
878
879 WebInspector.documentCanCopy = function(event)
880 {
881     if (WebInspector.inspectorView.currentPanel() && WebInspector.inspectorView.currentPanel().handleCopyEvent)
882         event.preventDefault();
883 }
884
885 WebInspector.documentCopy = function(event)
886 {
887     if (WebInspector.inspectorView.currentPanel() && WebInspector.inspectorView.currentPanel().handleCopyEvent)
888         WebInspector.inspectorView.currentPanel().handleCopyEvent(event);
889     WebInspector.documentCopyEventFired(event);
890 }
891
892 WebInspector.documentCopyEventFired = function(event)
893 {
894 }
895
896 WebInspector.contextMenuEventFired = function(event)
897 {
898     if (event.handled || event.target.hasStyleClass("popup-glasspane"))
899         event.preventDefault();
900 }
901
902 WebInspector.showConsole = function()
903 {
904     if (WebInspector._toggleConsoleButton && !WebInspector._toggleConsoleButton.toggled) {
905         if (WebInspector.drawer.visible)
906             this._closePreviousDrawerView();
907         WebInspector._toggleConsoleButtonClicked();
908     }
909 }
910
911 WebInspector.showPanel = function(panel)
912 {
913     return WebInspector.inspectorView.showPanel(panel);
914 }
915
916 WebInspector.panel = function(panel)
917 {
918     return WebInspector.inspectorView.panel(panel);
919 }
920
921 WebInspector.bringToFront = function()
922 {
923     InspectorFrontendHost.bringToFront();
924 }
925
926 /**
927  * @param {string=} messageLevel
928  * @param {boolean=} showConsole
929  */
930 WebInspector.log = function(message, messageLevel, showConsole)
931 {
932     // remember 'this' for setInterval() callback
933     var self = this;
934
935     // return indication if we can actually log a message
936     function isLogAvailable()
937     {
938         return WebInspector.ConsoleMessage && WebInspector.RemoteObject && self.console;
939     }
940
941     // flush the queue of pending messages
942     function flushQueue()
943     {
944         var queued = WebInspector.log.queued;
945         if (!queued)
946             return;
947
948         for (var i = 0; i < queued.length; ++i)
949             logMessage(queued[i]);
950
951         delete WebInspector.log.queued;
952     }
953
954     // flush the queue if it console is available
955     // - this function is run on an interval
956     function flushQueueIfAvailable()
957     {
958         if (!isLogAvailable())
959             return;
960
961         clearInterval(WebInspector.log.interval);
962         delete WebInspector.log.interval;
963
964         flushQueue();
965     }
966
967     // actually log the message
968     function logMessage(message)
969     {
970         // post the message
971         var msg = WebInspector.ConsoleMessage.create(
972             WebInspector.ConsoleMessage.MessageSource.Other,
973             messageLevel || WebInspector.ConsoleMessage.MessageLevel.Debug,
974             message);
975
976         self.console.addMessage(msg);
977         if (showConsole)
978             WebInspector.showConsole();
979     }
980
981     // if we can't log the message, queue it
982     if (!isLogAvailable()) {
983         if (!WebInspector.log.queued)
984             WebInspector.log.queued = [];
985
986         WebInspector.log.queued.push(message);
987
988         if (!WebInspector.log.interval)
989             WebInspector.log.interval = setInterval(flushQueueIfAvailable, 1000);
990
991         return;
992     }
993
994     // flush the pending queue if any
995     flushQueue();
996
997     // log the message
998     logMessage(message);
999 }
1000
1001 WebInspector.showErrorMessage = function(error)
1002 {
1003     WebInspector.log(error, WebInspector.ConsoleMessage.MessageLevel.Error, true);
1004 }
1005
1006 WebInspector.inspect = function(payload, hints)
1007 {
1008     var object = WebInspector.RemoteObject.fromPayload(payload);
1009     if (object.subtype === "node") {
1010         function callback(nodeId)
1011         {
1012             WebInspector._updateFocusedNode(nodeId);
1013             object.release();
1014         }
1015         object.pushNodeToFrontend(callback);
1016         return;
1017     }
1018
1019     if (hints.databaseId)
1020         WebInspector.showPanel("resources").selectDatabase(hints.databaseId);
1021     else if (hints.domStorageId)
1022         WebInspector.showPanel("resources").selectDOMStorage(hints.domStorageId);
1023
1024     object.release();
1025 }
1026
1027 WebInspector._updateFocusedNode = function(nodeId)
1028 {
1029     if (WebInspector._nodeSearchButton.toggled) {
1030         InspectorFrontendHost.bringToFront();
1031         WebInspector._nodeSearchButton.toggled = false;
1032     }
1033     WebInspector.showPanel("elements").revealAndSelectNode(nodeId);
1034 }
1035
1036 WebInspector._showAnchorLocation = function(anchor)
1037 {
1038     if (WebInspector.openAnchorLocationRegistry.dispatch({ url: anchor.href, lineNumber: anchor.lineNumber}))
1039         return true;
1040     var preferredPanel = this.panels[anchor.preferredPanel];
1041     if (preferredPanel && WebInspector._showAnchorLocationInPanel(anchor, preferredPanel))
1042         return true;
1043     if (WebInspector._showAnchorLocationInPanel(anchor, this.panel("scripts")))
1044         return true;
1045     if (WebInspector._showAnchorLocationInPanel(anchor, this.panel("resources")))
1046         return true;
1047     if (WebInspector._showAnchorLocationInPanel(anchor, this.panel("network")))
1048         return true;
1049     return false;
1050 }
1051
1052 WebInspector._showAnchorLocationInPanel = function(anchor, panel)
1053 {
1054     if (!panel || !panel.canShowAnchorLocation(anchor))
1055         return false;
1056
1057     // FIXME: support webkit-html-external-link links here.
1058     if (anchor.hasStyleClass("webkit-html-external-link")) {
1059         anchor.removeStyleClass("webkit-html-external-link");
1060         anchor.addStyleClass("webkit-html-resource-link");
1061     }
1062
1063     this.showPanelForAnchorNavigation(panel);
1064     panel.showAnchorLocation(anchor);
1065     return true;
1066 }
1067
1068 WebInspector.showPanelForAnchorNavigation = function(panel)
1069 {
1070     WebInspector.searchController.disableSearchUntilExplicitAction();
1071     WebInspector.inspectorView.setCurrentPanel(panel);
1072 }
1073
1074 WebInspector.showProfileForURL = function(url)
1075 {
1076     WebInspector.showPanel("profiles").showProfileForURL(url);
1077 }
1078
1079 WebInspector.evaluateInConsole = function(expression, showResultOnly)
1080 {
1081     this.showConsole();
1082     this.consoleView.evaluateUsingTextPrompt(expression, showResultOnly);
1083 }
1084
1085 WebInspector.addMainEventListeners = function(doc)
1086 {
1087     doc.addEventListener("keydown", this.documentKeyDown.bind(this), true);
1088     doc.addEventListener("keydown", this.postDocumentKeyDown.bind(this), false);
1089     doc.addEventListener("beforecopy", this.documentCanCopy.bind(this), true);
1090     doc.addEventListener("copy", this.documentCopy.bind(this), true);
1091     doc.addEventListener("contextmenu", this.contextMenuEventFired.bind(this), true);
1092     doc.addEventListener("click", this.documentClick.bind(this), true);
1093 }
1094
1095 WebInspector.frontendReused = function()
1096 {
1097     this.resourceTreeModel.frontendReused();
1098 }
1099
1100 WebInspector.ProfileURLRegExp = /webkit-profile:\/\/(.+)\/(.+)#([0-9]+)/;