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