Web Inspector: Add infrastructure for eslint based static analyzer
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Base / Main.js
1 /*
2  * Copyright (C) 2013-2014 Apple Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 WebInspector.ContentViewCookieType = {
27     ApplicationCache: "application-cache",
28     CookieStorage: "cookie-storage",
29     Database: "database",
30     DatabaseTable: "database-table",
31     DOMStorage: "dom-storage",
32     Resource: "resource", // includes Frame too.
33     Timelines: "timelines",
34 };
35
36 WebInspector.DebuggableType = {
37     Web: "web",
38     JavaScript: "javascript"
39 };
40
41 WebInspector.SelectedSidebarPanelCookieKey = "selected-sidebar-panel";
42 WebInspector.TypeIdentifierCookieKey = "represented-object-type";
43
44 WebInspector.loaded = function()
45 {
46     // Initialize WebSocket to communication.
47     this._initializeWebSocketIfNeeded();
48
49     this.debuggableType = InspectorFrontendHost.debuggableType() === "web" ? WebInspector.DebuggableType.Web : WebInspector.DebuggableType.JavaScript;
50     this.hasExtraDomains = false;
51
52     // Register observers for events from the InspectorBackend.
53     if (InspectorBackend.registerInspectorDispatcher)
54         InspectorBackend.registerInspectorDispatcher(new WebInspector.InspectorObserver);
55     if (InspectorBackend.registerPageDispatcher)
56         InspectorBackend.registerPageDispatcher(new WebInspector.PageObserver);
57     if (InspectorBackend.registerConsoleDispatcher)
58         InspectorBackend.registerConsoleDispatcher(new WebInspector.ConsoleObserver);
59     if (InspectorBackend.registerNetworkDispatcher)
60         InspectorBackend.registerNetworkDispatcher(new WebInspector.NetworkObserver);
61     if (InspectorBackend.registerDOMDispatcher)
62         InspectorBackend.registerDOMDispatcher(new WebInspector.DOMObserver);
63     if (InspectorBackend.registerDebuggerDispatcher)
64         InspectorBackend.registerDebuggerDispatcher(new WebInspector.DebuggerObserver);
65     if (InspectorBackend.registerDatabaseDispatcher)
66         InspectorBackend.registerDatabaseDispatcher(new WebInspector.DatabaseObserver);
67     if (InspectorBackend.registerDOMStorageDispatcher)
68         InspectorBackend.registerDOMStorageDispatcher(new WebInspector.DOMStorageObserver);
69     if (InspectorBackend.registerApplicationCacheDispatcher)
70         InspectorBackend.registerApplicationCacheDispatcher(new WebInspector.ApplicationCacheObserver);
71     if (InspectorBackend.registerTimelineDispatcher)
72         InspectorBackend.registerTimelineDispatcher(new WebInspector.TimelineObserver);
73     if (InspectorBackend.registerCSSDispatcher)
74         InspectorBackend.registerCSSDispatcher(new WebInspector.CSSObserver);
75     if (InspectorBackend.registerLayerTreeDispatcher)
76         InspectorBackend.registerLayerTreeDispatcher(new WebInspector.LayerTreeObserver);
77     if (InspectorBackend.registerRuntimeDispatcher)
78         InspectorBackend.registerRuntimeDispatcher(new WebInspector.RuntimeObserver);
79     if (InspectorBackend.registerReplayDispatcher)
80         InspectorBackend.registerReplayDispatcher(new WebInspector.ReplayObserver);
81
82     // Enable agents.
83     if (window.InspectorAgent)
84         InspectorAgent.enable();
85
86     // Perform one-time tasks.
87     WebInspector.CSSCompletions.requestCSSNameCompletions();
88     this._generateDisclosureTriangleImages();
89
90     // Listen for the ProvisionalLoadStarted event before registering for events so our code gets called before any managers or sidebars.
91     // This lets us save a state cookie before any managers or sidebars do any resets that would affect state (namely TimelineManager).
92     WebInspector.Frame.addEventListener(WebInspector.Frame.Event.ProvisionalLoadStarted, this._provisionalLoadStarted, this);
93
94     // Create the singleton managers next, before the user interface elements, so the user interface can register
95     // as event listeners on these managers.
96     this.branchManager = new WebInspector.BranchManager;
97     this.frameResourceManager = new WebInspector.FrameResourceManager;
98     this.storageManager = new WebInspector.StorageManager;
99     this.domTreeManager = new WebInspector.DOMTreeManager;
100     this.cssStyleManager = new WebInspector.CSSStyleManager;
101     this.logManager = new WebInspector.LogManager;
102     this.issueManager = new WebInspector.IssueManager;
103     this.analyzerManager = new WebInspector.AnalyzerManager;
104     this.runtimeManager = new WebInspector.RuntimeManager;
105     this.applicationCacheManager = new WebInspector.ApplicationCacheManager;
106     this.timelineManager = new WebInspector.TimelineManager;
107     this.debuggerManager = new WebInspector.DebuggerManager;
108     this.sourceMapManager = new WebInspector.SourceMapManager;
109     this.layerTreeManager = new WebInspector.LayerTreeManager;
110     this.dashboardManager = new WebInspector.DashboardManager;
111     this.probeManager = new WebInspector.ProbeManager;
112     this.replayManager = new WebInspector.ReplayManager;
113
114     // Enable the Console Agent after creating the singleton managers.
115     if (window.ConsoleAgent)
116         ConsoleAgent.enable();
117
118     // Tell the backend we are initialized after all our initialization messages have been sent.
119     setTimeout(function() {
120         if (window.InspectorAgent && InspectorAgent.initialized)
121             InspectorAgent.initialized();
122     }, 0);
123
124     // Register for events.
125     this.replayManager.addEventListener(WebInspector.ReplayManager.Event.CaptureStarted, this._captureDidStart, this);
126     this.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Paused, this._debuggerDidPause, this);
127     this.debuggerManager.addEventListener(WebInspector.DebuggerManager.Event.Resumed, this._debuggerDidResume, this);
128     this.domTreeManager.addEventListener(WebInspector.DOMTreeManager.Event.InspectModeStateChanged, this._inspectModeStateChanged, this);
129     this.domTreeManager.addEventListener(WebInspector.DOMTreeManager.Event.DOMNodeWasInspected, this._domNodeWasInspected, this);
130     this.frameResourceManager.addEventListener(WebInspector.FrameResourceManager.Event.MainFrameDidChange, this._mainFrameDidChange, this);
131
132     WebInspector.Frame.addEventListener(WebInspector.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
133
134     document.addEventListener("DOMContentLoaded", this.contentLoaded.bind(this));
135
136     // Create settings.
137     this._lastInspectorViewStateCookieSetting = new WebInspector.Setting("last-content-view-state-cookie", {});
138
139     this._navigationSidebarCollapsedSetting = new WebInspector.Setting("navigation-sidebar-collapsed", false);
140     this._navigationSidebarWidthSetting = new WebInspector.Setting("navigation-sidebar-width", null);
141
142     this._lastSelectedDetailsSidebarPanelSetting = new WebInspector.Setting("last-selected-details-sidebar-panel", null);
143     this._detailsSidebarCollapsedSetting = new WebInspector.Setting("details-sidebar-collapsed", true);
144     this._detailsSidebarWidthSetting = new WebInspector.Setting("details-sidebar-width", null);
145
146     this._toolbarDockedRightDisplayModeSetting = new WebInspector.Setting("toolbar-docked-right-display-mode", WebInspector.Toolbar.DisplayMode.IconAndLabelVertical);
147     this._toolbarDockedRightSizeModeSetting = new WebInspector.Setting("toolbar-docked-right-size-mode",WebInspector.Toolbar.SizeMode.Normal);
148
149     this._toolbarDockedBottomDisplayModeSetting = new WebInspector.Setting("toolbar-docked-display-mode", WebInspector.Toolbar.DisplayMode.IconAndLabelHorizontal);
150     this._toolbarDockedBottomSizeModeSetting = new WebInspector.Setting("toolbar-docked-size-mode",WebInspector.Toolbar.SizeMode.Small);
151
152     this._toolbarUndockedDisplayModeSetting = new WebInspector.Setting("toolbar-undocked-display-mode", WebInspector.Toolbar.DisplayMode.IconAndLabelVertical);
153     this._toolbarUndockedSizeModeSetting = new WebInspector.Setting("toolbar-undocked-size-mode",WebInspector.Toolbar.SizeMode.Normal);
154
155     this._showingSplitConsoleSetting = new WebInspector.Setting("showing-split-console", false);
156     this._splitConsoleHeightSetting = new WebInspector.Setting("split-console-height", 150);
157
158     this._dockButtonToggledSetting = new WebInspector.Setting("dock-button-toggled", false);
159
160     this.showShadowDOMSetting = new WebInspector.Setting("show-shadow-dom", false);
161     this.showReplayInterfaceSetting = new WebInspector.Setting("show-web-replay", false);
162
163     this.showJavaScriptTypeInformationSetting = new WebInspector.Setting("show-javascript-type-information", false);
164     if (this.showJavaScriptTypeInformationSetting.value && window.RuntimeAgent && RuntimeAgent.enableTypeProfiler)
165         RuntimeAgent.enableTypeProfiler();
166
167     this.showPaintRectsSetting = new WebInspector.Setting("show-paint-rects", false);
168     if (this.showPaintRectsSetting.value && window.PageAgent && PageAgent.setShowPaintRects)
169         PageAgent.setShowPaintRects(true);
170
171     this.mouseCoords = {
172         x: 0,
173         y: 0
174     };
175
176     this._windowKeydownListeners = [];
177 };
178
179 WebInspector.contentLoaded = function()
180 {
181     // Register for global events.
182     document.addEventListener("beforecopy", this._beforecopy.bind(this));
183     document.addEventListener("copy", this._copy.bind(this));
184
185     document.addEventListener("click", this._mouseWasClicked.bind(this));
186     document.addEventListener("dragover", this._dragOver.bind(this));
187     document.addEventListener("focus", WebInspector._focusChanged.bind(this), true);
188
189     window.addEventListener("focus", this._windowFocused.bind(this));
190     window.addEventListener("blur", this._windowBlurred.bind(this));
191     window.addEventListener("resize", this._windowResized.bind(this));
192     window.addEventListener("keydown", this._windowKeyDown.bind(this));
193     window.addEventListener("keyup", this._windowKeyUp.bind(this));
194     window.addEventListener("mousemove", this._mouseMoved.bind(this), true);
195     window.addEventListener("pagehide", this._pageHidden.bind(this));
196
197     // Add platform style classes so the UI can be tweaked per-platform.
198     document.body.classList.add(WebInspector.Platform.name + "-platform");
199     if (WebInspector.Platform.isNightlyBuild)
200         document.body.classList.add("nightly-build");
201     if (WebInspector.Platform.isLegacyMacOS)
202         document.body.classList.add("legacy");
203     if (WebInspector.Platform.version.name)
204         document.body.classList.add(WebInspector.Platform.version.name);
205
206     document.body.classList.add(this.debuggableType);
207
208     // Create the user interface elements.
209     this.toolbar = new WebInspector.Toolbar(document.getElementById("toolbar"));
210     this.toolbar.addEventListener(WebInspector.Toolbar.Event.DisplayModeDidChange, this._toolbarDisplayModeDidChange, this);
211     this.toolbar.addEventListener(WebInspector.Toolbar.Event.SizeModeDidChange, this._toolbarSizeModeDidChange, this);
212
213     var contentElement = document.getElementById("content");
214     contentElement.setAttribute("role", "main");
215     contentElement.setAttribute("aria-label", WebInspector.UIString("Content"));
216
217     this.contentBrowser = new WebInspector.ContentBrowser(document.getElementById("content-browser"), this);
218     this.contentBrowser.addEventListener(WebInspector.ContentBrowser.Event.CurrentRepresentedObjectsDidChange, this._contentBrowserRepresentedObjectsDidChange, this);
219     this.contentBrowser.addEventListener(WebInspector.ContentBrowser.Event.CurrentContentViewDidChange, this._contentBrowserCurrentContentViewDidChange, this);
220
221     this.splitContentBrowser = new WebInspector.ContentBrowser(document.getElementById("split-content-browser"), this, true);
222     this.splitContentBrowser.navigationBar.element.addEventListener("mousedown", this._consoleResizerMouseDown.bind(this));
223
224     this.quickConsole = new WebInspector.QuickConsole(document.getElementById("quick-console"));
225     this.quickConsole.addEventListener(WebInspector.QuickConsole.Event.DidResize, this._quickConsoleDidResize, this);
226
227     this._consoleRepresentedObject = new WebInspector.LogObject;
228     this._consoleTreeElement = new WebInspector.LogTreeElement(this._consoleRepresentedObject);
229     this.consoleContentView = WebInspector.contentBrowser.contentViewForRepresentedObject(this._consoleRepresentedObject);
230
231     // FIXME: The sidebars should be flipped in RTL languages.
232     this.leftSidebar = this.navigationSidebar = new WebInspector.Sidebar(document.getElementById("navigation-sidebar"), WebInspector.Sidebar.Sides.Left);
233     this.navigationSidebar.addEventListener(WebInspector.Sidebar.Event.CollapsedStateDidChange, this._sidebarCollapsedStateDidChange, this);
234     this.navigationSidebar.addEventListener(WebInspector.Sidebar.Event.WidthDidChange, this._sidebarWidthDidChange, this);
235     this.navigationSidebar.addEventListener(WebInspector.Sidebar.Event.SidebarPanelSelected, this._navigationSidebarPanelSelected, this);
236
237     this.rightSidebar = this.detailsSidebar = new WebInspector.Sidebar(document.getElementById("details-sidebar"), WebInspector.Sidebar.Sides.Right, null, null, WebInspector.UIString("Details"));
238     this.detailsSidebar.addEventListener(WebInspector.Sidebar.Event.CollapsedStateDidChange, this._sidebarCollapsedStateDidChange, this);
239     this.detailsSidebar.addEventListener(WebInspector.Sidebar.Event.WidthDidChange, this._sidebarWidthDidChange, this);
240     this.detailsSidebar.addEventListener(WebInspector.Sidebar.Event.SidebarPanelSelected, this._detailsSidebarPanelSelected, this);
241
242     this._reloadPageKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl, "R", this._reloadPage.bind(this));
243     this._reloadPageIgnoringCacheKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl | WebInspector.KeyboardShortcut.Modifier.Shift, "R", this._reloadPageIgnoringCache.bind(this));
244
245     this._consoleKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.Option | WebInspector.KeyboardShortcut.Modifier.CommandOrControl, "C", this.toggleConsoleView.bind(this));
246
247     this._inspectModeKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl | WebInspector.KeyboardShortcut.Modifier.Shift, "C", this._toggleInspectMode.bind(this));
248
249     this._undoKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl, "Z", this._undoKeyboardShortcut.bind(this));
250     this._redoKeyboardShortcut = new WebInspector.KeyboardShortcut(WebInspector.KeyboardShortcut.Modifier.CommandOrControl | WebInspector.KeyboardShortcut.Modifier.Shift, "Z", this._redoKeyboardShortcut.bind(this));
251     this._undoKeyboardShortcut.implicitlyPreventsDefault = this._redoKeyboardShortcut.implicitlyPreventsDefault = false;
252
253     this.undockButtonNavigationItem = new WebInspector.ToggleControlToolbarItem("undock", WebInspector.UIString("Detach into separate window"), "", platformImagePath("Undock.svg"), "", 16, 14);
254     this.undockButtonNavigationItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._undock, this);
255
256     this.closeButtonNavigationItem = new WebInspector.ControlToolbarItem("dock-close", WebInspector.UIString("Close"), platformImagePath("Close.svg"), 16, 14);
257     this.closeButtonNavigationItem.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this.close, this);
258
259     this.toolbar.addToolbarItem(this.closeButtonNavigationItem, WebInspector.Toolbar.Section.Control);
260     this.toolbar.addToolbarItem(this.undockButtonNavigationItem, WebInspector.Toolbar.Section.Control);
261
262     this.resourceSidebarPanel = new WebInspector.ResourceSidebarPanel;
263     this.timelineSidebarPanel = new WebInspector.TimelineSidebarPanel;
264     this.debuggerSidebarPanel = new WebInspector.DebuggerSidebarPanel;
265
266     this.navigationSidebar.addSidebarPanel(this.resourceSidebarPanel);
267     // FIXME: Enable timelines panel for JavaScript inspection.
268     if (this.debuggableType !== WebInspector.DebuggableType.JavaScript)
269         this.navigationSidebar.addSidebarPanel(this.timelineSidebarPanel);
270     this.navigationSidebar.addSidebarPanel(this.debuggerSidebarPanel);
271
272     this.toolbar.addToolbarItem(this.resourceSidebarPanel.toolbarItem, WebInspector.Toolbar.Section.Left);
273     // FIXME: Enable timelines panel for JavaScript inspection.
274     if (this.debuggableType !== WebInspector.DebuggableType.JavaScript)
275         this.toolbar.addToolbarItem(this.timelineSidebarPanel.toolbarItem, WebInspector.Toolbar.Section.Left);
276     this.toolbar.addToolbarItem(this.debuggerSidebarPanel.toolbarItem, WebInspector.Toolbar.Section.Left);
277
278     // The toolbar button for the console.
279     var toolTip = WebInspector.UIString("Show console (%s)").format(WebInspector._consoleKeyboardShortcut.displayName);
280     var activatedToolTip = WebInspector.UIString("Hide console (%s)").format(WebInspector._consoleKeyboardShortcut.displayName);
281     this._consoleToolbarButton = new WebInspector.ActivateButtonToolbarItem("console", toolTip, activatedToolTip, WebInspector.UIString("Console"), "Images/NavigationItemLog.svg");
282     this._consoleToolbarButton.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this.toggleConsoleView, this);
283     this.toolbar.addToolbarItem(this._consoleToolbarButton, WebInspector.Toolbar.Section.Center);
284
285     this.dashboardContainer = new WebInspector.DashboardContainerView;
286     this.dashboardContainer.showDashboardViewForRepresentedObject(this.dashboardManager.dashboards.default);
287
288     this.toolbar.addToolbarItem(this.dashboardContainer.toolbarItem, WebInspector.Toolbar.Section.Center);
289
290     // The toolbar button for node inspection.
291     if (this.debuggableType === WebInspector.DebuggableType.Web) {
292         var toolTip = WebInspector.UIString("Enable point to inspect mode (%s)").format(WebInspector._inspectModeKeyboardShortcut.displayName);
293         var activatedToolTip = WebInspector.UIString("Disable point to inspect mode (%s)").format(WebInspector._inspectModeKeyboardShortcut.displayName);
294         this._inspectModeToolbarButton = new WebInspector.ActivateButtonToolbarItem("inspect", toolTip, activatedToolTip, WebInspector.UIString("Inspect"), "Images/Crosshair.svg");
295         this._inspectModeToolbarButton.addEventListener(WebInspector.ButtonNavigationItem.Event.Clicked, this._toggleInspectMode, this);
296         this.toolbar.addToolbarItem(this._inspectModeToolbarButton, WebInspector.Toolbar.Section.Center);
297     }
298
299     this.resourceDetailsSidebarPanel = new WebInspector.ResourceDetailsSidebarPanel;
300     this.domNodeDetailsSidebarPanel = new WebInspector.DOMNodeDetailsSidebarPanel;
301     this.cssStyleDetailsSidebarPanel = new WebInspector.CSSStyleDetailsSidebarPanel;
302     this.applicationCacheDetailsSidebarPanel = new WebInspector.ApplicationCacheDetailsSidebarPanel;
303     this.scopeChainDetailsSidebarPanel = new WebInspector.ScopeChainDetailsSidebarPanel;
304     this.probeDetailsSidebarPanel = new WebInspector.ProbeDetailsSidebarPanel;
305
306     this.detailsSidebarPanels = [this.resourceDetailsSidebarPanel, this.applicationCacheDetailsSidebarPanel, this.scopeChainDetailsSidebarPanel,
307         this.domNodeDetailsSidebarPanel, this.cssStyleDetailsSidebarPanel, this.probeDetailsSidebarPanel];
308
309     if (window.LayerTreeAgent) {
310         this.layerTreeDetailsSidebarPanel = new WebInspector.LayerTreeDetailsSidebarPanel;
311         this.detailsSidebarPanels.splice(this.detailsSidebarPanels.length - 1, 0, this.layerTreeDetailsSidebarPanel);
312     }
313
314     this.modifierKeys = {altKey: false, metaKey: false, shiftKey: false};
315
316     // Add the items in reverse order since the last items appear and disappear the least. So they
317     // will not cause the other buttons to visually shift around, keeping things more stable.
318     for (var i = this.detailsSidebarPanels.length - 1; i >= 0; --i) {
319         var toolbarItem = this.detailsSidebarPanels[i].toolbarItem;
320         toolbarItem.hidden = true;
321         this.toolbar.addToolbarItem(toolbarItem, WebInspector.Toolbar.Section.Right);
322     }
323
324     this.toolbar.element.addEventListener("mousedown", this._toolbarMouseDown.bind(this));
325     document.getElementById("docked-resizer").addEventListener("mousedown", this._dockedResizerMouseDown.bind(this));
326
327     this._updateToolbarHeight();
328
329     if (this._navigationSidebarWidthSetting.value)
330         this.navigationSidebar.width = this._navigationSidebarWidthSetting.value;
331
332     if (this._detailsSidebarWidthSetting.value)
333         this.detailsSidebar.width = this._detailsSidebarWidthSetting.value;
334
335     // Signal that the frontend is now ready to receive messages.
336     InspectorFrontendAPI.loadCompleted();
337
338     // Tell the InspectorFrontendHost we loaded, which causes the window to display
339     // and pending InspectorFrontendAPI commands to be sent.
340     InspectorFrontendHost.loaded();
341
342     // Set collapsed after loading the pending frontend commands are dispatched so only the final
343     // selected sidebar panel gets shown and has a say in what content view gets shown.
344     this.navigationSidebar.collapsed = this._navigationSidebarCollapsedSetting.value;
345
346     // If InspectorFrontendAPI didn't show a content view, then try to restore the last saved view state.
347     if (!this.contentBrowser.currentContentView && !this.ignoreLastContentCookie)
348         this._restoreInspectorViewStateFromCookie(this._lastInspectorViewStateCookieSetting.value);
349
350     this._updateSplitConsoleHeight(this._splitConsoleHeightSetting.value);
351
352     if (this._showingSplitConsoleSetting.value)
353         this.showSplitConsole();
354
355     this._contentLoaded = true;
356 }
357
358 WebInspector.activateExtraDomains = function(domains)
359 {
360     console.assert(!this.hasExtraDomains);
361     this.hasExtraDomains = true;
362
363     for (var domain of domains) {
364         var agent = InspectorBackend.activateDomain(domain);
365         if (agent.enable)
366             agent.enable();
367     }
368
369     this.notifications.dispatchEventToListeners(WebInspector.Notification.ExtraDomainsActivated);
370
371     WebInspector.CSSCompletions.requestCSSNameCompletions();
372 }
373
374 WebInspector.sidebarPanelForCurrentContentView = function()
375 {
376     var currentContentView = this.contentBrowser.currentContentView;
377     if (!currentContentView)
378         return null;
379     return this.sidebarPanelForRepresentedObject(currentContentView.representedObject);
380 }
381
382 WebInspector.sidebarPanelForRepresentedObject = function(representedObject)
383 {
384     if (representedObject instanceof WebInspector.Frame || representedObject instanceof WebInspector.Resource ||
385         representedObject instanceof WebInspector.Script || representedObject instanceof WebInspector.ContentFlow)
386         return this.resourceSidebarPanel;
387
388     if (representedObject instanceof WebInspector.DOMStorageObject || representedObject instanceof WebInspector.CookieStorageObject ||
389         representedObject instanceof WebInspector.DatabaseTableObject || representedObject instanceof WebInspector.DatabaseObject ||
390         representedObject instanceof WebInspector.ApplicationCacheFrame || representedObject instanceof WebInspector.IndexedDatabaseObjectStore ||
391         representedObject instanceof WebInspector.IndexedDatabaseObjectStoreIndex)
392         return this.resourceSidebarPanel;
393
394     if (representedObject instanceof WebInspector.TimelineRecording)
395         return this.timelineSidebarPanel;
396
397     // The console does not have a sidebar.
398     if (representedObject instanceof WebInspector.LogObject)
399         return null;
400
401     console.error("Unknown representedObject: ", representedObject);
402     return null;
403 }
404
405 WebInspector.contentBrowserTreeElementForRepresentedObject = function(contentBrowser, representedObject)
406 {
407     // The console does not have a sidebar, so return a tree element here so something is shown.
408     if (representedObject instanceof WebInspector.LogObject)
409         return this._consoleTreeElement;
410
411     var sidebarPanel = this.sidebarPanelForRepresentedObject(representedObject);
412     if (sidebarPanel)
413         return sidebarPanel.treeElementForRepresentedObject(representedObject);
414     return null;
415 }
416
417 WebInspector.updateWindowTitle = function()
418 {
419     var mainFrame = this.frameResourceManager.mainFrame;
420     console.assert(mainFrame);
421
422     var urlComponents = mainFrame.mainResource.urlComponents;
423
424     var lastPathComponent;
425     try {
426         lastPathComponent = decodeURIComponent(urlComponents.lastPathComponent || "");
427     } catch (e) {
428         lastPathComponent = urlComponents.lastPathComponent;
429     }
430
431     // Build a title based on the URL components.
432     if (urlComponents.host && lastPathComponent)
433         var title = this.displayNameForHost(urlComponents.host) + " \u2014 " + lastPathComponent;
434     else if (urlComponents.host)
435         var title = this.displayNameForHost(urlComponents.host);
436     else if (lastPathComponent)
437         var title = lastPathComponent;
438     else
439         var title = mainFrame.url;
440
441     // The name "inspectedURLChanged" sounds like the whole URL is required, however this is only
442     // used for updating the window title and it can be any string.
443     InspectorFrontendHost.inspectedURLChanged(title);
444 }
445
446 WebInspector.updateDockedState = function(side)
447 {
448     if (this._dockSide === side)
449         return;
450
451     this._dockSide = side;
452
453     this.docked = side !== "undocked";
454
455     this._ignoreToolbarModeDidChangeEvents = true;
456
457     if (side === "bottom") {
458         document.body.classList.add("docked");
459         document.body.classList.add("bottom");
460
461         document.body.classList.remove("window-inactive");
462         document.body.classList.remove("right");
463
464         this.toolbar.displayMode = this._toolbarDockedBottomDisplayModeSetting.value;
465         this.toolbar.sizeMode = this._toolbarDockedBottomSizeModeSetting.value;
466     } else if (side === "right") {
467         document.body.classList.add("docked");
468         document.body.classList.add("right");
469
470         document.body.classList.remove("window-inactive");
471         document.body.classList.remove("bottom");
472
473         this.toolbar.displayMode = this._toolbarDockedRightDisplayModeSetting.value;
474         this.toolbar.sizeMode = this._toolbarDockedRightSizeModeSetting.value;
475     } else {
476         document.body.classList.remove("docked");
477         document.body.classList.remove("right");
478         document.body.classList.remove("bottom");
479
480         this.toolbar.displayMode = this._toolbarUndockedDisplayModeSetting.value;
481         this.toolbar.sizeMode = this._toolbarUndockedSizeModeSetting.value;
482     }
483
484     this._ignoreToolbarModeDidChangeEvents = false;
485
486     this._updateDockNavigationItems();
487     this._updateToolbarHeight();
488 }
489
490 WebInspector.handlePossibleLinkClick = function(event, frame, alwaysOpenExternally)
491 {
492     var anchorElement = event.target.enclosingNodeOrSelfWithNodeName("a");
493     if (!anchorElement || !anchorElement.href)
494         return false;
495
496     if (WebInspector.isBeingEdited(anchorElement)) {
497         // Don't follow the link when it is being edited.
498         return false;
499     }
500
501     // Prevent the link from navigating, since we don't do any navigation by following links normally.
502     event.preventDefault();
503     event.stopPropagation();
504
505     this.openURL(anchorElement.href, frame, false, anchorElement.lineNumber);
506
507     return true;
508 }
509
510 WebInspector.openURL = function(url, frame, alwaysOpenExternally, lineNumber)
511 {
512     console.assert(url);
513     if (!url)
514         return;
515
516     // If alwaysOpenExternally is not defined, base it off the command/meta key for the current event.
517     if (alwaysOpenExternally === undefined || alwaysOpenExternally === null)
518         alwaysOpenExternally = window.event ? window.event.metaKey : false;
519
520     if (alwaysOpenExternally) {
521         InspectorFrontendHost.openInNewTab(url);
522         return;
523     }
524
525     var searchChildFrames = false;
526     if (!frame) {
527         frame = this.frameResourceManager.mainFrame;
528         searchChildFrames = true;
529     }
530
531     console.assert(frame);
532
533     // WebInspector.Frame.resourceForURL does not check the main resource, only sub-resources. So check both.
534     var resource = frame.url === url ? frame.mainResource : frame.resourceForURL(url, searchChildFrames);
535     if (resource) {
536         var position = new WebInspector.SourceCodePosition(lineNumber, 0);
537         this.resourceSidebarPanel.showSourceCode(resource, position);
538         return;
539     }
540
541     InspectorFrontendHost.openInNewTab(url);
542 }
543
544 WebInspector.close = function()
545 {
546     if (this._isClosing)
547         return;
548
549     this._isClosing = true;
550
551     InspectorFrontendHost.closeWindow();
552 }
553
554 WebInspector.isConsoleFocused = function()
555 {
556     return this.quickConsole.prompt.focused;
557 }
558
559 WebInspector.isShowingSplitConsole = function()
560 {
561     return !this.splitContentBrowser.element.classList.contains("hidden");
562 }
563
564 WebInspector.currentViewSupportsSplitContentBrowser = function()
565 {
566     var currentContentView = this.contentBrowser.currentContentView;
567     return !currentContentView || currentContentView.supportsSplitContentBrowser;
568 }
569
570 WebInspector.toggleSplitConsole = function()
571 {
572     if (!this.currentViewSupportsSplitContentBrowser()) {
573         this.toggleConsoleView();
574         return;
575     }
576
577     if (this.isShowingSplitConsole())
578         this.hideSplitConsole();
579     else
580         this.showSplitConsole();
581 }
582
583 WebInspector.showSplitConsole = function()
584 {
585     if (!this.currentViewSupportsSplitContentBrowser()) {
586         this.showFullHeightConsole();
587         return;
588     }
589
590     this.splitContentBrowser.element.classList.remove("hidden");
591
592     this._showingSplitConsoleSetting.value = true;
593
594     if (this.splitContentBrowser.currentContentView !== this.consoleContentView) {
595         // Be sure to close any existing log view in the main content browser before showing it in the
596         // split content browser. We can only show a content view in one browser at a time.
597         this.contentBrowser.contentViewContainer.closeAllContentViewsOfPrototype(WebInspector.LogContentView);
598         this.splitContentBrowser.showContentView(this.consoleContentView);
599     } else {
600         // This causes the view to know it was shown and focus the prompt.
601         this.splitContentBrowser.contentViewContainer.shown();
602     }
603
604     if (this._wasShowingNavigationSidebarBeforeFullHeightConsole)
605         this.navigationSidebar.collapsed = false;
606
607     this.quickConsole.consoleLogVisibilityChanged(true);
608 }
609
610 WebInspector.hideSplitConsole = function()
611 {
612     this.splitContentBrowser.element.classList.add("hidden");
613
614     this._showingSplitConsoleSetting.value = false;
615
616     // This causes the view to know it was hidden.
617     this.splitContentBrowser.contentViewContainer.hidden();
618
619     this.quickConsole.consoleLogVisibilityChanged(false);
620 }
621
622 WebInspector.showFullHeightConsole = function(scope)
623 {
624     this.splitContentBrowser.element.classList.add("hidden");
625
626     this._showingSplitConsoleSetting.value = false;
627
628     scope = scope || WebInspector.LogContentView.Scopes.All;
629
630     // If the requested scope is already selected and the console is showing, then switch back to All.
631     if (this.isShowingConsoleView() && this.consoleContentView.scopeBar.item(scope).selected)
632         scope = WebInspector.LogContentView.Scopes.All;
633
634     this.consoleContentView.scopeBar.item(scope).selected = true;
635
636     if (!this.contentBrowser.currentContentView || this.contentBrowser.currentContentView !== this.consoleContentView) {
637         this._wasShowingNavigationSidebarBeforeFullHeightConsole = !this.navigationSidebar.collapsed;
638
639         // Collapse the sidebar before showing the console view, so the check for the collapsed state in
640         // _revealAndSelectRepresentedObjectInNavigationSidebar returns early and does not deselect any
641         // tree elements in the current sidebar.
642         this.navigationSidebar.collapsed = true;
643
644         // If this is before the content has finished loading update the collapsed value setting
645         // ourselves so that we don't uncollapse the navigation sidebar when it is loaded.
646         if (!this._contentLoaded)
647             this._navigationSidebarCollapsedSetting.value = true;
648
649         // Be sure to close any existing log view in the split content browser before showing it in the
650         // main content browser. We can only show a content view in one browser at a time.
651         this.splitContentBrowser.contentViewContainer.closeAllContentViewsOfPrototype(WebInspector.LogContentView);
652         this.contentBrowser.showContentView(this.consoleContentView);
653     }
654
655     console.assert(this.isShowingConsoleView());
656     console.assert(this._consoleToolbarButton.activated);
657
658     this.quickConsole.consoleLogVisibilityChanged(true);
659 }
660
661 WebInspector.isShowingConsoleView = function()
662 {
663     return this.contentBrowser.currentContentView instanceof WebInspector.LogContentView;
664 }
665
666 WebInspector.showConsoleView = function(scope)
667 {
668     this.showFullHeightConsole(scope);
669 }
670
671 WebInspector.toggleConsoleView = function()
672 {
673     if (this.isShowingConsoleView()) {
674         if (this.contentBrowser.canGoBack())
675             this.contentBrowser.goBack();
676         else {
677             if (!this.navigationSidebar.selectedSidebarPanel)
678                 this.navigationSidebar.selectedSidebarPanel = this.resourceSidebarPanel;
679             this.resourceSidebarPanel.showDefaultContentView();
680         }
681
682         if (this._wasShowingNavigationSidebarBeforeFullHeightConsole)
683             this.navigationSidebar.collapsed = false;
684     } else
685         this.showFullHeightConsole();
686 }
687
688 WebInspector.UIString = function(string, vararg)
689 {
690     if (WebInspector.dontLocalizeUserInterface)
691         return string;
692
693     if (window.localizedStrings && string in window.localizedStrings)
694         return window.localizedStrings[string];
695
696     if (!this._missingLocalizedStrings)
697         this._missingLocalizedStrings = {};
698
699     if (!(string in this._missingLocalizedStrings)) {
700         console.error("Localized string \"" + string + "\" was not found.");
701         this._missingLocalizedStrings[string] = true;
702     }
703
704     return "LOCALIZED STRING NOT FOUND";
705 }
706
707 WebInspector.restoreFocusFromElement = function(element)
708 {
709     if (element && element.isSelfOrAncestor(this.currentFocusElement))
710         this.previousFocusElement.focus();
711 }
712
713 WebInspector._focusChanged = function(event)
714 {
715     // Make a caret selection inside the focused element if there isn't a range selection and there isn't already
716     // a caret selection inside. This is needed (at least) to remove caret from console when focus is moved.
717     // The selection change should not apply to text fields and text areas either.
718
719     if (WebInspector.isEventTargetAnEditableField(event))
720         return;
721
722     var selection = window.getSelection();
723     if (!selection.isCollapsed)
724         return;
725
726     var element = event.target;
727
728     if (element !== this.currentFocusElement) {
729         this.previousFocusElement = this.currentFocusElement;
730         this.currentFocusElement = element;
731     }
732
733     if (element.isInsertionCaretInside())
734         return;
735
736     var selectionRange = element.ownerDocument.createRange();
737     selectionRange.setStart(element, 0);
738     selectionRange.setEnd(element, 0);
739
740     selection.removeAllRanges();
741     selection.addRange(selectionRange);
742 }
743
744 WebInspector._mouseWasClicked = function(event)
745 {
746     this.handlePossibleLinkClick(event);
747 }
748
749 WebInspector._dragOver = function(event)
750 {
751     // Do nothing if another event listener handled the event already.
752     if (event.defaultPrevented)
753         return;
754
755     // Allow dropping into editable areas.
756     if (WebInspector.isEventTargetAnEditableField(event))
757         return;
758
759     // Prevent the drop from being accepted.
760     event.dataTransfer.dropEffect = "none";
761     event.preventDefault();
762 }
763
764 WebInspector._captureDidStart = function(event)
765 {
766     this.dashboardContainer.showDashboardViewForRepresentedObject(this.dashboardManager.dashboards.replay);
767 }
768
769 WebInspector._debuggerDidPause = function(event)
770 {
771     this.debuggerSidebarPanel.show();
772     this.dashboardContainer.showDashboardViewForRepresentedObject(this.dashboardManager.dashboards.debugger);
773
774     // Since the Scope Chain details sidebar panel might not be in the sidebar yet,
775     // set a flag to select and show it when it does become available.
776     this._selectAndShowScopeChainDetailsSidebarPanelWhenAvailable = true;
777
778     InspectorFrontendHost.bringToFront();
779 }
780
781 WebInspector._debuggerDidResume = function(event)
782 {
783     this.dashboardContainer.closeDashboardViewForRepresentedObject(this.dashboardManager.dashboards.debugger);
784 }
785
786 WebInspector._mainFrameDidChange = function(event)
787 {
788     this.updateWindowTitle();
789 }
790
791 WebInspector._mainResourceDidChange = function(event)
792 {
793     if (!event.target.isMainFrame())
794         return;
795
796     this._inProvisionalLoad = false;
797
798     this._restoreInspectorViewStateFromCookie(this._lastInspectorViewStateCookieSetting.value, true);
799
800     this.updateWindowTitle();
801 }
802
803 WebInspector._provisionalLoadStarted = function(event)
804 {
805     if (!event.target.isMainFrame())
806         return;
807
808     this._updateCookieForInspectorViewState();
809
810     this._inProvisionalLoad = true;
811 }
812
813 WebInspector._windowFocused = function(event)
814 {
815     if (event.target.document.nodeType !== Node.DOCUMENT_NODE || this.docked)
816         return;
817
818     // FIXME: We should use the :window-inactive pseudo class once https://webkit.org/b/38927 is fixed.
819     document.body.classList.remove("window-inactive");
820 }
821
822 WebInspector._windowBlurred = function(event)
823 {
824     if (event.target.document.nodeType !== Node.DOCUMENT_NODE || this.docked)
825         return;
826
827     // FIXME: We should use the :window-inactive pseudo class once https://webkit.org/b/38927 is fixed.
828     document.body.classList.add("window-inactive");
829 }
830
831 WebInspector._windowResized = function(event)
832 {
833     this.toolbar.updateLayout();
834
835     this._contentBrowserSizeDidChange(event);
836 }
837
838 WebInspector._updateModifierKeys = function(event)
839 {
840     var didChange = this.modifierKeys.altKey !== event.altKey || this.modifierKeys.metaKey !== event.metaKey || this.modifierKeys.shiftKey !== event.shiftKey;
841
842     this.modifierKeys = {altKey: event.altKey, metaKey: event.metaKey, shiftKey: event.shiftKey};
843
844     if (didChange)
845         this.notifications.dispatchEventToListeners(WebInspector.Notification.GlobalModifierKeysDidChange, event);
846 }
847
848 WebInspector._windowKeyDown = function(event)
849 {
850     this._updateModifierKeys(event);
851
852     var opposite = !this._dockButtonToggledSetting.value;
853     this.undockButtonNavigationItem.toggled = (event.altKey && !event.metaKey && !event.shiftKey) ? opposite : !opposite;
854 }
855
856 WebInspector._windowKeyUp = function(event)
857 {
858     this._updateModifierKeys(event);
859
860     var opposite = !this._dockButtonToggledSetting.value;
861     this.undockButtonNavigationItem.toggled = (event.altKey && !event.metaKey && !event.shiftKey) ? opposite : !opposite;
862 }
863
864 WebInspector._mouseMoved = function(event)
865 {
866     this._updateModifierKeys(event);
867     this.mouseCoords = {
868         x: event.pageX,
869         y: event.pageY
870     };
871 }
872
873 WebInspector._pageHidden = function(event)
874 {
875     this._updateCookieForInspectorViewState();
876 }
877
878 WebInspector._undock = function(event)
879 {
880     this._dockButtonToggledSetting.value = this.undockButtonNavigationItem.toggled;
881
882     if (this.undockButtonNavigationItem.toggled)
883         InspectorFrontendHost.requestSetDockSide(this._dockSide === "bottom" ? "right" : "bottom");
884     else
885         InspectorFrontendHost.requestSetDockSide("undocked");
886 }
887
888 WebInspector._updateDockNavigationItems = function()
889 {
890     // The close and undock buttons are only available when docked.
891     var docked = this.docked;
892     this.closeButtonNavigationItem.hidden = !docked;
893     this.undockButtonNavigationItem.hidden = !docked;
894
895     if (docked) {
896         this.undockButtonNavigationItem.alternateImage = this._dockSide === "bottom" ? platformImagePath("DockRight.svg") : platformImagePath("DockBottom.svg");
897         this.undockButtonNavigationItem.alternateToolTip = this._dockSide === "bottom" ? WebInspector.UIString("Dock to right of window") : WebInspector.UIString("Dock to bottom of window");
898     }
899
900     this.undockButtonNavigationItem.toggled = this._dockButtonToggledSetting.value;
901 }
902
903 WebInspector._sidebarCollapsedStateDidChange = function(event)
904 {
905     if (event.target === this.navigationSidebar) {
906         this._navigationSidebarCollapsedSetting.value = this.navigationSidebar.collapsed;
907         this._updateNavigationSidebarForCurrentContentView();
908     } else if (event.target === this.detailsSidebar) {
909         if (!this._ignoreDetailsSidebarPanelCollapsedEvent)
910             this._detailsSidebarCollapsedSetting.value = this.detailsSidebar.collapsed;
911     }
912 }
913
914 WebInspector._detailsSidebarPanelSelected = function(event)
915 {
916     if (!this.detailsSidebar.selectedSidebarPanel || this._ignoreDetailsSidebarPanelSelectedEvent)
917         return;
918
919     this._lastSelectedDetailsSidebarPanelSetting.value = this.detailsSidebar.selectedSidebarPanel.identifier;
920 }
921
922 WebInspector._revealAndSelectRepresentedObjectInNavigationSidebar = function(representedObject)
923 {
924     if (this.navigationSidebar.collapsed)
925         return;
926
927     var selectedSidebarPanel = this.navigationSidebar.selectedSidebarPanel;
928     if (!selectedSidebarPanel)
929         return;
930
931     // If a tree outline is processing a selection currently then we can assume the selection does not
932     // need to be changed. This is needed to allow breakpoint and call frame tree elements to be selected
933     // without jumping back to selecting the resource tree element.
934     for (var contentTreeOutline of selectedSidebarPanel.visibleContentTreeOutlines) {
935         if (contentTreeOutline.processingSelectionChange)
936             return;
937     }
938
939     var treeElement = selectedSidebarPanel.treeElementForRepresentedObject(representedObject);
940
941     if (treeElement)
942         treeElement.revealAndSelect(true, false, false, true);
943     else if (selectedSidebarPanel.contentTreeOutline.selectedTreeElement)
944         selectedSidebarPanel.contentTreeOutline.selectedTreeElement.deselect(true);
945
946     if (!selectedSidebarPanel.hasSelectedElement)
947         selectedSidebarPanel.showDefaultContentView();
948 }
949
950 WebInspector._updateNavigationSidebarForCurrentContentView = function()
951 {
952     if (this.navigationSidebar.collapsed)
953         return;
954
955     var selectedSidebarPanel = this.navigationSidebar.selectedSidebarPanel;
956     if (!selectedSidebarPanel)
957         return;
958
959     var currentContentView = this.contentBrowser.currentContentView;
960     if (!currentContentView)
961         return;
962
963     // Ensure the navigation sidebar panel is allowed by the current content view, if not ask the sidebar panel
964     // to show the content view for the current selection.
965     var allowedNavigationSidebarPanels = currentContentView.allowedNavigationSidebarPanels;
966     if (allowedNavigationSidebarPanels.length && !allowedNavigationSidebarPanels.contains(selectedSidebarPanel.identifier)) {
967         selectedSidebarPanel.showContentViewForCurrentSelection();
968
969         // Fetch the current content view again, since it likely changed.
970         currentContentView = this.contentBrowser.currentContentView;
971     }
972
973     if (!allowedNavigationSidebarPanels.length || allowedNavigationSidebarPanels.contains(selectedSidebarPanel.identifier))
974         currentContentView.__lastNavigationSidebarPanelIdentifer = selectedSidebarPanel.identifier;
975
976     this._revealAndSelectRepresentedObjectInNavigationSidebar(currentContentView.representedObject);
977 }
978
979 WebInspector._navigationSidebarPanelSelected = function(event)
980 {
981     var selectedSidebarPanel = this.navigationSidebar.selectedSidebarPanel;
982     if (!selectedSidebarPanel)
983         return;
984
985     this._updateNavigationSidebarForCurrentContentView();
986 }
987
988 WebInspector._domNodeWasInspected = function(event)
989 {
990     WebInspector.domTreeManager.highlightDOMNodeForTwoSeconds(event.data.node.id);
991
992     // Select the Style details sidebar panel if one of the DOM details sidebar panels isn't already selected.
993     if (!(this.detailsSidebar.selectedSidebarPanel instanceof WebInspector.DOMDetailsSidebarPanel))
994         this.detailsSidebar.selectedSidebarPanel = this.cssStyleDetailsSidebarPanel;
995
996     InspectorFrontendHost.bringToFront();
997 }
998
999 WebInspector._contentBrowserSizeDidChange = function(event)
1000 {
1001     this.contentBrowser.updateLayout();
1002     this.splitContentBrowser.updateLayout();
1003     this.quickConsole.updateLayout();
1004 }
1005
1006 WebInspector._quickConsoleDidResize = function(event)
1007 {
1008     this.contentBrowser.updateLayout();
1009 }
1010
1011 WebInspector._sidebarWidthDidChange = function(event)
1012 {
1013     if (!event.target.collapsed) {
1014         if (event.target === this.navigationSidebar)
1015             this._navigationSidebarWidthSetting.value = this.navigationSidebar.width;
1016         else if (event.target === this.detailsSidebar)
1017             this._detailsSidebarWidthSetting.value = this.detailsSidebar.width;
1018     }
1019
1020     this._contentBrowserSizeDidChange(event);
1021 }
1022
1023 WebInspector._updateToolbarHeight = function()
1024 {
1025     if (WebInspector.Platform.isLegacyMacOS)
1026         InspectorFrontendHost.setToolbarHeight(this.toolbar.element.offsetHeight);
1027 }
1028
1029 WebInspector._toolbarDisplayModeDidChange = function(event)
1030 {
1031     if (this._ignoreToolbarModeDidChangeEvents)
1032         return;
1033
1034     if (this._dockSide === "bottom")
1035         this._toolbarDockedBottomDisplayModeSetting.value = this.toolbar.displayMode;
1036     else if (this._dockSide === "right")
1037         this._toolbarDockedRightDisplayModeSetting.value = this.toolbar.displayMode;
1038     else
1039         this._toolbarUndockedDisplayModeSetting.value = this.toolbar.displayMode;
1040
1041     this._updateToolbarHeight();
1042 }
1043
1044 WebInspector._toolbarSizeModeDidChange = function(event)
1045 {
1046     if (this._ignoreToolbarModeDidChangeEvents)
1047         return;
1048
1049     if (this._dockSide === "bottom")
1050         this._toolbarDockedBottomSizeModeSetting.value = this.toolbar.sizeMode;
1051     else if (this._dockSide === "right")
1052         this._toolbarDockedRightSizeModeSetting.value = this.toolbar.sizeMode;
1053     else
1054         this._toolbarUndockedSizeModeSetting.value = this.toolbar.sizeMode;
1055
1056     this._updateToolbarHeight();
1057 }
1058
1059 WebInspector._updateCookieForInspectorViewState = function()
1060 {
1061     var cookie = {};
1062     var currentContentView = this.contentBrowser.currentContentView;
1063
1064     // The console does not have a sidebar, so create a cookie here.
1065     if (currentContentView && currentContentView.representedObject instanceof WebInspector.LogObject) {
1066         cookie[WebInspector.SelectedSidebarPanelCookieKey] = "console";
1067         this._lastInspectorViewStateCookieSetting.value = cookie;
1068         return;
1069     }
1070
1071     // Ignore saving the sidebar state for provisional loads. The currently selected sidebar
1072     // may have been the result of content views closing as a result of a page navigation,
1073     // but those content views may come back very soon.
1074     if (this._inProvisionalLoad)
1075         return;
1076
1077     var selectedSidebarPanel = this.navigationSidebar.selectedSidebarPanel;
1078     if (!selectedSidebarPanel)
1079         return;
1080
1081     // Restoring view state after inspector re-open or page reload is delegated to navigation sidebars.
1082     // This is because some navigation sidebar state (such as breakpoint selections) cannot be inferred
1083     // solely based on which content view is visible, or multiple navigation sidebars could be shown.
1084     cookie[WebInspector.SelectedSidebarPanelCookieKey] = selectedSidebarPanel.identifier;
1085     selectedSidebarPanel.saveStateToCookie(cookie);
1086     this._lastInspectorViewStateCookieSetting.value = cookie;
1087 }
1088
1089 WebInspector._contentBrowserCurrentContentViewDidChange = function(event)
1090 {
1091     var consoleViewShowing = this.isShowingConsoleView();
1092     this._consoleToolbarButton.activated = consoleViewShowing;
1093
1094     if (!this.isShowingSplitConsole())
1095         this.quickConsole.consoleLogVisibilityChanged(consoleViewShowing);
1096
1097     if (!this.currentViewSupportsSplitContentBrowser())
1098         this.hideSplitConsole();
1099
1100     var currentContentView = this.contentBrowser.currentContentView;
1101     if (!currentContentView)
1102         return;
1103
1104     var selectedSidebarPanel = this.navigationSidebar.selectedSidebarPanel;
1105     if (!selectedSidebarPanel)
1106         return;
1107
1108     // Ensure the navigation sidebar panel is allowed by the current content view, if not change the navigation sidebar panel
1109     // to the last navigation sidebar panel used with the content view or the first one allowed.
1110     var selectedSidebarPanelIdentifier = selectedSidebarPanel.identifier;
1111
1112     var allowedNavigationSidebarPanels = currentContentView.allowedNavigationSidebarPanels;
1113     if (allowedNavigationSidebarPanels.length && !allowedNavigationSidebarPanels.contains(selectedSidebarPanelIdentifier)) {
1114         console.assert(!currentContentView.__lastNavigationSidebarPanelIdentifer || allowedNavigationSidebarPanels.contains(currentContentView.__lastNavigationSidebarPanelIdentifer));
1115         this.navigationSidebar.selectedSidebarPanel = currentContentView.__lastNavigationSidebarPanelIdentifer || allowedNavigationSidebarPanels[0];
1116     }
1117
1118     if (!allowedNavigationSidebarPanels.length || allowedNavigationSidebarPanels.contains(selectedSidebarPanelIdentifier))
1119         currentContentView.__lastNavigationSidebarPanelIdentifer = selectedSidebarPanelIdentifier;
1120
1121     this._revealAndSelectRepresentedObjectInNavigationSidebar(currentContentView.representedObject);
1122 }
1123
1124 WebInspector._contentBrowserRepresentedObjectsDidChange = function(event)
1125 {
1126     var currentRepresentedObjects = this.contentBrowser.currentRepresentedObjects;
1127     var currentSidebarPanels = this.detailsSidebar.sidebarPanels;
1128     var wasSidebarEmpty = !currentSidebarPanels.length;
1129
1130     // Ignore any changes to the selected sidebar panel during this function so only user initiated
1131     // changes are recorded in _lastSelectedDetailsSidebarPanelSetting.
1132     this._ignoreDetailsSidebarPanelSelectedEvent = true;
1133
1134     for (var i = 0; i < this.detailsSidebarPanels.length; ++i) {
1135         var sidebarPanel = this.detailsSidebarPanels[i];
1136         if (sidebarPanel.inspect(currentRepresentedObjects)) {
1137             var currentSidebarPanelIndex = currentSidebarPanels.indexOf(sidebarPanel);
1138             if (currentSidebarPanelIndex !== -1) {
1139                 // Already showing the panel.
1140                 continue;
1141             }
1142
1143             // The sidebar panel was not previously showing, so add the panel and show the toolbar item.
1144             this.detailsSidebar.addSidebarPanel(sidebarPanel);
1145             sidebarPanel.toolbarItem.hidden = false;
1146
1147             if (this._selectAndShowScopeChainDetailsSidebarPanelWhenAvailable && sidebarPanel === this.scopeChainDetailsSidebarPanel) {
1148                 // Select the scope chain sidebar panel since it needs to be shown after pausing in the debugger.
1149                 delete this._selectAndShowScopeChainDetailsSidebarPanelWhenAvailable;
1150                 this.detailsSidebar.selectedSidebarPanel = this.scopeChainDetailsSidebarPanel;
1151
1152                 this._ignoreDetailsSidebarPanelCollapsedEvent = true;
1153                 this.detailsSidebar.collapsed = false;
1154                 delete this._ignoreDetailsSidebarPanelCollapsedEvent;
1155             } else if (this._lastSelectedDetailsSidebarPanelSetting.value === sidebarPanel.identifier) {
1156                 // Restore the sidebar panel selection if this sidebar panel was the last one selected by the user.
1157                 this.detailsSidebar.selectedSidebarPanel = sidebarPanel;
1158             }
1159         } else {
1160             // The sidebar panel can't inspect the current represented objects, so remove the panel and hide the toolbar item.
1161             this.detailsSidebar.removeSidebarPanel(sidebarPanel);
1162             sidebarPanel.toolbarItem.hidden = true;
1163         }
1164     }
1165
1166     if (!this.detailsSidebar.selectedSidebarPanel && currentSidebarPanels.length)
1167         this.detailsSidebar.selectedSidebarPanel = currentSidebarPanels[0];
1168
1169     this._ignoreDetailsSidebarPanelCollapsedEvent = true;
1170
1171     if (!this.detailsSidebar.sidebarPanels.length)
1172         this.detailsSidebar.collapsed = true;
1173     else if (wasSidebarEmpty)
1174         this.detailsSidebar.collapsed = this._detailsSidebarCollapsedSetting.value;
1175
1176     delete this._ignoreDetailsSidebarPanelCollapsedEvent;
1177
1178     // Stop ignoring the sidebar panel selected event.
1179     delete this._ignoreDetailsSidebarPanelSelectedEvent;
1180 }
1181
1182 WebInspector._restoreInspectorViewStateFromCookie = function(cookie, causedByReload)
1183 {
1184     if (!cookie) {
1185         this.navigationSidebar.selectedSidebarPanel = this.resourceSidebarPanel;
1186         return;
1187     }
1188
1189     // The console does not have a sidebar, so handle its special cookie here.
1190     if (cookie[WebInspector.SelectedSidebarPanelCookieKey] === "console") {
1191         this.showFullHeightConsole();
1192         return;
1193     }
1194
1195     const matchTypeOnlyDelayForReload = 2000;
1196     const matchTypeOnlyDelayForReopen = 1000;
1197
1198     var sidebarPanelIdentifier = cookie[WebInspector.SelectedSidebarPanelCookieKey];
1199     var sidebarPanel = this.navigationSidebar.findSidebarPanel(sidebarPanelIdentifier);
1200     if (!sidebarPanel) {
1201         this.navigationSidebar.selectedSidebarPanel = this.resourceSidebarPanel;
1202         return;
1203     }
1204
1205     this.navigationSidebar.selectedSidebarPanel = sidebarPanel;
1206
1207     var relaxMatchDelay = causedByReload ? matchTypeOnlyDelayForReload : matchTypeOnlyDelayForReopen;
1208     sidebarPanel.restoreStateFromCookie(cookie, relaxMatchDelay);
1209 }
1210
1211 WebInspector._initializeWebSocketIfNeeded = function()
1212 {
1213     if (!InspectorFrontendHost.initializeWebSocket)
1214         return;
1215
1216     var queryParams = parseLocationQueryParameters();
1217
1218     if ("ws" in queryParams)
1219         var url = "ws://" + queryParams.ws;
1220     else if ("page" in queryParams) {
1221         var page = queryParams.page;
1222         var host = "host" in queryParams ? queryParams.host : window.location.host;
1223         var url = "ws://" + host + "/devtools/page/" + page;
1224     }
1225
1226     if (!url)
1227         return;
1228
1229     InspectorFrontendHost.initializeWebSocket(url);
1230 }
1231
1232 WebInspector._updateSplitConsoleHeight = function(height)
1233 {
1234     const minimumHeight = 64;
1235     const maximumHeight = window.innerHeight * 0.55;
1236
1237     height = Math.max(minimumHeight, Math.min(height, maximumHeight));
1238
1239     this.splitContentBrowser.element.style.height = height + "px";
1240 }
1241
1242 WebInspector._consoleResizerMouseDown = function(event)
1243 {
1244     if (event.button !== 0 || event.ctrlKey)
1245         return;
1246
1247     // Only start dragging if the target is one of the elements that we expect.
1248     if (!event.target.classList.contains("navigation-bar") && !event.target.classList.contains("flexible-space"))
1249         return;
1250
1251     var resizerElement = event.target;
1252     var mouseOffset = resizerElement.offsetHeight - (event.pageY - resizerElement.totalOffsetTop);
1253
1254     function dockedResizerDrag(event)
1255     {
1256         if (event.button !== 0)
1257             return;
1258
1259         var height = window.innerHeight - event.pageY - mouseOffset;
1260
1261         this._splitConsoleHeightSetting.value = height;
1262
1263         this._updateSplitConsoleHeight(height);
1264     }
1265
1266     function dockedResizerDragEnd(event)
1267     {
1268         if (event.button !== 0)
1269             return;
1270
1271         this.elementDragEnd(event);
1272     }
1273
1274     this.elementDragStart(resizerElement, dockedResizerDrag.bind(this), dockedResizerDragEnd.bind(this), event, "row-resize");
1275 }
1276
1277 WebInspector._toolbarMouseDown = function(event)
1278 {
1279     if (event.ctrlKey)
1280         return;
1281
1282     if (this._dockSide === "right")
1283         return;
1284
1285     if (this.docked)
1286         this._dockedResizerMouseDown(event);
1287     else
1288         this._moveWindowMouseDown(event);
1289 }
1290
1291 WebInspector._dockedResizerMouseDown = function(event)
1292 {
1293     if (event.button !== 0 || event.ctrlKey)
1294         return;
1295
1296     if (!this.docked)
1297         return;
1298
1299     // Only start dragging if the target is one of the elements that we expect.
1300     if (event.target.id !== "docked-resizer" && !event.target.classList.contains("toolbar") &&
1301         !event.target.classList.contains("flexible-space") && !event.target.classList.contains("item-section"))
1302         return;
1303
1304     var windowProperty = this._dockSide === "bottom" ? "innerHeight" : "innerWidth";
1305     var eventScreenProperty = this._dockSide === "bottom" ? "screenY" : "screenX";
1306     var eventClientProperty = this._dockSide === "bottom" ? "clientY" : "clientX";
1307
1308     var resizerElement = event.target;
1309     var firstClientPosition = event[eventClientProperty];
1310     var lastScreenPosition = event[eventScreenProperty];
1311
1312     function dockedResizerDrag(event)
1313     {
1314         if (event.button !== 0)
1315             return;
1316
1317         var position = event[eventScreenProperty];
1318         var delta = position - lastScreenPosition;
1319         var clientPosition = event[eventClientProperty];
1320
1321         lastScreenPosition = position;
1322
1323         // If delta is positive the docked Inspector size is decreasing, in which case the cursor client position
1324         // with respect to the target cannot be less than the first mouse down position within the target.
1325         if (delta > 0 && clientPosition < firstClientPosition)
1326             return;
1327
1328         // If delta is negative the docked Inspector size is increasing, in which case the cursor client position
1329         // with respect to the target cannot be greater than the first mouse down position within the target.
1330         if (delta < 0 && clientPosition > firstClientPosition)
1331             return;
1332
1333         var dimension = Math.max(0, window[windowProperty] - delta);
1334
1335         if (this._dockSide === "bottom")
1336             InspectorFrontendHost.setAttachedWindowHeight(dimension);
1337         else
1338             InspectorFrontendHost.setAttachedWindowWidth(dimension);
1339     }
1340
1341     function dockedResizerDragEnd(event)
1342     {
1343         if (event.button !== 0)
1344             return;
1345
1346         WebInspector.elementDragEnd(event);
1347     }
1348
1349     WebInspector.elementDragStart(resizerElement, dockedResizerDrag.bind(this), dockedResizerDragEnd.bind(this), event, this._dockSide === "bottom" ? "row-resize" : "col-resize");
1350 }
1351
1352 WebInspector._moveWindowMouseDown = function(event)
1353 {
1354     console.assert(!this.docked);
1355
1356     if (event.button !== 0 || event.ctrlKey)
1357         return;
1358
1359     // Only start dragging if the target is one of the elements that we expect.
1360     if (!event.target.classList.contains("toolbar") && !event.target.classList.contains("flexible-space") &&
1361         !event.target.classList.contains("item-section"))
1362         return;
1363
1364     // Ignore dragging on the top of the toolbar on Mac where the inspector content fills the entire window.
1365     if (WebInspector.Platform.name === "mac" && WebInspector.Platform.version.release >= 10) {
1366         const windowDragHandledTitleBarHeight = 22;
1367         if (event.pageY < windowDragHandledTitleBarHeight)
1368             return;
1369     }
1370
1371     var lastScreenX = event.screenX;
1372     var lastScreenY = event.screenY;
1373
1374     function toolbarDrag(event)
1375     {
1376         if (event.button !== 0)
1377             return;
1378
1379         var x = event.screenX - lastScreenX;
1380         var y = event.screenY - lastScreenY;
1381
1382         InspectorFrontendHost.moveWindowBy(x, y);
1383
1384         lastScreenX = event.screenX;
1385         lastScreenY = event.screenY;
1386     }
1387
1388     function toolbarDragEnd(event)
1389     {
1390         if (event.button !== 0)
1391             return;
1392
1393         WebInspector.elementDragEnd(event);
1394     }
1395
1396     WebInspector.elementDragStart(event.target, toolbarDrag, toolbarDragEnd, event, "default");
1397 }
1398
1399 WebInspector._inspectModeStateChanged = function(event)
1400 {
1401     this._inspectModeToolbarButton.activated = WebInspector.domTreeManager.inspectModeEnabled;
1402 }
1403
1404 WebInspector._toggleInspectMode = function(event)
1405 {
1406     WebInspector.domTreeManager.inspectModeEnabled = !WebInspector.domTreeManager.inspectModeEnabled;
1407 }
1408
1409 WebInspector._reloadPage = function(event)
1410 {
1411     PageAgent.reload();
1412 }
1413
1414 WebInspector._reloadPageIgnoringCache = function(event)
1415 {
1416     PageAgent.reload(true);
1417 }
1418
1419 WebInspector._toggleInspectMode = function(event)
1420 {
1421     this.domTreeManager.inspectModeEnabled = !this.domTreeManager.inspectModeEnabled;
1422 }
1423
1424 WebInspector._focusedContentView = function()
1425 {
1426     if (this.contentBrowser.element.isSelfOrAncestor(this.currentFocusElement))
1427         return this.contentBrowser.currentContentView;
1428     if (this.splitContentBrowser.element.isSelfOrAncestor(this.currentFocusElement))
1429         return  this.splitContentBrowser.currentContentView;
1430     return null;
1431 }
1432
1433 WebInspector._beforecopy = function(event)
1434 {
1435     var selection = window.getSelection();
1436
1437     // If there is no selection, see if the focused element or focused ContentView can handle the copy event.
1438     if (selection.isCollapsed && !WebInspector.isEventTargetAnEditableField(event)) {
1439         var focusedCopyHandler = this.currentFocusElement && this.currentFocusElement.copyHandler;
1440         if (focusedCopyHandler && typeof focusedCopyHandler.handleBeforeCopyEvent === "function") {
1441             focusedCopyHandler.handleBeforeCopyEvent(event);
1442             if (event.defaultPrevented)
1443                 return;
1444         }
1445
1446         var focusedContentView = this._focusedContentView();
1447         if (focusedContentView && typeof focusedContentView.handleCopyEvent === "function") {
1448             event.preventDefault();
1449             return;
1450         }
1451
1452         return;
1453     }
1454
1455     if (selection.isCollapsed)
1456         return;
1457
1458     // Say we can handle it (by preventing default) to remove word break characters.
1459     event.preventDefault();
1460 }
1461
1462 WebInspector._copy = function(event)
1463 {
1464     var selection = window.getSelection();
1465
1466     // If there is no selection, pass the copy event on to the focused element or focused ContentView.
1467     if (selection.isCollapsed && !WebInspector.isEventTargetAnEditableField(event)) {
1468         var focusedCopyHandler = this.currentFocusElement && this.currentFocusElement.copyHandler;
1469         if (focusedCopyHandler && typeof focusedCopyHandler.handleCopyEvent === "function") {
1470             focusedCopyHandler.handleCopyEvent(event);
1471             if (event.defaultPrevented)
1472                 return;
1473         }
1474
1475         var focusedContentView = this._focusedContentView();
1476         if (focusedContentView && typeof focusedContentView.handleCopyEvent === "function") {
1477             focusedContentView.handleCopyEvent(event);
1478             return;
1479         }
1480
1481         return;
1482     }
1483
1484     if (selection.isCollapsed)
1485         return;
1486
1487     // Remove word break characters from the selection before putting it on the pasteboard.
1488     var selectionString = selection.toString().removeWordBreakCharacters();
1489     event.clipboardData.setData("text/plain", selectionString);
1490     event.preventDefault();
1491 }
1492
1493 WebInspector._generateDisclosureTriangleImages = function()
1494 {
1495     var specifications = {};
1496     specifications["normal"] = {fillColor: [140, 140, 140, 1]};
1497     specifications["normal-active"] = {fillColor: [128, 128, 128, 1]};
1498
1499     generateColoredImagesForCSS("Images/DisclosureTriangleSmallOpen.svg", specifications, 13, 13, "disclosure-triangle-small-open-");
1500     generateColoredImagesForCSS("Images/DisclosureTriangleSmallClosed.svg", specifications, 13, 13, "disclosure-triangle-small-closed-");
1501
1502     specifications["selected"] = {fillColor: [255, 255, 255, 1]};
1503
1504     generateColoredImagesForCSS("Images/DisclosureTriangleTinyOpen.svg", specifications, 8, 8, "disclosure-triangle-tiny-open-");
1505     generateColoredImagesForCSS("Images/DisclosureTriangleTinyClosed.svg", specifications, 8, 8, "disclosure-triangle-tiny-closed-");
1506 }
1507
1508 WebInspector.elementDragStart = function(element, dividerDrag, elementDragEnd, event, cursor, eventTarget)
1509 {
1510     if (WebInspector._elementDraggingEventListener || WebInspector._elementEndDraggingEventListener)
1511         WebInspector.elementDragEnd(event);
1512
1513     if (element) {
1514         // Install glass pane
1515         if (WebInspector._elementDraggingGlassPane)
1516             WebInspector._elementDraggingGlassPane.parentElement.removeChild(WebInspector._elementDraggingGlassPane);
1517
1518         var glassPane = document.createElement("div");
1519         glassPane.style.cssText = "position:absolute;top:0;bottom:0;left:0;right:0;opacity:0;z-index:1";
1520         glassPane.id = "glass-pane-for-drag";
1521         element.ownerDocument.body.appendChild(glassPane);
1522         WebInspector._elementDraggingGlassPane = glassPane;
1523     }
1524
1525     WebInspector._elementDraggingEventListener = dividerDrag;
1526     WebInspector._elementEndDraggingEventListener = elementDragEnd;
1527
1528     var targetDocument = event.target.ownerDocument;
1529
1530     WebInspector._elementDraggingEventTarget = eventTarget || targetDocument;
1531     WebInspector._elementDraggingEventTarget.addEventListener("mousemove", dividerDrag, true);
1532     WebInspector._elementDraggingEventTarget.addEventListener("mouseup", elementDragEnd, true);
1533
1534     targetDocument.body.style.cursor = cursor;
1535
1536     event.preventDefault();
1537 }
1538
1539 WebInspector.elementDragEnd = function(event)
1540 {
1541     WebInspector._elementDraggingEventTarget.removeEventListener("mousemove", WebInspector._elementDraggingEventListener, true);
1542     WebInspector._elementDraggingEventTarget.removeEventListener("mouseup", WebInspector._elementEndDraggingEventListener, true);
1543
1544     event.target.ownerDocument.body.style.removeProperty("cursor");
1545
1546     if (WebInspector._elementDraggingGlassPane)
1547         WebInspector._elementDraggingGlassPane.parentElement.removeChild(WebInspector._elementDraggingGlassPane);
1548
1549     delete WebInspector._elementDraggingGlassPane;
1550     delete WebInspector._elementDraggingEventTarget;
1551     delete WebInspector._elementDraggingEventListener;
1552     delete WebInspector._elementEndDraggingEventListener;
1553
1554     event.preventDefault();
1555 }
1556
1557 WebInspector.createMessageTextView = function(message, isError)
1558 {
1559     var messageElement = document.createElement("div");
1560     messageElement.className = "message-text-view";
1561     if (isError)
1562         messageElement.classList.add("error");
1563
1564     messageElement.textContent = message;
1565
1566     return messageElement;
1567 }
1568
1569 WebInspector.createGoToArrowButton = function()
1570 {
1571     if (!WebInspector._generatedGoToArrowButtonImages) {
1572         WebInspector._generatedGoToArrowButtonImages = true;
1573
1574         var specifications = {};
1575         specifications["go-to-arrow-normal"] = {fillColor: [0, 0, 0, 0.5]};
1576         specifications["go-to-arrow-normal-active"] = {fillColor: [0, 0, 0, 0.7]};
1577         specifications["go-to-arrow-selected"] = {fillColor: [255, 255, 255, 0.8]};
1578         specifications["go-to-arrow-selected-active"] = {fillColor: [255, 255, 255, 1]};
1579
1580         generateColoredImagesForCSS("Images/GoToArrow.svg", specifications, 10, 10);
1581     }
1582
1583     function stopPropagation(event)
1584     {
1585         event.stopPropagation()
1586     }
1587
1588     var button = document.createElement("button");
1589     button.addEventListener("mousedown", stopPropagation, true);
1590     button.className = "go-to-arrow";
1591     button.tabIndex = -1;
1592     return button;
1593 }
1594
1595 WebInspector.createSourceCodeLocationLink = function(sourceCodeLocation, dontFloat, useGoToArrowButton)
1596 {
1597     console.assert(sourceCodeLocation);
1598     if (!sourceCodeLocation)
1599         return null;
1600
1601     function showSourceCodeLocation(event)
1602     {
1603         event.stopPropagation();
1604         event.preventDefault();
1605
1606         if (event.metaKey)
1607             this.resourceSidebarPanel.showOriginalUnformattedSourceCodeLocation(sourceCodeLocation);
1608         else
1609             this.resourceSidebarPanel.showSourceCodeLocation(sourceCodeLocation);
1610     }
1611
1612     var linkElement = document.createElement("a");
1613     linkElement.className = "go-to-link";
1614     linkElement.addEventListener("click", showSourceCodeLocation.bind(this));
1615     sourceCodeLocation.populateLiveDisplayLocationTooltip(linkElement);
1616
1617     if (useGoToArrowButton)
1618         linkElement.appendChild(WebInspector.createGoToArrowButton());
1619     else
1620         sourceCodeLocation.populateLiveDisplayLocationString(linkElement, "textContent");
1621
1622     if (dontFloat)
1623         linkElement.classList.add("dont-float");
1624
1625     return linkElement;
1626 }
1627
1628 WebInspector.linkifyLocation = function(url, lineNumber, columnNumber, className)
1629 {
1630     var sourceCode = WebInspector.frameResourceManager.resourceForURL(url);
1631     if (!sourceCode) {
1632         sourceCode = WebInspector.debuggerManager.scriptsForURL(url)[0];
1633         if (sourceCode)
1634             sourceCode = sourceCode.resource || sourceCode;
1635     }
1636
1637     if (!sourceCode) {
1638         var anchor = document.createElement("a");
1639         anchor.href  = url;
1640         anchor.lineNumber = lineNumber;
1641         if (className)
1642             anchor.className = className;
1643         anchor.appendChild(document.createTextNode(WebInspector.displayNameForURL(url) + ":" + lineNumber));
1644         return anchor;
1645     }
1646
1647     var sourceCodeLocation = sourceCode.createSourceCodeLocation(lineNumber, columnNumber);
1648     var linkElement = WebInspector.createSourceCodeLocationLink(sourceCodeLocation, true);
1649     if (className)
1650         linkElement.classList.add(className);
1651     return linkElement;
1652 }
1653
1654 WebInspector.linkifyURLAsNode = function(url, linkText, classes, tooltipText)
1655 {
1656     if (!linkText)
1657         linkText = url;
1658
1659     classes = (classes ? classes + " " : "");
1660
1661     var a = document.createElement("a");
1662     a.href = url;
1663     a.className = classes;
1664
1665     if (tooltipText === undefined)
1666         a.title = url;
1667     else if (typeof tooltipText !== "string" || tooltipText.length)
1668         a.title = tooltipText;
1669
1670     a.textContent = linkText;
1671     a.style.maxWidth = "100%";
1672
1673     return a;
1674 }
1675
1676 WebInspector.linkifyStringAsFragmentWithCustomLinkifier = function(string, linkifier)
1677 {
1678     var container = document.createDocumentFragment();
1679     var linkStringRegEx = /(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\/\/|www\.)[\w$\-_+*'=\|\/\\(){}[\]%@&#~,:;.!?]{2,}[\w$\-_+*=\|\/\\({%@&#~]/;
1680     var lineColumnRegEx = /:(\d+)(:(\d+))?$/;
1681
1682     while (string) {
1683         var linkString = linkStringRegEx.exec(string);
1684         if (!linkString)
1685             break;
1686
1687         linkString = linkString[0];
1688         var linkIndex = string.indexOf(linkString);
1689         var nonLink = string.substring(0, linkIndex);
1690         container.appendChild(document.createTextNode(nonLink));
1691
1692         var title = linkString;
1693         var realURL = (linkString.startsWith("www.") ? "http://" + linkString : linkString);
1694         var lineColumnMatch = lineColumnRegEx.exec(realURL);
1695         if (lineColumnMatch)
1696             realURL = realURL.substring(0, realURL.length - lineColumnMatch[0].length);
1697
1698         var linkNode = linkifier(title, realURL, lineColumnMatch ? lineColumnMatch[1] : undefined);
1699         container.appendChild(linkNode);
1700         string = string.substring(linkIndex + linkString.length, string.length);
1701     }
1702
1703     if (string)
1704         container.appendChild(document.createTextNode(string));
1705
1706     return container;
1707 }
1708
1709 WebInspector.linkifyStringAsFragment = function(string)
1710 {
1711     function linkifier(title, url, lineNumber)
1712     {
1713         var urlNode = WebInspector.linkifyURLAsNode(url, title, undefined);
1714         if (lineNumber !== undefined)
1715             urlNode.lineNumber = lineNumber;
1716
1717         return urlNode;
1718     }
1719
1720     return WebInspector.linkifyStringAsFragmentWithCustomLinkifier(string, linkifier);
1721 }
1722
1723 WebInspector._undoKeyboardShortcut = function(event)
1724 {
1725     if (!this.isEditingAnyField() && !this.isEventTargetAnEditableField(event)) {
1726         this.undo();
1727         event.preventDefault();
1728     }
1729 }
1730
1731 WebInspector._redoKeyboardShortcut = function(event)
1732 {
1733     if (!this.isEditingAnyField() && !this.isEventTargetAnEditableField(event)) {
1734         this.redo();
1735         event.preventDefault();
1736     }
1737 }
1738
1739 WebInspector.undo = function()
1740 {
1741     DOMAgent.undo();
1742 }
1743
1744 WebInspector.redo = function()
1745 {
1746     DOMAgent.redo();
1747 }
1748
1749 WebInspector.highlightRangesWithStyleClass = function(element, resultRanges, styleClass, changes)
1750 {
1751     changes = changes || [];
1752     var highlightNodes = [];
1753     var lineText = element.textContent;
1754     var ownerDocument = element.ownerDocument;
1755     var textNodeSnapshot = ownerDocument.evaluate(".//text()", element, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
1756
1757     var snapshotLength = textNodeSnapshot.snapshotLength;
1758     if (snapshotLength === 0)
1759         return highlightNodes;
1760
1761     var nodeRanges = [];
1762     var rangeEndOffset = 0;
1763     for (var i = 0; i < snapshotLength; ++i) {
1764         var range = {};
1765         range.offset = rangeEndOffset;
1766         range.length = textNodeSnapshot.snapshotItem(i).textContent.length;
1767         rangeEndOffset = range.offset + range.length;
1768         nodeRanges.push(range);
1769     }
1770
1771     var startIndex = 0;
1772     for (var i = 0; i < resultRanges.length; ++i) {
1773         var startOffset = resultRanges[i].offset;
1774         var endOffset = startOffset + resultRanges[i].length;
1775
1776         while (startIndex < snapshotLength && nodeRanges[startIndex].offset + nodeRanges[startIndex].length <= startOffset)
1777             startIndex++;
1778         var endIndex = startIndex;
1779         while (endIndex < snapshotLength && nodeRanges[endIndex].offset + nodeRanges[endIndex].length < endOffset)
1780             endIndex++;
1781         if (endIndex === snapshotLength)
1782             break;
1783
1784         var highlightNode = ownerDocument.createElement("span");
1785         highlightNode.className = styleClass;
1786         highlightNode.textContent = lineText.substring(startOffset, endOffset);
1787
1788         var lastTextNode = textNodeSnapshot.snapshotItem(endIndex);
1789         var lastText = lastTextNode.textContent;
1790         lastTextNode.textContent = lastText.substring(endOffset - nodeRanges[endIndex].offset);
1791         changes.push({ node: lastTextNode, type: "changed", oldText: lastText, newText: lastTextNode.textContent });
1792
1793         if (startIndex === endIndex) {
1794             lastTextNode.parentElement.insertBefore(highlightNode, lastTextNode);
1795             changes.push({ node: highlightNode, type: "added", nextSibling: lastTextNode, parent: lastTextNode.parentElement });
1796             highlightNodes.push(highlightNode);
1797
1798             var prefixNode = ownerDocument.createTextNode(lastText.substring(0, startOffset - nodeRanges[startIndex].offset));
1799             lastTextNode.parentElement.insertBefore(prefixNode, highlightNode);
1800             changes.push({ node: prefixNode, type: "added", nextSibling: highlightNode, parent: lastTextNode.parentElement });
1801         } else {
1802             var firstTextNode = textNodeSnapshot.snapshotItem(startIndex);
1803             var firstText = firstTextNode.textContent;
1804             var anchorElement = firstTextNode.nextSibling;
1805
1806             firstTextNode.parentElement.insertBefore(highlightNode, anchorElement);
1807             changes.push({ node: highlightNode, type: "added", nextSibling: anchorElement, parent: firstTextNode.parentElement });
1808             highlightNodes.push(highlightNode);
1809
1810             firstTextNode.textContent = firstText.substring(0, startOffset - nodeRanges[startIndex].offset);
1811             changes.push({ node: firstTextNode, type: "changed", oldText: firstText, newText: firstTextNode.textContent });
1812
1813             for (var j = startIndex + 1; j < endIndex; j++) {
1814                 var textNode = textNodeSnapshot.snapshotItem(j);
1815                 var text = textNode.textContent;
1816                 textNode.textContent = "";
1817                 changes.push({ node: textNode, type: "changed", oldText: text, newText: textNode.textContent });
1818             }
1819         }
1820         startIndex = endIndex;
1821         nodeRanges[startIndex].offset = endOffset;
1822         nodeRanges[startIndex].length = lastTextNode.textContent.length;
1823
1824     }
1825     return highlightNodes;
1826 }
1827
1828 WebInspector.revertDomChanges = function(domChanges)
1829 {
1830     for (var i = domChanges.length - 1; i >= 0; --i) {
1831         var entry = domChanges[i];
1832         switch (entry.type) {
1833         case "added":
1834             if (entry.node.parentElement)
1835                 entry.node.parentElement.removeChild(entry.node);
1836             break;
1837         case "changed":
1838             entry.node.textContent = entry.oldText;
1839             break;
1840         }
1841     }
1842 }
1843
1844 WebInspector.archiveMainFrame = function()
1845 {
1846     this.notifications.dispatchEventToListeners(WebInspector.Notification.PageArchiveStarted, event);
1847
1848     setTimeout(function() {
1849         PageAgent.archive(function(error, data) {
1850             this.notifications.dispatchEventToListeners(WebInspector.Notification.PageArchiveEnded, event);
1851             if (error)
1852                 return;
1853
1854             var mainFrame = WebInspector.frameResourceManager.mainFrame;
1855             var archiveName = mainFrame.mainResource.urlComponents.host || mainFrame.mainResource.displayName || "Archive";
1856             var url = "web-inspector:///" + encodeURI(archiveName) + ".webarchive";
1857             InspectorFrontendHost.save(url, data, true, true);
1858         }.bind(this));
1859     }.bind(this), 3000);
1860 }
1861
1862 WebInspector.canArchiveMainFrame = function()
1863 {
1864     if (!PageAgent.archive)
1865         return false;
1866
1867     return WebInspector.Resource.typeFromMIMEType(WebInspector.frameResourceManager.mainFrame.mainResource.mimeType) === WebInspector.Resource.Type.Document;
1868 }
1869
1870 WebInspector.addWindowKeydownListener = function(listener)
1871 {
1872     if (typeof listener.handleKeydownEvent !== "function")
1873         return;
1874
1875     this._windowKeydownListeners.push(listener);
1876
1877     this._updateWindowKeydownListener();
1878 };
1879
1880 WebInspector.removeWindowKeydownListener = function(listener)
1881 {
1882     this._windowKeydownListeners.remove(listener);
1883
1884     this._updateWindowKeydownListener();
1885 };
1886
1887 WebInspector._updateWindowKeydownListener = function()
1888 {
1889     if (this._windowKeydownListeners.length > 0)
1890         window.addEventListener("keydown", WebInspector._sharedWindowKeydownListener, true);
1891     else
1892         window.removeEventListener("keydown", WebInspector._sharedWindowKeydownListener, true);
1893 }
1894
1895 WebInspector._sharedWindowKeydownListener = function(event)
1896 {
1897     for (var i = WebInspector._windowKeydownListeners.length - 1; i >= 0; --i) {
1898         if (WebInspector._windowKeydownListeners[i].handleKeydownEvent(event)) {
1899             event.stopImmediatePropagation();
1900             event.preventDefault();
1901             break;
1902         }
1903     }
1904 };