be6771765111ae942c7a71519302c97c1f0c6cc9
[WebKit-https.git] / Source / WebInspectorUI / UserInterface / Base / Main.js
1 /*
2  * Copyright (C) 2013-2017 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 WI.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 WI.SelectedSidebarPanelCookieKey = "selected-sidebar-panel";
37 WI.TypeIdentifierCookieKey = "represented-object-type";
38
39 WI.StateRestorationType = {
40     Load: "state-restoration-load",
41     Navigation: "state-restoration-navigation",
42     Delayed: "state-restoration-delayed",
43 };
44
45 WI.LayoutDirection = {
46     System: "system",
47     LTR: "ltr",
48     RTL: "rtl",
49 };
50
51 WI.loaded = function()
52 {
53     // Register observers for events from the InspectorBackend.
54     if (InspectorBackend.registerInspectorDispatcher)
55         InspectorBackend.registerInspectorDispatcher(new WI.InspectorObserver);
56     if (InspectorBackend.registerPageDispatcher)
57         InspectorBackend.registerPageDispatcher(new WI.PageObserver);
58     if (InspectorBackend.registerConsoleDispatcher)
59         InspectorBackend.registerConsoleDispatcher(new WI.ConsoleObserver);
60     if (InspectorBackend.registerNetworkDispatcher)
61         InspectorBackend.registerNetworkDispatcher(new WI.NetworkObserver);
62     if (InspectorBackend.registerDOMDispatcher)
63         InspectorBackend.registerDOMDispatcher(new WI.DOMObserver);
64     if (InspectorBackend.registerDebuggerDispatcher)
65         InspectorBackend.registerDebuggerDispatcher(new WI.DebuggerObserver);
66     if (InspectorBackend.registerHeapDispatcher)
67         InspectorBackend.registerHeapDispatcher(new WI.HeapObserver);
68     if (InspectorBackend.registerMemoryDispatcher)
69         InspectorBackend.registerMemoryDispatcher(new WI.MemoryObserver);
70     if (InspectorBackend.registerDatabaseDispatcher)
71         InspectorBackend.registerDatabaseDispatcher(new WI.DatabaseObserver);
72     if (InspectorBackend.registerDOMStorageDispatcher)
73         InspectorBackend.registerDOMStorageDispatcher(new WI.DOMStorageObserver);
74     if (InspectorBackend.registerApplicationCacheDispatcher)
75         InspectorBackend.registerApplicationCacheDispatcher(new WI.ApplicationCacheObserver);
76     if (InspectorBackend.registerScriptProfilerDispatcher)
77         InspectorBackend.registerScriptProfilerDispatcher(new WI.ScriptProfilerObserver);
78     if (InspectorBackend.registerTimelineDispatcher)
79         InspectorBackend.registerTimelineDispatcher(new WI.TimelineObserver);
80     if (InspectorBackend.registerCSSDispatcher)
81         InspectorBackend.registerCSSDispatcher(new WI.CSSObserver);
82     if (InspectorBackend.registerLayerTreeDispatcher)
83         InspectorBackend.registerLayerTreeDispatcher(new WI.LayerTreeObserver);
84     if (InspectorBackend.registerRuntimeDispatcher)
85         InspectorBackend.registerRuntimeDispatcher(new WI.RuntimeObserver);
86     if (InspectorBackend.registerWorkerDispatcher)
87         InspectorBackend.registerWorkerDispatcher(new WI.WorkerObserver);
88     if (InspectorBackend.registerCanvasDispatcher)
89         InspectorBackend.registerCanvasDispatcher(new WI.CanvasObserver);
90
91     // Listen for the ProvisionalLoadStarted event before registering for events so our code gets called before any managers or sidebars.
92     // This lets us save a state cookie before any managers or sidebars do any resets that would affect state (namely TimelineManager).
93     WI.Frame.addEventListener(WI.Frame.Event.ProvisionalLoadStarted, this._provisionalLoadStarted, this);
94
95     // Populate any UIStrings that must be done early after localized strings have loaded.
96     WI.KeyboardShortcut.Key.Space._displayName = WI.UIString("Space");
97
98     // Create the singleton managers next, before the user interface elements, so the user interface can register
99     // as event listeners on these managers.
100     WI.managers = [
101         WI.targetManager = new WI.TargetManager,
102         WI.branchManager = new WI.BranchManager,
103         WI.networkManager = new WI.NetworkManager,
104         WI.domStorageManager = new WI.DOMStorageManager,
105         WI.databaseManager = new WI.DatabaseManager,
106         WI.indexedDBManager = new WI.IndexedDBManager,
107         WI.domManager = new WI.DOMManager,
108         WI.cssManager = new WI.CSSManager,
109         WI.consoleManager = new WI.ConsoleManager,
110         WI.runtimeManager = new WI.RuntimeManager,
111         WI.heapManager = new WI.HeapManager,
112         WI.memoryManager = new WI.MemoryManager,
113         WI.applicationCacheManager = new WI.ApplicationCacheManager,
114         WI.timelineManager = new WI.TimelineManager,
115         WI.debuggerManager = new WI.DebuggerManager,
116         WI.layerTreeManager = new WI.LayerTreeManager,
117         WI.workerManager = new WI.WorkerManager,
118         WI.domDebuggerManager = new WI.DOMDebuggerManager,
119         WI.canvasManager = new WI.CanvasManager,
120         WI.auditManager = new WI.AuditManager,
121     ];
122
123     // Register for events.
124     WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.Paused, this._debuggerDidPause, this);
125     WI.debuggerManager.addEventListener(WI.DebuggerManager.Event.Resumed, this._debuggerDidResume, this);
126     WI.domManager.addEventListener(WI.DOMManager.Event.InspectModeStateChanged, this._inspectModeStateChanged, this);
127     WI.domManager.addEventListener(WI.DOMManager.Event.DOMNodeWasInspected, this._domNodeWasInspected, this);
128     WI.domStorageManager.addEventListener(WI.DOMStorageManager.Event.DOMStorageObjectWasInspected, this._domStorageWasInspected, this);
129     WI.databaseManager.addEventListener(WI.DatabaseManager.Event.DatabaseWasInspected, this._databaseWasInspected, this);
130     WI.networkManager.addEventListener(WI.NetworkManager.Event.MainFrameDidChange, this._mainFrameDidChange, this);
131     WI.networkManager.addEventListener(WI.NetworkManager.Event.FrameWasAdded, this._frameWasAdded, this);
132
133     WI.Frame.addEventListener(WI.Frame.Event.MainResourceDidChange, this._mainResourceDidChange, this);
134
135     document.addEventListener("DOMContentLoaded", this.contentLoaded.bind(this));
136
137     // Create settings.
138     this._showingSplitConsoleSetting = new WI.Setting("showing-split-console", false);
139     this._openTabsSetting = new WI.Setting("open-tab-types", ["elements", "network", "debugger", "resources", "timeline", "storage", "canvas", "console"]);
140     this._selectedTabIndexSetting = new WI.Setting("selected-tab-index", 0);
141
142     // State.
143     this.printStylesEnabled = false;
144     this.setZoomFactor(WI.settings.zoomFactor.value);
145     this.mouseCoords = {x: 0, y: 0};
146     this.visible = false;
147     this._windowKeydownListeners = [];
148
149     // Targets.
150     WI.mainTarget = new WI.MainTarget;
151     WI.mainTarget.initialize();
152     WI.pageTarget = WI.sharedApp.debuggableType === WI.DebuggableType.Web ? WI.mainTarget : null;
153
154     // Post-target initialization.
155     WI.targetManager.initializeMainTarget();
156     WI.runtimeManager.activeExecutionContext = WI.mainTarget.executionContext;
157 };
158
159 WI.contentLoaded = function()
160 {
161     // If there was an uncaught exception earlier during loading, then
162     // abort loading more content. We could be in an inconsistent state.
163     if (window.__uncaughtExceptions)
164         return;
165
166     // Register for global events.
167     document.addEventListener("beforecopy", this._beforecopy.bind(this));
168     document.addEventListener("copy", this._copy.bind(this));
169
170     document.addEventListener("click", this._mouseWasClicked.bind(this));
171     document.addEventListener("dragover", this._dragOver.bind(this));
172     document.addEventListener("focus", WI._focusChanged.bind(this), true);
173
174     window.addEventListener("focus", this._windowFocused.bind(this));
175     window.addEventListener("blur", this._windowBlurred.bind(this));
176     window.addEventListener("resize", this._windowResized.bind(this));
177     window.addEventListener("keydown", this._windowKeyDown.bind(this));
178     window.addEventListener("keyup", this._windowKeyUp.bind(this));
179     window.addEventListener("mousedown", this._mouseDown.bind(this), true);
180     window.addEventListener("mousemove", this._mouseMoved.bind(this), true);
181     window.addEventListener("pagehide", this._pageHidden.bind(this));
182     window.addEventListener("contextmenu", this._contextMenuRequested.bind(this));
183
184     // Add platform style classes so the UI can be tweaked per-platform.
185     document.body.classList.add(WI.Platform.name + "-platform");
186     if (WI.Platform.isNightlyBuild)
187         document.body.classList.add("nightly-build");
188
189     if (WI.Platform.name === "mac") {
190         document.body.classList.add(WI.Platform.version.name);
191
192         if (WI.Platform.version.release >= 11)
193             document.body.classList.add("latest-mac");
194         else
195             document.body.classList.add("legacy-mac");
196     }
197
198     document.body.classList.add(WI.sharedApp.debuggableType);
199     document.body.setAttribute("dir", this.resolvedLayoutDirection());
200
201     WI.settings.showJavaScriptTypeInformation.addEventListener(WI.Setting.Event.Changed, this._showJavaScriptTypeInformationSettingChanged, this);
202     WI.settings.enableControlFlowProfiler.addEventListener(WI.Setting.Event.Changed, this._enableControlFlowProfilerSettingChanged, this);
203     WI.settings.resourceCachingDisabled.addEventListener(WI.Setting.Event.Changed, this._resourceCachingDisabledSettingChanged, this);
204
205     function setTabSize() {
206         document.body.style.tabSize = WI.settings.tabSize.value;
207     }
208     WI.settings.tabSize.addEventListener(WI.Setting.Event.Changed, setTabSize);
209     setTabSize();
210
211     function setInvalidCharacterClassName() {
212         document.body.classList.toggle("show-invalid-characters", WI.settings.showInvalidCharacters.value);
213     }
214     WI.settings.showInvalidCharacters.addEventListener(WI.Setting.Event.Changed, setInvalidCharacterClassName);
215     setInvalidCharacterClassName();
216
217     function setWhitespaceCharacterClassName() {
218         document.body.classList.toggle("show-whitespace-characters", WI.settings.showWhitespaceCharacters.value);
219     }
220     WI.settings.showWhitespaceCharacters.addEventListener(WI.Setting.Event.Changed, setWhitespaceCharacterClassName);
221     setWhitespaceCharacterClassName();
222
223     this.settingsTabContentView = new WI.SettingsTabContentView;
224
225     this._settingsKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl, WI.KeyboardShortcut.Key.Comma, this._showSettingsTab.bind(this));
226
227     // Create the user interface elements.
228     this.toolbar = new WI.Toolbar(document.getElementById("toolbar"));
229
230     if (WI.settings.experimentalEnableNewTabBar.value)
231         this.tabBar = new WI.TabBar(document.getElementById("tab-bar"));
232     else {
233         this.tabBar = new WI.LegacyTabBar(document.getElementById("tab-bar"));
234         this.tabBar.addEventListener(WI.TabBar.Event.OpenDefaultTab, this._openDefaultTab, this);
235     }
236
237     this._contentElement = document.getElementById("content");
238     this._contentElement.setAttribute("role", "main");
239     this._contentElement.setAttribute("aria-label", WI.UIString("Content"));
240
241     this.clearKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl, "K", this._clear.bind(this));
242
243     // FIXME: <https://webkit.org/b/151310> Web Inspector: Command-E should propagate to other search fields (including the system)
244     this.populateFindKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl, "E", this._populateFind.bind(this));
245     this.populateFindKeyboardShortcut.implicitlyPreventsDefault = false;
246     this.findNextKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl, "G", this._findNext.bind(this));
247     this.findPreviousKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.Shift | WI.KeyboardShortcut.Modifier.CommandOrControl, "G", this._findPrevious.bind(this));
248
249     this.consoleDrawer = new WI.ConsoleDrawer(document.getElementById("console-drawer"));
250     this.consoleDrawer.addEventListener(WI.ConsoleDrawer.Event.CollapsedStateChanged, this._consoleDrawerCollapsedStateDidChange, this);
251     this.consoleDrawer.addEventListener(WI.ConsoleDrawer.Event.Resized, this._consoleDrawerDidResize, this);
252
253     this.quickConsole = new WI.QuickConsole(document.getElementById("quick-console"));
254
255     this._consoleRepresentedObject = new WI.LogObject;
256     this.consoleContentView = this.consoleDrawer.contentViewForRepresentedObject(this._consoleRepresentedObject);
257     this.consoleLogViewController = this.consoleContentView.logViewController;
258     this.breakpointPopoverController = new WI.BreakpointPopoverController;
259
260     // FIXME: The sidebars should be flipped in RTL languages.
261     this.navigationSidebar = new WI.Sidebar(document.getElementById("navigation-sidebar"), WI.Sidebar.Sides.Left);
262     this.navigationSidebar.addEventListener(WI.Sidebar.Event.WidthDidChange, this._sidebarWidthDidChange, this);
263
264     this.detailsSidebar = new WI.Sidebar(document.getElementById("details-sidebar"), WI.Sidebar.Sides.Right, null, null, WI.UIString("Details"), true);
265     this.detailsSidebar.addEventListener(WI.Sidebar.Event.WidthDidChange, this._sidebarWidthDidChange, this);
266
267     this.searchKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl | WI.KeyboardShortcut.Modifier.Shift, "F", this._focusSearchField.bind(this));
268     this._findKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl, "F", this._find.bind(this));
269     this.saveKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl, "S", this._save.bind(this));
270     this._saveAsKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.Shift | WI.KeyboardShortcut.Modifier.CommandOrControl, "S", this._saveAs.bind(this));
271
272     this.openResourceKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl | WI.KeyboardShortcut.Modifier.Shift, "O", this._showOpenResourceDialog.bind(this));
273     new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl, "P", this._showOpenResourceDialog.bind(this));
274
275     this.navigationSidebarKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl | WI.KeyboardShortcut.Modifier.Shift, "0", this.toggleNavigationSidebar.bind(this));
276     this.detailsSidebarKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl | WI.KeyboardShortcut.Modifier.Option, "0", this.toggleDetailsSidebar.bind(this));
277
278     let boundIncreaseZoom = this._increaseZoom.bind(this);
279     let boundDecreaseZoom = this._decreaseZoom.bind(this);
280     this._increaseZoomKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl, WI.KeyboardShortcut.Key.Plus, boundIncreaseZoom);
281     this._decreaseZoomKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl, WI.KeyboardShortcut.Key.Minus, boundDecreaseZoom);
282     this._increaseZoomKeyboardShortcut2 = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl | WI.KeyboardShortcut.Modifier.Shift, WI.KeyboardShortcut.Key.Plus, boundIncreaseZoom);
283     this._decreaseZoomKeyboardShortcut2 = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl | WI.KeyboardShortcut.Modifier.Shift, WI.KeyboardShortcut.Key.Minus, boundDecreaseZoom);
284     this._resetZoomKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl, "0", this._resetZoom.bind(this));
285
286     this._showTabAtIndexKeyboardShortcuts = [1, 2, 3, 4, 5, 6, 7, 8, 9].map((i) => new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl | WI.KeyboardShortcut.Modifier.Option, `${i}`, this._showTabAtIndex.bind(this, i)));
287     this._openNewTabKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl | WI.KeyboardShortcut.Modifier.Option, "T", this.showNewTabTab.bind(this));
288
289     this.tabBrowser = new WI.TabBrowser(document.getElementById("tab-browser"), this.tabBar, this.navigationSidebar, this.detailsSidebar);
290     this.tabBrowser.addEventListener(WI.TabBrowser.Event.SelectedTabContentViewDidChange, this._tabBrowserSelectedTabContentViewDidChange, this);
291
292     this._reloadPageKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl, "R", this._reloadPage.bind(this));
293     this._reloadPageFromOriginKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl | WI.KeyboardShortcut.Modifier.Option, "R", this._reloadPageFromOrigin.bind(this));
294     this._reloadPageKeyboardShortcut.implicitlyPreventsDefault = this._reloadPageFromOriginKeyboardShortcut.implicitlyPreventsDefault = false;
295
296     this._consoleTabKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.Option | WI.KeyboardShortcut.Modifier.CommandOrControl, "C", this._showConsoleTab.bind(this));
297     this._quickConsoleKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.Control, WI.KeyboardShortcut.Key.Apostrophe, this._focusConsolePrompt.bind(this));
298
299     this._inspectModeKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl | WI.KeyboardShortcut.Modifier.Shift, "C", this._toggleInspectMode.bind(this));
300
301     this._undoKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl, "Z", this._undoKeyboardShortcut.bind(this));
302     this._redoKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl | WI.KeyboardShortcut.Modifier.Shift, "Z", this._redoKeyboardShortcut.bind(this));
303     this._undoKeyboardShortcut.implicitlyPreventsDefault = this._redoKeyboardShortcut.implicitlyPreventsDefault = false;
304
305     this.toggleBreakpointsKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl, "Y", this.debuggerToggleBreakpoints.bind(this));
306     this.pauseOrResumeKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.Control | WI.KeyboardShortcut.Modifier.CommandOrControl, "Y", this.debuggerPauseResumeToggle.bind(this));
307     this.stepOverKeyboardShortcut = new WI.KeyboardShortcut(null, WI.KeyboardShortcut.Key.F6, this.debuggerStepOver.bind(this));
308     this.stepIntoKeyboardShortcut = new WI.KeyboardShortcut(null, WI.KeyboardShortcut.Key.F7, this.debuggerStepInto.bind(this));
309     this.stepOutKeyboardShortcut = new WI.KeyboardShortcut(null, WI.KeyboardShortcut.Key.F8, this.debuggerStepOut.bind(this));
310
311     this.pauseOrResumeAlternateKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl, WI.KeyboardShortcut.Key.Backslash, this.debuggerPauseResumeToggle.bind(this));
312     this.stepOverAlternateKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl, WI.KeyboardShortcut.Key.SingleQuote, this.debuggerStepOver.bind(this));
313     this.stepIntoAlternateKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl, WI.KeyboardShortcut.Key.Semicolon, this.debuggerStepInto.bind(this));
314     this.stepOutAlternateKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.Shift | WI.KeyboardShortcut.Modifier.CommandOrControl, WI.KeyboardShortcut.Key.Semicolon, this.debuggerStepOut.bind(this));
315
316     this._closeToolbarButton = new WI.ControlToolbarItem("dock-close", WI.UIString("Close"), "Images/Close.svg", 16, 14);
317     this._closeToolbarButton.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this.close, this);
318
319     this._undockToolbarButton = new WI.ButtonToolbarItem("undock", WI.UIString("Detach into separate window"), "Images/Undock.svg");
320     this._undockToolbarButton.element.classList.add(WI.Popover.IgnoreAutoDismissClassName);
321     this._undockToolbarButton.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._undock, this);
322
323     let dockImage = WI.resolvedLayoutDirection() === WI.LayoutDirection.RTL ? "Images/DockLeft.svg" : "Images/DockRight.svg";
324     this._dockToSideToolbarButton = new WI.ButtonToolbarItem("dock-right", WI.UIString("Dock to side of window"), dockImage);
325     this._dockToSideToolbarButton.element.classList.add(WI.Popover.IgnoreAutoDismissClassName);
326
327     let dockToSideCallback = WI.resolvedLayoutDirection() === WI.LayoutDirection.RTL ? this._dockLeft : this._dockRight;
328     this._dockToSideToolbarButton.addEventListener(WI.ButtonNavigationItem.Event.Clicked, dockToSideCallback, this);
329
330     this._dockBottomToolbarButton = new WI.ButtonToolbarItem("dock-bottom", WI.UIString("Dock to bottom of window"), "Images/DockBottom.svg");
331     this._dockBottomToolbarButton.element.classList.add(WI.Popover.IgnoreAutoDismissClassName);
332     this._dockBottomToolbarButton.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._dockBottom, this);
333
334     this._togglePreviousDockConfigurationKeyboardShortcut = new WI.KeyboardShortcut(WI.KeyboardShortcut.Modifier.CommandOrControl | WI.KeyboardShortcut.Modifier.Shift, "D", this._togglePreviousDockConfiguration.bind(this));
335
336     let reloadToolTip;
337     if (WI.sharedApp.debuggableType === WI.DebuggableType.JavaScript)
338         reloadToolTip = WI.UIString("Restart (%s)").format(this._reloadPageKeyboardShortcut.displayName);
339     else
340         reloadToolTip = WI.UIString("Reload page (%s)\nReload page ignoring cache (%s)").format(this._reloadPageKeyboardShortcut.displayName, this._reloadPageFromOriginKeyboardShortcut.displayName);
341     this._reloadToolbarButton = new WI.ButtonToolbarItem("reload", reloadToolTip, "Images/ReloadToolbar.svg");
342     this._reloadToolbarButton.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._reloadToolbarButtonClicked, this);
343
344     this._downloadToolbarButton = new WI.ButtonToolbarItem("download", WI.UIString("Download Web Archive"), "Images/DownloadArrow.svg");
345     this._downloadToolbarButton.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._downloadWebArchive, this);
346
347     let elementSelectionToolTip = WI.UIString("Start element selection (%s)").format(WI._inspectModeKeyboardShortcut.displayName);
348     let activatedElementSelectionToolTip = WI.UIString("Stop element selection (%s)").format(WI._inspectModeKeyboardShortcut.displayName);
349     this._inspectModeToolbarButton = new WI.ActivateButtonToolbarItem("inspect", elementSelectionToolTip, activatedElementSelectionToolTip, "Images/Crosshair.svg");
350     this._inspectModeToolbarButton.addEventListener(WI.ButtonNavigationItem.Event.Clicked, this._toggleInspectMode, this);
351
352     this._updateReloadToolbarButton();
353     this._updateDownloadToolbarButton();
354     this._updateInspectModeToolbarButton();
355
356     this._dashboards = {
357         default: new WI.DefaultDashboard,
358         debugger: new WI.DebuggerDashboard,
359     };
360
361     this._dashboardContainer = new WI.DashboardContainerView;
362     this._dashboardContainer.showDashboardViewForRepresentedObject(this._dashboards.default);
363
364     this.toolbar.addToolbarItem(this._closeToolbarButton, WI.Toolbar.Section.Control);
365
366     this.toolbar.addToolbarItem(this._undockToolbarButton, WI.Toolbar.Section.Left);
367     this.toolbar.addToolbarItem(this._dockToSideToolbarButton, WI.Toolbar.Section.Left);
368     this.toolbar.addToolbarItem(this._dockBottomToolbarButton, WI.Toolbar.Section.Left);
369
370     this.toolbar.addToolbarItem(this._reloadToolbarButton, WI.Toolbar.Section.CenterLeft);
371     this.toolbar.addToolbarItem(this._downloadToolbarButton, WI.Toolbar.Section.CenterLeft);
372
373     this.toolbar.addToolbarItem(this._dashboardContainer.toolbarItem, WI.Toolbar.Section.Center);
374
375     this.toolbar.addToolbarItem(this._inspectModeToolbarButton, WI.Toolbar.Section.CenterRight);
376
377     this._searchTabContentView = new WI.SearchTabContentView;
378
379     if (WI.settings.experimentalEnableNewTabBar.value) {
380         this.tabBrowser.addTabForContentView(this._searchTabContentView, {suppressAnimations: true});
381         this.tabBar.addTabBarItem(this.settingsTabContentView.tabBarItem, {suppressAnimations: true});
382     } else {
383         const incremental = false;
384         this._searchToolbarItem = new WI.SearchBar("inspector-search", WI.UIString("Search"), incremental);
385         this._searchToolbarItem.addEventListener(WI.SearchBar.Event.TextChanged, this._searchTextDidChange, this);
386         this.toolbar.addToolbarItem(this._searchToolbarItem, WI.Toolbar.Section.Right);
387     }
388
389     this.modifierKeys = {altKey: false, metaKey: false, shiftKey: false};
390
391     let dockedResizerElement = document.getElementById("docked-resizer");
392     dockedResizerElement.classList.add(WI.Popover.IgnoreAutoDismissClassName);
393     dockedResizerElement.addEventListener("mousedown", this._dockedResizerMouseDown.bind(this));
394
395     this._dockingAvailable = false;
396
397     this._updateDockNavigationItems();
398     this._setupViewHierarchy();
399
400     // These tabs are always available for selecting, modulo isTabAllowed().
401     // Other tabs may be engineering-only or toggled at runtime if incomplete.
402     let productionTabClasses = [
403         WI.ElementsTabContentView,
404         WI.NetworkTabContentView,
405         WI.DebuggerTabContentView,
406         WI.ResourcesTabContentView,
407         WI.TimelineTabContentView,
408         WI.StorageTabContentView,
409         WI.CanvasTabContentView,
410         WI.LayersTabContentView,
411         WI.AuditTabContentView,
412         WI.ConsoleTabContentView,
413         WI.SearchTabContentView,
414         WI.NewTabContentView,
415         WI.SettingsTabContentView,
416     ];
417
418     this._knownTabClassesByType = new Map;
419     // Set tab classes directly. The public API triggers other updates and
420     // notifications that won't work or have no listeners at this point.
421     for (let tabClass of productionTabClasses)
422         this._knownTabClassesByType.set(tabClass.Type, tabClass);
423
424     this._pendingOpenTabs = [];
425
426     // Previously we may have stored duplicates in this setting. Avoid creating duplicate tabs.
427     let openTabTypes = this._openTabsSetting.value;
428     let seenTabTypes = new Set;
429
430     for (let i = 0; i < openTabTypes.length; ++i) {
431         let tabType = openTabTypes[i];
432
433         if (seenTabTypes.has(tabType))
434             continue;
435         seenTabTypes.add(tabType);
436
437         if (!this.isTabTypeAllowed(tabType)) {
438             this._pendingOpenTabs.push({tabType, index: i});
439             continue;
440         }
441
442         if (!this.isNewTabWithTypeAllowed(tabType))
443             continue;
444
445         let tabContentView = this._createTabContentViewForType(tabType);
446         if (!tabContentView)
447             continue;
448         this.tabBrowser.addTabForContentView(tabContentView, {suppressAnimations: true});
449     }
450
451     this._restoreCookieForOpenTabs(WI.StateRestorationType.Load);
452
453     this.tabBar.selectedTabBarItem = this._selectedTabIndexSetting.value;
454
455     if (!this.tabBar.selectedTabBarItem)
456         this.tabBar.selectedTabBarItem = 0;
457
458     if (!this.tabBar.normalTabCount)
459         this.showNewTabTab({suppressAnimations: true});
460
461     // Listen to the events after restoring the saved tabs to avoid recursion.
462     this.tabBar.addEventListener(WI.TabBar.Event.TabBarItemAdded, this._rememberOpenTabs, this);
463     this.tabBar.addEventListener(WI.TabBar.Event.TabBarItemRemoved, this._rememberOpenTabs, this);
464     this.tabBar.addEventListener(WI.TabBar.Event.TabBarItemsReordered, this._rememberOpenTabs, this);
465
466     // Signal that the frontend is now ready to receive messages.
467     InspectorFrontendAPI.loadCompleted();
468
469     // Tell the InspectorFrontendHost we loaded, which causes the window to display
470     // and pending InspectorFrontendAPI commands to be sent.
471     InspectorFrontendHost.loaded();
472
473     if (this._showingSplitConsoleSetting.value)
474         this.showSplitConsole();
475
476     // Store this on the window in case the WebInspector global gets corrupted.
477     window.__frontendCompletedLoad = true;
478
479     if (this.runBootstrapOperations)
480         this.runBootstrapOperations();
481 };
482
483 WI.performOneTimeFrontendInitializationsUsingTarget = function(target)
484 {
485     if (!WI.__didPerformConsoleInitialization && target.ConsoleAgent) {
486         WI.__didPerformConsoleInitialization = true;
487         WI.consoleManager.initializeLogChannels(target);
488     }
489
490     if (!WI.__didPerformCSSInitialization && target.CSSAgent) {
491         WI.__didPerformCSSInitialization = true;
492         WI.CSSCompletions.initializeCSSCompletions(target);
493     }
494 };
495
496 WI.isTabTypeAllowed = function(tabType)
497 {
498     let tabClass = this._knownTabClassesByType.get(tabType);
499     if (!tabClass)
500         return false;
501
502     return tabClass.isTabAllowed();
503 };
504
505 WI.knownTabClasses = function()
506 {
507     return new Set(this._knownTabClassesByType.values());
508 };
509
510 WI._showOpenResourceDialog = function()
511 {
512     if (!this._openResourceDialog)
513         this._openResourceDialog = new WI.OpenResourceDialog(this);
514
515     if (this._openResourceDialog.visible)
516         return;
517
518     this._openResourceDialog.present(this._contentElement);
519 };
520
521 WI._createTabContentViewForType = function(tabType)
522 {
523     let tabClass = this._knownTabClassesByType.get(tabType);
524     if (!tabClass) {
525         console.error("Unknown tab type", tabType);
526         return null;
527     }
528
529     console.assert(WI.TabContentView.isPrototypeOf(tabClass));
530     return new tabClass;
531 };
532
533 WI._rememberOpenTabs = function()
534 {
535     let seenTabTypes = new Set;
536     let openTabs = [];
537
538     for (let tabBarItem of this.tabBar.tabBarItems) {
539         let tabContentView = tabBarItem.representedObject;
540         if (!(tabContentView instanceof WI.TabContentView))
541             continue;
542         if (!tabContentView.constructor.shouldSaveTab())
543             continue;
544         console.assert(tabContentView.type, "Tab type can't be null, undefined, or empty string", tabContentView.type, tabContentView);
545         openTabs.push(tabContentView.type);
546         seenTabTypes.add(tabContentView.type);
547     }
548
549     // Keep currently unsupported tabs in the setting at their previous index.
550     for (let {tabType, index} of this._pendingOpenTabs) {
551         if (seenTabTypes.has(tabType))
552             continue;
553         openTabs.insertAtIndex(tabType, index);
554         seenTabTypes.add(tabType);
555     }
556
557     this._openTabsSetting.value = openTabs;
558 };
559
560 WI._openDefaultTab = function(event)
561 {
562     this.showNewTabTab({suppressAnimations: true});
563 };
564
565 WI._showSettingsTab = function(event)
566 {
567     this.tabBrowser.showTabForContentView(this.settingsTabContentView);
568 };
569
570 WI._tryToRestorePendingTabs = function()
571 {
572     let stillPendingOpenTabs = [];
573     for (let {tabType, index} of this._pendingOpenTabs) {
574         if (!this.isTabTypeAllowed(tabType)) {
575             stillPendingOpenTabs.push({tabType, index});
576             continue;
577         }
578
579         let tabContentView = this._createTabContentViewForType(tabType);
580         if (!tabContentView)
581             continue;
582
583         this.tabBrowser.addTabForContentView(tabContentView, {
584             suppressAnimations: true,
585             insertionIndex: index,
586         });
587
588         tabContentView.restoreStateFromCookie(WI.StateRestorationType.Load);
589     }
590
591     this._pendingOpenTabs = stillPendingOpenTabs;
592
593     if (!WI.settings.experimentalEnableNewTabBar.value)
594         this.tabBar.updateNewTabTabBarItemState();
595 };
596
597 WI.showNewTabTab = function(options)
598 {
599     if (!this.isNewTabWithTypeAllowed(WI.NewTabContentView.Type))
600         return;
601
602     let tabContentView = this.tabBrowser.bestTabContentViewForClass(WI.NewTabContentView);
603     if (!tabContentView)
604         tabContentView = new WI.NewTabContentView;
605     this.tabBrowser.showTabForContentView(tabContentView, options);
606 };
607
608 WI.isNewTabWithTypeAllowed = function(tabType)
609 {
610     let tabClass = this._knownTabClassesByType.get(tabType);
611     if (!tabClass || !tabClass.isTabAllowed())
612         return false;
613
614     // Only allow one tab per class for now.
615     for (let tabBarItem of this.tabBar.tabBarItems) {
616         let tabContentView = tabBarItem.representedObject;
617         if (!(tabContentView instanceof WI.TabContentView))
618             continue;
619         if (tabContentView.constructor === tabClass)
620             return false;
621     }
622
623     if (tabClass === WI.NewTabContentView) {
624         let allTabs = Array.from(this.knownTabClasses());
625         let addableTabs = allTabs.filter((tabClass) => !tabClass.tabInfo().isEphemeral);
626         let canMakeNewTab = addableTabs.some((tabClass) => WI.isNewTabWithTypeAllowed(tabClass.Type));
627         return canMakeNewTab;
628     }
629
630     return true;
631 };
632
633 WI.createNewTabWithType = function(tabType, options = {})
634 {
635     console.assert(this.isNewTabWithTypeAllowed(tabType));
636
637     let {referencedView, shouldReplaceTab, shouldShowNewTab} = options;
638     console.assert(!referencedView || referencedView instanceof WI.TabContentView, referencedView);
639     console.assert(!shouldReplaceTab || referencedView, "Must provide a reference view to replace a tab.");
640
641     let tabContentView = this._createTabContentViewForType(tabType);
642     const suppressAnimations = true;
643     this.tabBrowser.addTabForContentView(tabContentView, {
644         suppressAnimations,
645         insertionIndex: referencedView ? this.tabBar.tabBarItems.indexOf(referencedView.tabBarItem) : undefined,
646     });
647
648     if (shouldReplaceTab)
649         this.tabBrowser.closeTabForContentView(referencedView, {suppressAnimations});
650
651     if (shouldShowNewTab)
652         this.tabBrowser.showTabForContentView(tabContentView);
653 };
654
655 WI.activateExtraDomains = function(domains)
656 {
657     this.notifications.dispatchEventToListeners(WI.Notification.ExtraDomainsActivated, {domains});
658
659     if (WI.mainTarget && WI.mainTarget.CSSAgent)
660         WI.CSSCompletions.initializeCSSCompletions(WI.assumingMainTarget());
661
662     this._updateReloadToolbarButton();
663     this._updateDownloadToolbarButton();
664     this._updateInspectModeToolbarButton();
665
666     this._tryToRestorePendingTabs();
667 };
668
669 WI.updateWindowTitle = function()
670 {
671     var mainFrame = this.networkManager.mainFrame;
672     if (!mainFrame)
673         return;
674
675     var urlComponents = mainFrame.mainResource.urlComponents;
676
677     var lastPathComponent;
678     try {
679         lastPathComponent = decodeURIComponent(urlComponents.lastPathComponent || "");
680     } catch {
681         lastPathComponent = urlComponents.lastPathComponent;
682     }
683
684     // Build a title based on the URL components.
685     if (urlComponents.host && lastPathComponent)
686         var title = this.displayNameForHost(urlComponents.host) + " \u2014 " + lastPathComponent;
687     else if (urlComponents.host)
688         var title = this.displayNameForHost(urlComponents.host);
689     else if (lastPathComponent)
690         var title = lastPathComponent;
691     else
692         var title = mainFrame.url;
693
694     // The name "inspectedURLChanged" sounds like the whole URL is required, however this is only
695     // used for updating the window title and it can be any string.
696     InspectorFrontendHost.inspectedURLChanged(title);
697 };
698
699 WI.updateDockingAvailability = function(available)
700 {
701     this._dockingAvailable = available;
702
703     this._updateDockNavigationItems();
704 };
705
706 WI.updateDockedState = function(side)
707 {
708     if (this._dockConfiguration === side)
709         return;
710
711     this._previousDockConfiguration = this._dockConfiguration;
712
713     if (!this._previousDockConfiguration) {
714         if (side === WI.DockConfiguration.Right || side === WI.DockConfiguration.Left)
715             this._previousDockConfiguration = WI.DockConfiguration.Bottom;
716         else
717             this._previousDockConfiguration = WI.resolvedLayoutDirection() === WI.LayoutDirection.RTL ? WI.DockConfiguration.Left : WI.DockConfiguration.Right;
718     }
719
720     this._dockConfiguration = side;
721
722     this.docked = side !== WI.DockConfiguration.Undocked;
723
724     this._ignoreToolbarModeDidChangeEvents = true;
725
726     if (side === WI.DockConfiguration.Bottom) {
727         document.body.classList.add("docked", WI.DockConfiguration.Bottom);
728         document.body.classList.remove("window-inactive", WI.DockConfiguration.Right, WI.DockConfiguration.Left);
729     } else if (side === WI.DockConfiguration.Right) {
730         document.body.classList.add("docked", WI.DockConfiguration.Right);
731         document.body.classList.remove("window-inactive", WI.DockConfiguration.Bottom, WI.DockConfiguration.Left);
732     } else if (side === WI.DockConfiguration.Left) {
733         document.body.classList.add("docked", WI.DockConfiguration.Left);
734         document.body.classList.remove("window-inactive", WI.DockConfiguration.Bottom, WI.DockConfiguration.Right);
735     } else
736         document.body.classList.remove("docked", WI.DockConfiguration.Right, WI.DockConfiguration.Left, WI.DockConfiguration.Bottom);
737
738     this._ignoreToolbarModeDidChangeEvents = false;
739
740     this._updateDockNavigationItems();
741
742     if (!this.dockedConfigurationSupportsSplitContentBrowser() && !this.doesCurrentTabSupportSplitContentBrowser())
743         this.hideSplitConsole();
744 };
745
746 WI.updateVisibilityState = function(visible)
747 {
748     this.visible = visible;
749     this.notifications.dispatchEventToListeners(WI.Notification.VisibilityStateDidChange);
750 };
751
752 WI.handlePossibleLinkClick = function(event, frame, options = {})
753 {
754     let anchorElement = event.target.enclosingNodeOrSelfWithNodeName("a");
755     if (!anchorElement || !anchorElement.href)
756         return false;
757
758     if (WI.isBeingEdited(anchorElement)) {
759         // Don't follow the link when it is being edited.
760         return false;
761     }
762
763     // Prevent the link from navigating, since we don't do any navigation by following links normally.
764     event.preventDefault();
765     event.stopPropagation();
766
767     this.openURL(anchorElement.href, frame, {
768         ...options,
769         lineNumber: anchorElement.lineNumber,
770         ignoreSearchTab: !WI.isShowingSearchTab(),
771     });
772
773     return true;
774 };
775
776 WI.openURL = function(url, frame, options = {})
777 {
778     console.assert(url);
779     if (!url)
780         return;
781
782     console.assert(typeof options.lineNumber === "undefined" || typeof options.lineNumber === "number", "lineNumber should be a number.");
783
784     // If alwaysOpenExternally is not defined, base it off the command/meta key for the current event.
785     if (options.alwaysOpenExternally === undefined || options.alwaysOpenExternally === null)
786         options.alwaysOpenExternally = window.event ? window.event.metaKey : false;
787
788     if (options.alwaysOpenExternally) {
789         InspectorFrontendHost.openInNewTab(url);
790         return;
791     }
792
793     let searchChildFrames = false;
794     if (!frame) {
795         frame = this.networkManager.mainFrame;
796         searchChildFrames = true;
797     }
798
799     let resource;
800     let simplifiedURL = removeURLFragment(url);
801     if (frame) {
802         // WI.Frame.resourceForURL does not check the main resource, only sub-resources. So check both.
803         resource = frame.url === simplifiedURL ? frame.mainResource : frame.resourceForURL(simplifiedURL, searchChildFrames);
804     } else if (WI.sharedApp.debuggableType === WI.DebuggableType.ServiceWorker)
805         resource = WI.mainTarget.resourceCollection.resourceForURL(removeURLFragment(url));
806
807     if (resource) {
808         let positionToReveal = new WI.SourceCodePosition(options.lineNumber, 0);
809         this.showSourceCode(resource, {...options, positionToReveal});
810         return;
811     }
812
813     InspectorFrontendHost.openInNewTab(url);
814 };
815
816 WI.close = function()
817 {
818     if (this._isClosing)
819         return;
820
821     this._isClosing = true;
822
823     InspectorFrontendHost.closeWindow();
824 };
825
826 WI.isConsoleFocused = function()
827 {
828     return this.quickConsole.prompt.focused;
829 };
830
831 WI.isShowingSplitConsole = function()
832 {
833     return !this.consoleDrawer.collapsed;
834 };
835
836 WI.dockedConfigurationSupportsSplitContentBrowser = function()
837 {
838     return this._dockConfiguration !== WI.DockConfiguration.Bottom;
839 };
840
841 WI.doesCurrentTabSupportSplitContentBrowser = function()
842 {
843     var currentContentView = this.tabBrowser.selectedTabContentView;
844     return !currentContentView || currentContentView.supportsSplitContentBrowser;
845 };
846
847 WI.toggleSplitConsole = function()
848 {
849     if (!this.doesCurrentTabSupportSplitContentBrowser()) {
850         this.showConsoleTab();
851         return;
852     }
853
854     if (this.isShowingSplitConsole())
855         this.hideSplitConsole();
856     else
857         this.showSplitConsole();
858 };
859
860 WI.showSplitConsole = function()
861 {
862     if (!this.doesCurrentTabSupportSplitContentBrowser()) {
863         this.showConsoleTab();
864         return;
865     }
866
867     this.consoleDrawer.collapsed = false;
868
869     if (this.consoleDrawer.currentContentView === this.consoleContentView)
870         return;
871
872     this.consoleDrawer.showContentView(this.consoleContentView);
873 };
874
875 WI.hideSplitConsole = function()
876 {
877     if (!this.isShowingSplitConsole())
878         return;
879
880     this.consoleDrawer.collapsed = true;
881 };
882
883 WI.showConsoleTab = function(requestedScope)
884 {
885     requestedScope = requestedScope || WI.LogContentView.Scopes.All;
886
887     this.hideSplitConsole();
888
889     this.consoleContentView.scopeBar.item(requestedScope).selected = true;
890
891     this.showRepresentedObject(this._consoleRepresentedObject);
892
893     console.assert(this.isShowingConsoleTab());
894 };
895
896 WI.isShowingConsoleTab = function()
897 {
898     return this.tabBrowser.selectedTabContentView instanceof WI.ConsoleTabContentView;
899 };
900
901 WI.showElementsTab = function()
902 {
903     var tabContentView = this.tabBrowser.bestTabContentViewForClass(WI.ElementsTabContentView);
904     if (!tabContentView)
905         tabContentView = new WI.ElementsTabContentView;
906     this.tabBrowser.showTabForContentView(tabContentView);
907 };
908
909 WI.isShowingElementsTab = function()
910 {
911     return this.tabBrowser.selectedTabContentView instanceof WI.ElementsTabContentView;
912 };
913
914 WI.showDebuggerTab = function(options)
915 {
916     var tabContentView = this.tabBrowser.bestTabContentViewForClass(WI.DebuggerTabContentView);
917     if (!tabContentView)
918         tabContentView = new WI.DebuggerTabContentView;
919
920     if (options.breakpointToSelect instanceof WI.Breakpoint)
921         tabContentView.revealAndSelectBreakpoint(options.breakpointToSelect);
922
923     if (options.showScopeChainSidebar)
924         tabContentView.showScopeChainDetailsSidebarPanel();
925
926     this.tabBrowser.showTabForContentView(tabContentView);
927 };
928
929 WI.isShowingDebuggerTab = function()
930 {
931     return this.tabBrowser.selectedTabContentView instanceof WI.DebuggerTabContentView;
932 };
933
934 WI.showResourcesTab = function()
935 {
936     var tabContentView = this.tabBrowser.bestTabContentViewForClass(WI.ResourcesTabContentView);
937     if (!tabContentView)
938         tabContentView = new WI.ResourcesTabContentView;
939     this.tabBrowser.showTabForContentView(tabContentView);
940 };
941
942 WI.isShowingResourcesTab = function()
943 {
944     return this.tabBrowser.selectedTabContentView instanceof WI.ResourcesTabContentView;
945 };
946
947 WI.showStorageTab = function()
948 {
949     var tabContentView = this.tabBrowser.bestTabContentViewForClass(WI.StorageTabContentView);
950     if (!tabContentView)
951         tabContentView = new WI.StorageTabContentView;
952     this.tabBrowser.showTabForContentView(tabContentView);
953 };
954
955 WI.showNetworkTab = function()
956 {
957     let tabContentView = this.tabBrowser.bestTabContentViewForClass(WI.NetworkTabContentView);
958     if (!tabContentView)
959         tabContentView = new WI.NetworkTabContentView;
960
961     this.tabBrowser.showTabForContentView(tabContentView);
962 };
963
964 WI.isShowingNetworkTab = function()
965 {
966     return this.tabBrowser.selectedTabContentView instanceof WI.NetworkTabContentView;
967 };
968
969 WI.isShowingSearchTab = function()
970 {
971     return this.tabBrowser.selectedTabContentView instanceof WI.SearchTabContentView;
972 };
973
974 WI.showTimelineTab = function()
975 {
976     var tabContentView = this.tabBrowser.bestTabContentViewForClass(WI.TimelineTabContentView);
977     if (!tabContentView)
978         tabContentView = new WI.TimelineTabContentView;
979     this.tabBrowser.showTabForContentView(tabContentView);
980 };
981
982 WI.showLayersTab = function(options = {})
983 {
984     let tabContentView = this.tabBrowser.bestTabContentViewForClass(WI.LayersTabContentView);
985     if (!tabContentView)
986         tabContentView = new WI.LayersTabContentView;
987     if (options.nodeToSelect)
988         tabContentView.selectLayerForNode(options.nodeToSelect);
989     this.tabBrowser.showTabForContentView(tabContentView);
990 };
991
992 WI.isShowingLayersTab = function()
993 {
994     return this.tabBrowser.selectedTabContentView instanceof WI.LayersTabContentView;
995 };
996
997 WI.indentString = function()
998 {
999     if (WI.settings.indentWithTabs.value)
1000         return "\t";
1001     return " ".repeat(WI.settings.indentUnit.value);
1002 };
1003
1004 WI.restoreFocusFromElement = function(element)
1005 {
1006     if (element && element.contains(this.currentFocusElement))
1007         this.previousFocusElement.focus();
1008 };
1009
1010 WI.toggleNavigationSidebar = function(event)
1011 {
1012     if (!this.navigationSidebar.collapsed || !this.navigationSidebar.sidebarPanels.length) {
1013         this.navigationSidebar.collapsed = true;
1014         return;
1015     }
1016
1017     if (!this.navigationSidebar.selectedSidebarPanel)
1018         this.navigationSidebar.selectedSidebarPanel = this.navigationSidebar.sidebarPanels[0];
1019     this.navigationSidebar.collapsed = false;
1020 };
1021
1022 WI.toggleDetailsSidebar = function(event)
1023 {
1024     if (!this.detailsSidebar.collapsed || !this.detailsSidebar.sidebarPanels.length) {
1025         this.detailsSidebar.collapsed = true;
1026         return;
1027     }
1028
1029     if (!this.detailsSidebar.selectedSidebarPanel)
1030         this.detailsSidebar.selectedSidebarPanel = this.detailsSidebar.sidebarPanels[0];
1031     this.detailsSidebar.collapsed = false;
1032 };
1033
1034 WI.getMaximumSidebarWidth = function(sidebar)
1035 {
1036     console.assert(sidebar instanceof WI.Sidebar);
1037
1038     const minimumContentBrowserWidth = 100;
1039
1040     let minimumWidth = window.innerWidth - minimumContentBrowserWidth;
1041     let tabContentView = this.tabBrowser.selectedTabContentView;
1042     console.assert(tabContentView);
1043     if (!tabContentView)
1044         return minimumWidth;
1045
1046     let otherSidebar = null;
1047     if (sidebar === this.navigationSidebar)
1048         otherSidebar = tabContentView.detailsSidebarPanels.length ? this.detailsSidebar : null;
1049     else
1050         otherSidebar = tabContentView.navigationSidebarPanel ? this.navigationSidebar : null;
1051
1052     if (otherSidebar)
1053         minimumWidth -= otherSidebar.width;
1054
1055     return minimumWidth;
1056 };
1057
1058 WI.tabContentViewClassForRepresentedObject = function(representedObject)
1059 {
1060     if (representedObject instanceof WI.DOMTree)
1061         return WI.ElementsTabContentView;
1062
1063     if (representedObject instanceof WI.TimelineRecording)
1064         return WI.TimelineTabContentView;
1065
1066     // We only support one console tab right now. So this isn't an instanceof check.
1067     if (representedObject === this._consoleRepresentedObject)
1068         return WI.ConsoleTabContentView;
1069
1070     if (WI.debuggerManager.paused) {
1071         if (representedObject instanceof WI.Script)
1072             return WI.DebuggerTabContentView;
1073
1074         if (representedObject instanceof WI.Resource && (representedObject.type === WI.Resource.Type.Document || representedObject.type === WI.Resource.Type.Script))
1075             return WI.DebuggerTabContentView;
1076     }
1077
1078     if (representedObject instanceof WI.Frame
1079         || representedObject instanceof WI.FrameCollection
1080         || representedObject instanceof WI.Resource
1081         || representedObject instanceof WI.ResourceCollection
1082         || representedObject instanceof WI.Script
1083         || representedObject instanceof WI.ScriptCollection
1084         || representedObject instanceof WI.CSSStyleSheet
1085         || representedObject instanceof WI.CSSStyleSheetCollection)
1086         return WI.ResourcesTabContentView;
1087
1088     if (representedObject instanceof WI.DOMStorageObject || representedObject instanceof WI.CookieStorageObject ||
1089         representedObject instanceof WI.DatabaseTableObject || representedObject instanceof WI.DatabaseObject ||
1090         representedObject instanceof WI.ApplicationCacheFrame || representedObject instanceof WI.IndexedDatabaseObjectStore ||
1091         representedObject instanceof WI.IndexedDatabase || representedObject instanceof WI.IndexedDatabaseObjectStoreIndex)
1092         return WI.StorageTabContentView;
1093
1094     if (representedObject instanceof WI.AuditTestCase || representedObject instanceof WI.AuditTestGroup
1095         || representedObject instanceof WI.AuditTestCaseResult || representedObject instanceof WI.AuditTestGroupResult)
1096         return WI.AuditTabContentView;
1097
1098     if (representedObject instanceof WI.CanvasCollection)
1099         return WI.CanvasTabContentView;
1100
1101     if (representedObject instanceof WI.Recording)
1102         return WI.CanvasTabContentView;
1103
1104     return null;
1105 };
1106
1107 WI.tabContentViewForRepresentedObject = function(representedObject, options = {})
1108 {
1109     let tabContentView = this.tabBrowser.bestTabContentViewForRepresentedObject(representedObject, options);
1110     if (tabContentView)
1111         return tabContentView;
1112
1113     var tabContentViewClass = this.tabContentViewClassForRepresentedObject(representedObject);
1114     if (!tabContentViewClass) {
1115         console.error("Unknown representedObject, couldn't create TabContentView.", representedObject);
1116         return null;
1117     }
1118
1119     tabContentView = new tabContentViewClass;
1120
1121     this.tabBrowser.addTabForContentView(tabContentView);
1122
1123     return tabContentView;
1124 };
1125
1126 WI.showRepresentedObject = function(representedObject, cookie, options = {})
1127 {
1128     let tabContentView = this.tabContentViewForRepresentedObject(representedObject, options);
1129     console.assert(tabContentView);
1130     if (!tabContentView)
1131         return;
1132
1133     this.tabBrowser.showTabForContentView(tabContentView, options);
1134     tabContentView.showRepresentedObject(representedObject, cookie);
1135 };
1136
1137 WI.showMainFrameDOMTree = function(nodeToSelect, options = {})
1138 {
1139     console.assert(WI.networkManager.mainFrame);
1140     if (!WI.networkManager.mainFrame)
1141         return;
1142     this.showRepresentedObject(WI.networkManager.mainFrame.domTree, {nodeToSelect}, options);
1143 };
1144
1145 WI.showSourceCodeForFrame = function(frameIdentifier, options = {})
1146 {
1147     var frame = WI.networkManager.frameForIdentifier(frameIdentifier);
1148     if (!frame) {
1149         this._frameIdentifierToShowSourceCodeWhenAvailable = frameIdentifier;
1150         return;
1151     }
1152
1153     this._frameIdentifierToShowSourceCodeWhenAvailable = undefined;
1154
1155     this.showRepresentedObject(frame, null, options);
1156 };
1157
1158 WI.showSourceCode = function(sourceCode, options = {})
1159 {
1160     const positionToReveal = options.positionToReveal;
1161
1162     console.assert(!positionToReveal || positionToReveal instanceof WI.SourceCodePosition, positionToReveal);
1163     var representedObject = sourceCode;
1164
1165     if (representedObject instanceof WI.Script) {
1166         // A script represented by a resource should always show the resource.
1167         representedObject = representedObject.resource || representedObject;
1168     }
1169
1170     var cookie = positionToReveal ? {lineNumber: positionToReveal.lineNumber, columnNumber: positionToReveal.columnNumber} : {};
1171     this.showRepresentedObject(representedObject, cookie, options);
1172 };
1173
1174 WI.showSourceCodeLocation = function(sourceCodeLocation, options = {})
1175 {
1176     this.showSourceCode(sourceCodeLocation.displaySourceCode, {
1177         ...options,
1178         positionToReveal: sourceCodeLocation.displayPosition(),
1179     });
1180 };
1181
1182 WI.showOriginalUnformattedSourceCodeLocation = function(sourceCodeLocation, options = {})
1183 {
1184     this.showSourceCode(sourceCodeLocation.sourceCode, {
1185         ...options,
1186         positionToReveal: sourceCodeLocation.position(),
1187         forceUnformatted: true,
1188     });
1189 };
1190
1191 WI.showOriginalOrFormattedSourceCodeLocation = function(sourceCodeLocation, options = {})
1192 {
1193     this.showSourceCode(sourceCodeLocation.sourceCode, {
1194         ...options,
1195         positionToReveal: sourceCodeLocation.formattedPosition(),
1196     });
1197 };
1198
1199 WI.showOriginalOrFormattedSourceCodeTextRange = function(sourceCodeTextRange, options = {})
1200 {
1201     var textRangeToSelect = sourceCodeTextRange.formattedTextRange;
1202     this.showSourceCode(sourceCodeTextRange.sourceCode, {
1203         ...options,
1204         positionToReveal: textRangeToSelect.startPosition(),
1205         textRangeToSelect,
1206     });
1207 };
1208
1209 WI.showResourceRequest = function(resource, options = {})
1210 {
1211     this.showRepresentedObject(resource, {[WI.ResourceClusterContentView.ContentViewIdentifierCookieKey]: WI.ResourceClusterContentView.RequestIdentifier}, options);
1212 };
1213
1214 WI.debuggerToggleBreakpoints = function(event)
1215 {
1216     WI.debuggerManager.breakpointsEnabled = !WI.debuggerManager.breakpointsEnabled;
1217 };
1218
1219 WI.debuggerPauseResumeToggle = function(event)
1220 {
1221     if (WI.debuggerManager.paused)
1222         WI.debuggerManager.resume();
1223     else
1224         WI.debuggerManager.pause();
1225 };
1226
1227 WI.debuggerStepOver = function(event)
1228 {
1229     WI.debuggerManager.stepOver();
1230 };
1231
1232 WI.debuggerStepInto = function(event)
1233 {
1234     WI.debuggerManager.stepInto();
1235 };
1236
1237 WI.debuggerStepOut = function(event)
1238 {
1239     WI.debuggerManager.stepOut();
1240 };
1241
1242 WI._searchTextDidChange = function(event)
1243 {
1244     var tabContentView = this.tabBrowser.bestTabContentViewForClass(WI.SearchTabContentView);
1245     if (!tabContentView)
1246         tabContentView = new WI.SearchTabContentView;
1247
1248     var searchQuery = this._searchToolbarItem.text;
1249     this._searchToolbarItem.text = "";
1250
1251     this.tabBrowser.showTabForContentView(tabContentView);
1252
1253     tabContentView.performSearch(searchQuery);
1254 };
1255
1256 WI._focusSearchField = function(event)
1257 {
1258     if (WI.settings.experimentalEnableNewTabBar.value)
1259         this.tabBrowser.showTabForContentView(this._searchTabContentView);
1260
1261     if (this.tabBrowser.selectedTabContentView instanceof WI.SearchTabContentView) {
1262         this.tabBrowser.selectedTabContentView.focusSearchField();
1263         return;
1264     }
1265
1266     if (this._searchToolbarItem)
1267         this._searchToolbarItem.focus();
1268 };
1269
1270 WI._focusChanged = function(event)
1271 {
1272     // Make a caret selection inside the focused element if there isn't a range selection and there isn't already
1273     // a caret selection inside. This is needed (at least) to remove caret from console when focus is moved.
1274     // The selection change should not apply to text fields and text areas either.
1275
1276     if (WI.isEventTargetAnEditableField(event)) {
1277         // Still update the currentFocusElement if inside of a CodeMirror editor or an input element.
1278         let newFocusElement = null;
1279         if (event.target instanceof HTMLInputElement || event.target instanceof HTMLTextAreaElement)
1280             newFocusElement = event.target;
1281         else {
1282             let codeMirror = WI.enclosingCodeMirror(event.target);
1283             if (codeMirror) {
1284                 let codeMirrorElement = codeMirror.getWrapperElement();
1285                 if (codeMirrorElement && codeMirrorElement !== this.currentFocusElement)
1286                     newFocusElement = codeMirrorElement;
1287             }
1288         }
1289
1290         if (newFocusElement) {
1291             this.previousFocusElement = this.currentFocusElement;
1292             this.currentFocusElement = newFocusElement;
1293         }
1294
1295         // Due to the change in WI.isEventTargetAnEditableField (r196271), this return
1296         // will also get run when WI.startEditing is called on an element. We do not want
1297         // to return early in this case, as WI.EditingConfig handles its own editing
1298         // completion, so only return early if the focus change target is not from WI.startEditing.
1299         if (!WI.isBeingEdited(event.target))
1300             return;
1301     }
1302
1303     var selection = window.getSelection();
1304     if (!selection.isCollapsed)
1305         return;
1306
1307     var element = event.target;
1308
1309     if (element !== this.currentFocusElement) {
1310         this.previousFocusElement = this.currentFocusElement;
1311         this.currentFocusElement = element;
1312     }
1313
1314     if (element.isInsertionCaretInside())
1315         return;
1316
1317     var selectionRange = element.ownerDocument.createRange();
1318     selectionRange.setStart(element, 0);
1319     selectionRange.setEnd(element, 0);
1320
1321     selection.removeAllRanges();
1322     selection.addRange(selectionRange);
1323 };
1324
1325 WI._mouseWasClicked = function(event)
1326 {
1327     this.handlePossibleLinkClick(event);
1328 };
1329
1330 WI._dragOver = function(event)
1331 {
1332     // Do nothing if another event listener handled the event already.
1333     if (event.defaultPrevented)
1334         return;
1335
1336     // Allow dropping into editable areas.
1337     if (WI.isEventTargetAnEditableField(event))
1338         return;
1339
1340     // Prevent the drop from being accepted.
1341     event.dataTransfer.dropEffect = "none";
1342     event.preventDefault();
1343 };
1344
1345 WI._debuggerDidPause = function(event)
1346 {
1347     this.showDebuggerTab({showScopeChainSidebar: WI.settings.showScopeChainOnPause.value});
1348
1349     this._dashboardContainer.showDashboardViewForRepresentedObject(this._dashboards.debugger);
1350
1351     InspectorFrontendHost.bringToFront();
1352 };
1353
1354 WI._debuggerDidResume = function(event)
1355 {
1356     this._dashboardContainer.closeDashboardViewForRepresentedObject(this._dashboards.debugger);
1357 };
1358
1359 WI._frameWasAdded = function(event)
1360 {
1361     if (!this._frameIdentifierToShowSourceCodeWhenAvailable)
1362         return;
1363
1364     var frame = event.data.frame;
1365     if (frame.id !== this._frameIdentifierToShowSourceCodeWhenAvailable)
1366         return;
1367
1368     function delayedWork()
1369     {
1370         const options = {
1371             ignoreNetworkTab: true,
1372             ignoreSearchTab: true,
1373         };
1374         this.showSourceCodeForFrame(frame.id, options);
1375     }
1376
1377     // Delay showing the frame since FrameWasAdded is called before MainFrameChanged.
1378     // Calling showSourceCodeForFrame before MainFrameChanged will show the frame then close it.
1379     setTimeout(delayedWork.bind(this));
1380 };
1381
1382 WI._mainFrameDidChange = function(event)
1383 {
1384     this._updateDownloadToolbarButton();
1385
1386     this.updateWindowTitle();
1387 };
1388
1389 WI._mainResourceDidChange = function(event)
1390 {
1391     if (!event.target.isMainFrame())
1392         return;
1393
1394     // Run cookie restoration after we are sure all of the Tabs and NavigationSidebarPanels
1395     // have updated with respect to the main resource change.
1396     setTimeout(this._restoreCookieForOpenTabs.bind(this, WI.StateRestorationType.Navigation));
1397
1398     this._updateDownloadToolbarButton();
1399
1400     this.updateWindowTitle();
1401 };
1402
1403 WI._provisionalLoadStarted = function(event)
1404 {
1405     if (!event.target.isMainFrame())
1406         return;
1407
1408     this._saveCookieForOpenTabs();
1409 };
1410
1411 WI._restoreCookieForOpenTabs = function(restorationType)
1412 {
1413     for (var tabBarItem of this.tabBar.tabBarItems) {
1414         var tabContentView = tabBarItem.representedObject;
1415         if (!(tabContentView instanceof WI.TabContentView))
1416             continue;
1417         tabContentView.restoreStateFromCookie(restorationType);
1418     }
1419 };
1420
1421 WI._saveCookieForOpenTabs = function()
1422 {
1423     for (var tabBarItem of this.tabBar.tabBarItems) {
1424         var tabContentView = tabBarItem.representedObject;
1425         if (!(tabContentView instanceof WI.TabContentView))
1426             continue;
1427         tabContentView.saveStateToCookie();
1428     }
1429 };
1430
1431 WI._windowFocused = function(event)
1432 {
1433     if (event.target.document.nodeType !== Node.DOCUMENT_NODE)
1434         return;
1435
1436     // FIXME: We should use the :window-inactive pseudo class once https://webkit.org/b/38927 is fixed.
1437     document.body.classList.remove(this.docked ? "window-docked-inactive" : "window-inactive");
1438 };
1439
1440 WI._windowBlurred = function(event)
1441 {
1442     if (event.target.document.nodeType !== Node.DOCUMENT_NODE)
1443         return;
1444
1445     // FIXME: We should use the :window-inactive pseudo class once https://webkit.org/b/38927 is fixed.
1446     document.body.classList.add(this.docked ? "window-docked-inactive" : "window-inactive");
1447 };
1448
1449 WI._windowResized = function(event)
1450 {
1451     this.toolbar.updateLayout(WI.View.LayoutReason.Resize);
1452     this.tabBar.updateLayout(WI.View.LayoutReason.Resize);
1453     this._tabBrowserSizeDidChange();
1454 };
1455
1456 WI._updateModifierKeys = function(event)
1457 {
1458     let metaKeyDidChange = this.modifierKeys.metaKey !== event.metaKey;
1459     let didChange = this.modifierKeys.altKey !== event.altKey || metaKeyDidChange || this.modifierKeys.shiftKey !== event.shiftKey;
1460
1461     this.modifierKeys = {altKey: event.altKey, metaKey: event.metaKey, shiftKey: event.shiftKey};
1462
1463     if (metaKeyDidChange)
1464         document.body.classList.toggle("meta-key-pressed", this.modifierKeys.metaKey);
1465
1466     if (didChange)
1467         this.notifications.dispatchEventToListeners(WI.Notification.GlobalModifierKeysDidChange, event);
1468 };
1469
1470 WI._windowKeyDown = function(event)
1471 {
1472     this._updateModifierKeys(event);
1473 };
1474
1475 WI._windowKeyUp = function(event)
1476 {
1477     this._updateModifierKeys(event);
1478 };
1479
1480 WI._mouseDown = function(event)
1481 {
1482     if (this.toolbar.element.contains(event.target))
1483         this._toolbarMouseDown(event);
1484 };
1485
1486 WI._mouseMoved = function(event)
1487 {
1488     this._updateModifierKeys(event);
1489     this.mouseCoords = {
1490         x: event.pageX,
1491         y: event.pageY
1492     };
1493 };
1494
1495 WI._pageHidden = function(event)
1496 {
1497     this._saveCookieForOpenTabs();
1498 };
1499
1500 WI._contextMenuRequested = function(event)
1501 {
1502     let proposedContextMenu;
1503
1504     // This is setting is only defined in engineering builds.
1505     if (WI.isDebugUIEnabled()) {
1506         proposedContextMenu = WI.ContextMenu.createFromEvent(event);
1507         proposedContextMenu.appendSeparator();
1508         proposedContextMenu.appendItem(WI.unlocalizedString("Reload Web Inspector"), () => {
1509             window.location.reload();
1510         });
1511
1512         let protocolSubMenu = proposedContextMenu.appendSubMenuItem(WI.unlocalizedString("Protocol Debugging"), null, false);
1513         let isCapturingTraffic = InspectorBackend.activeTracer instanceof WI.CapturingProtocolTracer;
1514
1515         protocolSubMenu.appendCheckboxItem(WI.unlocalizedString("Capture Trace"), () => {
1516             if (isCapturingTraffic)
1517                 InspectorBackend.activeTracer = null;
1518             else
1519                 InspectorBackend.activeTracer = new WI.CapturingProtocolTracer;
1520         }, isCapturingTraffic);
1521
1522         protocolSubMenu.appendSeparator();
1523
1524         protocolSubMenu.appendItem(WI.unlocalizedString("Export Trace\u2026"), () => {
1525             const forceSaveAs = true;
1526             WI.saveDataToFile(InspectorBackend.activeTracer.trace.saveData, forceSaveAs);
1527         }, !isCapturingTraffic);
1528     } else {
1529         const onlyExisting = true;
1530         proposedContextMenu = WI.ContextMenu.createFromEvent(event, onlyExisting);
1531     }
1532
1533     if (proposedContextMenu)
1534         proposedContextMenu.show();
1535 };
1536
1537 WI.isDebugUIEnabled = function()
1538 {
1539     return WI.showDebugUISetting && WI.showDebugUISetting.value;
1540 };
1541
1542 WI._undock = function(event)
1543 {
1544     InspectorFrontendHost.requestSetDockSide(WI.DockConfiguration.Undocked);
1545 };
1546
1547 WI._dockBottom = function(event)
1548 {
1549     InspectorFrontendHost.requestSetDockSide(WI.DockConfiguration.Bottom);
1550 };
1551
1552 WI._dockRight = function(event)
1553 {
1554     InspectorFrontendHost.requestSetDockSide(WI.DockConfiguration.Right);
1555 };
1556
1557 WI._dockLeft = function(event)
1558 {
1559     InspectorFrontendHost.requestSetDockSide(WI.DockConfiguration.Left);
1560 };
1561
1562 WI._togglePreviousDockConfiguration = function(event)
1563 {
1564     InspectorFrontendHost.requestSetDockSide(this._previousDockConfiguration);
1565 };
1566
1567 WI._updateDockNavigationItems = function()
1568 {
1569     if (this._dockingAvailable || this.docked) {
1570         this._closeToolbarButton.hidden = !this.docked;
1571         this._undockToolbarButton.hidden = this._dockConfiguration === WI.DockConfiguration.Undocked;
1572         this._dockBottomToolbarButton.hidden = this._dockConfiguration === WI.DockConfiguration.Bottom;
1573         this._dockToSideToolbarButton.hidden = this._dockConfiguration === WI.DockConfiguration.Right || this._dockConfiguration === WI.DockConfiguration.Left;
1574     } else {
1575         this._closeToolbarButton.hidden = true;
1576         this._undockToolbarButton.hidden = true;
1577         this._dockBottomToolbarButton.hidden = true;
1578         this._dockToSideToolbarButton.hidden = true;
1579     }
1580 };
1581
1582 WI._tabBrowserSizeDidChange = function()
1583 {
1584     this.tabBrowser.updateLayout(WI.View.LayoutReason.Resize);
1585     this.consoleDrawer.updateLayout(WI.View.LayoutReason.Resize);
1586     this.quickConsole.updateLayout(WI.View.LayoutReason.Resize);
1587 };
1588
1589 WI._consoleDrawerCollapsedStateDidChange = function(event)
1590 {
1591     this._showingSplitConsoleSetting.value = WI.isShowingSplitConsole();
1592
1593     WI._consoleDrawerDidResize();
1594 };
1595
1596 WI._consoleDrawerDidResize = function(event)
1597 {
1598     this.tabBrowser.updateLayout(WI.View.LayoutReason.Resize);
1599 };
1600
1601 WI._sidebarWidthDidChange = function(event)
1602 {
1603     this._tabBrowserSizeDidChange();
1604 };
1605
1606 WI._setupViewHierarchy = function()
1607 {
1608     let rootView = WI.View.rootView();
1609     rootView.addSubview(this.toolbar);
1610     rootView.addSubview(this.tabBar);
1611     rootView.addSubview(this.navigationSidebar);
1612     rootView.addSubview(this.tabBrowser);
1613     rootView.addSubview(this.consoleDrawer);
1614     rootView.addSubview(this.quickConsole);
1615     rootView.addSubview(this.detailsSidebar);
1616 };
1617
1618 WI._tabBrowserSelectedTabContentViewDidChange = function(event)
1619 {
1620     if (this.tabBar.selectedTabBarItem && this.tabBar.selectedTabBarItem.representedObject.constructor.shouldSaveTab())
1621         this._selectedTabIndexSetting.value = this.tabBar.tabBarItems.indexOf(this.tabBar.selectedTabBarItem);
1622
1623     if (this.doesCurrentTabSupportSplitContentBrowser()) {
1624         if (this._shouldRevealSpitConsoleIfSupported) {
1625             this._shouldRevealSpitConsoleIfSupported = false;
1626             this.showSplitConsole();
1627         }
1628         return;
1629     }
1630
1631     this._shouldRevealSpitConsoleIfSupported = this.isShowingSplitConsole();
1632     this.hideSplitConsole();
1633 };
1634
1635 WI._toolbarMouseDown = function(event)
1636 {
1637     if (event.ctrlKey)
1638         return;
1639
1640     if (this._dockConfiguration === WI.DockConfiguration.Right || this._dockConfiguration === WI.DockConfiguration.Left)
1641         return;
1642
1643     if (this.docked)
1644         this._dockedResizerMouseDown(event);
1645     else
1646         this._moveWindowMouseDown(event);
1647 };
1648
1649 WI._dockedResizerMouseDown = function(event)
1650 {
1651     if (event.button !== 0 || event.ctrlKey)
1652         return;
1653
1654     if (!this.docked)
1655         return;
1656
1657     // Only start dragging if the target is one of the elements that we expect.
1658     if (event.target.id !== "docked-resizer" && !event.target.classList.contains("toolbar") &&
1659         !event.target.classList.contains("flexible-space") && !event.target.classList.contains("item-section"))
1660         return;
1661
1662     event[WI.Popover.EventPreventDismissSymbol] = true;
1663
1664     let windowProperty = this._dockConfiguration === WI.DockConfiguration.Bottom ? "innerHeight" : "innerWidth";
1665     let eventScreenProperty = this._dockConfiguration === WI.DockConfiguration.Bottom ? "screenY" : "screenX";
1666     let eventClientProperty = this._dockConfiguration === WI.DockConfiguration.Bottom ? "clientY" : "clientX";
1667
1668     var resizerElement = event.target;
1669     var firstClientPosition = event[eventClientProperty];
1670     var lastScreenPosition = event[eventScreenProperty];
1671
1672     function dockedResizerDrag(event)
1673     {
1674         if (event.button !== 0)
1675             return;
1676
1677         var position = event[eventScreenProperty];
1678         var delta = position - lastScreenPosition;
1679         var clientPosition = event[eventClientProperty];
1680
1681         lastScreenPosition = position;
1682
1683         if (this._dockConfiguration === WI.DockConfiguration.Left) {
1684             // If the mouse is travelling rightward but is positioned left of the resizer, ignore the event.
1685             if (delta > 0 && clientPosition < firstClientPosition)
1686                 return;
1687
1688             // If the mouse is travelling leftward but is positioned to the right of the resizer, ignore the event.
1689             if (delta < 0 && clientPosition > window[windowProperty])
1690                 return;
1691
1692             // We later subtract the delta from the current position, but since the inspected view and inspector view
1693             // are flipped when docked to left, we want dragging to have the opposite effect from docked to right.
1694             delta *= -1;
1695         } else {
1696             // If the mouse is travelling downward/rightward but is positioned above/left of the resizer, ignore the event.
1697             if (delta > 0 && clientPosition < firstClientPosition)
1698                 return;
1699
1700             // If the mouse is travelling upward/leftward but is positioned below/right of the resizer, ignore the event.
1701             if (delta < 0 && clientPosition > firstClientPosition)
1702                 return;
1703         }
1704
1705         let dimension = Math.max(0, window[windowProperty] - delta);
1706         // If zoomed in/out, there be greater/fewer document pixels shown, but the inspector's
1707         // width or height should be the same in device pixels regardless of the document zoom.
1708         dimension *= this.getZoomFactor();
1709
1710         if (this._dockConfiguration === WI.DockConfiguration.Bottom)
1711             InspectorFrontendHost.setAttachedWindowHeight(dimension);
1712         else
1713             InspectorFrontendHost.setAttachedWindowWidth(dimension);
1714     }
1715
1716     function dockedResizerDragEnd(event)
1717     {
1718         if (event.button !== 0)
1719             return;
1720
1721         WI.elementDragEnd(event);
1722     }
1723
1724     WI.elementDragStart(resizerElement, dockedResizerDrag.bind(this), dockedResizerDragEnd.bind(this), event, this._dockConfiguration === WI.DockConfiguration.Bottom ? "row-resize" : "col-resize");
1725 };
1726
1727 WI._moveWindowMouseDown = function(event)
1728 {
1729     console.assert(!this.docked);
1730
1731     if (event.button !== 0 || event.ctrlKey)
1732         return;
1733
1734     // Only start dragging if the target is one of the elements that we expect.
1735     if (!event.target.classList.contains("toolbar") && !event.target.classList.contains("flexible-space") &&
1736         !event.target.classList.contains("item-section"))
1737         return;
1738
1739     event[WI.Popover.EventPreventDismissSymbol] = true;
1740
1741     if (WI.Platform.name === "mac") {
1742         InspectorFrontendHost.startWindowDrag();
1743         event.preventDefault();
1744         return;
1745     }
1746
1747     var lastScreenX = event.screenX;
1748     var lastScreenY = event.screenY;
1749
1750     function toolbarDrag(event)
1751     {
1752         if (event.button !== 0)
1753             return;
1754
1755         var x = event.screenX - lastScreenX;
1756         var y = event.screenY - lastScreenY;
1757
1758         InspectorFrontendHost.moveWindowBy(x, y);
1759
1760         lastScreenX = event.screenX;
1761         lastScreenY = event.screenY;
1762     }
1763
1764     function toolbarDragEnd(event)
1765     {
1766         if (event.button !== 0)
1767             return;
1768
1769         WI.elementDragEnd(event);
1770     }
1771
1772     WI.elementDragStart(event.target, toolbarDrag, toolbarDragEnd, event, "default");
1773 };
1774
1775 WI._domStorageWasInspected = function(event)
1776 {
1777     this.showStorageTab();
1778     this.showRepresentedObject(event.data.domStorage, null, {ignoreSearchTab: true});
1779 };
1780
1781 WI._databaseWasInspected = function(event)
1782 {
1783     this.showStorageTab();
1784     this.showRepresentedObject(event.data.database, null, {ignoreSearchTab: true});
1785 };
1786
1787 WI._domNodeWasInspected = function(event)
1788 {
1789     this.domManager.highlightDOMNodeForTwoSeconds(event.data.node.id);
1790
1791     InspectorFrontendHost.bringToFront();
1792
1793     this.showElementsTab();
1794     this.showMainFrameDOMTree(event.data.node, {ignoreSearchTab: true});
1795 };
1796
1797 WI._inspectModeStateChanged = function(event)
1798 {
1799     this._inspectModeToolbarButton.activated = this.domManager.inspectModeEnabled;
1800 };
1801
1802 WI._toggleInspectMode = function(event)
1803 {
1804     this.domManager.inspectModeEnabled = !this.domManager.inspectModeEnabled;
1805 };
1806
1807 WI._downloadWebArchive = function(event)
1808 {
1809     this.archiveMainFrame();
1810 };
1811
1812 WI._reloadPage = function(event)
1813 {
1814     if (!window.PageAgent)
1815         return;
1816
1817     PageAgent.reload();
1818     event.preventDefault();
1819 };
1820
1821 WI._reloadToolbarButtonClicked = function(event)
1822 {
1823     // Reload page from origin if the button is clicked while the shift key is pressed down.
1824     PageAgent.reload.invoke({ignoreCache: this.modifierKeys.shiftKey});
1825 };
1826
1827 WI._reloadPageFromOrigin = function(event)
1828 {
1829     if (!window.PageAgent)
1830         return;
1831
1832     PageAgent.reload.invoke({ignoreCache: true});
1833     event.preventDefault();
1834 };
1835
1836 WI._updateReloadToolbarButton = function()
1837 {
1838     if (!window.PageAgent) {
1839         this._reloadToolbarButton.hidden = true;
1840         return;
1841     }
1842
1843     this._reloadToolbarButton.hidden = false;
1844 };
1845
1846 WI._updateDownloadToolbarButton = function()
1847 {
1848     if (!window.PageAgent || this.sharedApp.debuggableType !== WI.DebuggableType.Web) {
1849         this._downloadToolbarButton.hidden = true;
1850         return;
1851     }
1852
1853     if (this._downloadingPage) {
1854         this._downloadToolbarButton.enabled = false;
1855         return;
1856     }
1857
1858     this._downloadToolbarButton.enabled = this.canArchiveMainFrame();
1859 };
1860
1861 WI._updateInspectModeToolbarButton = function()
1862 {
1863     if (!window.DOMAgent || !DOMAgent.setInspectModeEnabled) {
1864         this._inspectModeToolbarButton.hidden = true;
1865         return;
1866     }
1867
1868     this._inspectModeToolbarButton.hidden = false;
1869 };
1870
1871 WI._toggleInspectMode = function(event)
1872 {
1873     this.domManager.inspectModeEnabled = !this.domManager.inspectModeEnabled;
1874 };
1875
1876 WI._showConsoleTab = function(event)
1877 {
1878     this.showConsoleTab();
1879 };
1880
1881 WI._focusConsolePrompt = function(event)
1882 {
1883     this.quickConsole.prompt.focus();
1884 };
1885
1886 WI._focusedContentBrowser = function()
1887 {
1888     if (this.currentFocusElement) {
1889         let contentBrowserElement = this.currentFocusElement.enclosingNodeOrSelfWithClass("content-browser");
1890         if (contentBrowserElement && contentBrowserElement.__view && contentBrowserElement.__view instanceof WI.ContentBrowser)
1891             return contentBrowserElement.__view;
1892     }
1893
1894     if (this.tabBrowser.element.contains(this.currentFocusElement) || document.activeElement === document.body) {
1895         let tabContentView = this.tabBrowser.selectedTabContentView;
1896         if (tabContentView.contentBrowser)
1897             return tabContentView.contentBrowser;
1898         return null;
1899     }
1900
1901     if (this.consoleDrawer.element.contains(this.currentFocusElement)
1902         || (WI.isShowingSplitConsole() && this.quickConsole.element.contains(this.currentFocusElement)))
1903         return this.consoleDrawer;
1904
1905     return null;
1906 };
1907
1908 WI._focusedContentView = function()
1909 {
1910     if (this.tabBrowser.element.contains(this.currentFocusElement) || document.activeElement === document.body) {
1911         var tabContentView = this.tabBrowser.selectedTabContentView;
1912         if (tabContentView.contentBrowser)
1913             return tabContentView.contentBrowser.currentContentView;
1914         return tabContentView;
1915     }
1916
1917     if (this.consoleDrawer.element.contains(this.currentFocusElement)
1918         || (WI.isShowingSplitConsole() && this.quickConsole.element.contains(this.currentFocusElement)))
1919         return this.consoleDrawer.currentContentView;
1920
1921     return null;
1922 };
1923
1924 WI._focusedOrVisibleContentBrowser = function()
1925 {
1926     let focusedContentBrowser = this._focusedContentBrowser();
1927     if (focusedContentBrowser)
1928         return focusedContentBrowser;
1929
1930     var tabContentView = this.tabBrowser.selectedTabContentView;
1931     if (tabContentView.contentBrowser)
1932         return tabContentView.contentBrowser;
1933
1934     return null;
1935 };
1936
1937 WI.focusedOrVisibleContentView = function()
1938 {
1939     let focusedContentView = this._focusedContentView();
1940     if (focusedContentView)
1941         return focusedContentView;
1942
1943     var tabContentView = this.tabBrowser.selectedTabContentView;
1944     if (tabContentView.contentBrowser)
1945         return tabContentView.contentBrowser.currentContentView;
1946     return tabContentView;
1947 };
1948
1949 WI._beforecopy = function(event)
1950 {
1951     var selection = window.getSelection();
1952
1953     // If there is no selection, see if the focused element or focused ContentView can handle the copy event.
1954     if (selection.isCollapsed && !WI.isEventTargetAnEditableField(event)) {
1955         var focusedCopyHandler = this.currentFocusElement && this.currentFocusElement.copyHandler;
1956         if (focusedCopyHandler && typeof focusedCopyHandler.handleBeforeCopyEvent === "function") {
1957             focusedCopyHandler.handleBeforeCopyEvent(event);
1958             if (event.defaultPrevented)
1959                 return;
1960         }
1961
1962         var focusedContentView = this._focusedContentView();
1963         if (focusedContentView && typeof focusedContentView.handleCopyEvent === "function") {
1964             event.preventDefault();
1965             return;
1966         }
1967
1968         return;
1969     }
1970
1971     if (selection.isCollapsed)
1972         return;
1973
1974     // Say we can handle it (by preventing default) to remove word break characters.
1975     event.preventDefault();
1976 };
1977
1978 WI._find = function(event)
1979 {
1980     let contentBrowser = this._focusedOrVisibleContentBrowser();
1981     if (!contentBrowser)
1982         return;
1983
1984     contentBrowser.showFindBanner();
1985 };
1986
1987 WI._save = function(event)
1988 {
1989     var contentView = this.focusedOrVisibleContentView();
1990     if (!contentView || !contentView.supportsSave)
1991         return;
1992
1993     WI.saveDataToFile(contentView.saveData);
1994 };
1995
1996 WI._saveAs = function(event)
1997 {
1998     var contentView = this.focusedOrVisibleContentView();
1999     if (!contentView || !contentView.supportsSave)
2000         return;
2001
2002     WI.saveDataToFile(contentView.saveData, true);
2003 };
2004
2005 WI._clear = function(event)
2006 {
2007     let contentView = this.focusedOrVisibleContentView();
2008     if (!contentView || typeof contentView.handleClearShortcut !== "function") {
2009         // If the current content view is unable to handle this event, clear the console to reset
2010         // the dashboard counters.
2011         this.consoleManager.requestClearMessages();
2012         return;
2013     }
2014
2015     contentView.handleClearShortcut(event);
2016 };
2017
2018 WI._populateFind = function(event)
2019 {
2020     let focusedContentView = this._focusedContentView();
2021     if (!focusedContentView)
2022         return;
2023
2024     if (focusedContentView.supportsCustomFindBanner) {
2025         focusedContentView.handlePopulateFindShortcut();
2026         return;
2027     }
2028
2029     let contentBrowser = this._focusedOrVisibleContentBrowser();
2030     if (!contentBrowser)
2031         return;
2032
2033     contentBrowser.handlePopulateFindShortcut();
2034 };
2035
2036 WI._findNext = function(event)
2037 {
2038     let focusedContentView = this._focusedContentView();
2039     if (!focusedContentView)
2040         return;
2041
2042     if (focusedContentView.supportsCustomFindBanner) {
2043         focusedContentView.handleFindNextShortcut();
2044         return;
2045     }
2046
2047     let contentBrowser = this._focusedOrVisibleContentBrowser();
2048     if (!contentBrowser)
2049         return;
2050
2051     contentBrowser.handleFindNextShortcut();
2052 };
2053
2054 WI._findPrevious = function(event)
2055 {
2056     let focusedContentView = this._focusedContentView();
2057     if (!focusedContentView)
2058         return;
2059
2060     if (focusedContentView.supportsCustomFindBanner) {
2061         focusedContentView.handleFindPreviousShortcut();
2062         return;
2063     }
2064
2065     let contentBrowser = this._focusedOrVisibleContentBrowser();
2066     if (!contentBrowser)
2067         return;
2068
2069     contentBrowser.handleFindPreviousShortcut();
2070 };
2071
2072 WI._copy = function(event)
2073 {
2074     var selection = window.getSelection();
2075
2076     // If there is no selection, pass the copy event on to the focused element or focused ContentView.
2077     if (selection.isCollapsed && !WI.isEventTargetAnEditableField(event)) {
2078         var focusedCopyHandler = this.currentFocusElement && this.currentFocusElement.copyHandler;
2079         if (focusedCopyHandler && typeof focusedCopyHandler.handleCopyEvent === "function") {
2080             focusedCopyHandler.handleCopyEvent(event);
2081             if (event.defaultPrevented)
2082                 return;
2083         }
2084
2085         var focusedContentView = this._focusedContentView();
2086         if (focusedContentView && typeof focusedContentView.handleCopyEvent === "function") {
2087             focusedContentView.handleCopyEvent(event);
2088             return;
2089         }
2090
2091         let tabContentView = this.tabBrowser.selectedTabContentView;
2092         if (tabContentView && typeof tabContentView.handleCopyEvent === "function") {
2093             tabContentView.handleCopyEvent(event);
2094             return;
2095         }
2096
2097         return;
2098     }
2099
2100     if (selection.isCollapsed)
2101         return;
2102
2103     // Remove word break characters from the selection before putting it on the pasteboard.
2104     var selectionString = selection.toString().removeWordBreakCharacters();
2105     event.clipboardData.setData("text/plain", selectionString);
2106     event.preventDefault();
2107 };
2108
2109 WI._increaseZoom = function(event)
2110 {
2111     const epsilon = 0.0001;
2112     const maximumZoom = 2.4;
2113     let currentZoom = this.getZoomFactor();
2114     if (currentZoom + epsilon >= maximumZoom) {
2115         InspectorFrontendHost.beep();
2116         return;
2117     }
2118
2119     this.setZoomFactor(Math.min(maximumZoom, currentZoom + 0.2));
2120 };
2121
2122 WI._decreaseZoom = function(event)
2123 {
2124     const epsilon = 0.0001;
2125     const minimumZoom = 0.6;
2126     let currentZoom = this.getZoomFactor();
2127     if (currentZoom - epsilon <= minimumZoom) {
2128         InspectorFrontendHost.beep();
2129         return;
2130     }
2131
2132     this.setZoomFactor(Math.max(minimumZoom, currentZoom - 0.2));
2133 };
2134
2135 WI._resetZoom = function(event)
2136 {
2137     this.setZoomFactor(1);
2138 };
2139
2140 WI.getZoomFactor = function()
2141 {
2142     return WI.settings.zoomFactor.value;
2143 };
2144
2145 WI.setZoomFactor = function(factor)
2146 {
2147     InspectorFrontendHost.setZoomFactor(factor);
2148     // Round-trip through the frontend host API in case the requested factor is not used.
2149     WI.settings.zoomFactor.value = InspectorFrontendHost.zoomFactor();
2150 };
2151
2152 WI.resolvedLayoutDirection = function()
2153 {
2154     let layoutDirection = WI.settings.layoutDirection.value;
2155     if (layoutDirection === WI.LayoutDirection.System)
2156         layoutDirection = InspectorFrontendHost.userInterfaceLayoutDirection();
2157
2158     return layoutDirection;
2159 };
2160
2161 WI.setLayoutDirection = function(value)
2162 {
2163     if (!Object.values(WI.LayoutDirection).includes(value))
2164         WI.reportInternalError("Unknown layout direction requested: " + value);
2165
2166     if (value === WI.settings.layoutDirection.value)
2167         return;
2168
2169     WI.settings.layoutDirection.value = value;
2170
2171     if (WI.resolvedLayoutDirection() === WI.LayoutDirection.RTL && this._dockConfiguration === WI.DockConfiguration.Right)
2172         this._dockLeft();
2173
2174     if (WI.resolvedLayoutDirection() === WI.LayoutDirection.LTR && this._dockConfiguration === WI.DockConfiguration.Left)
2175         this._dockRight();
2176
2177     window.location.reload();
2178 };
2179
2180 WI._showTabAtIndex = function(i, event)
2181 {
2182     if (i <= WI.tabBar.tabBarItems.length)
2183         WI.tabBar.selectedTabBarItem = i - 1;
2184 };
2185
2186 WI._showJavaScriptTypeInformationSettingChanged = function(event)
2187 {
2188     if (WI.settings.showJavaScriptTypeInformation.value) {
2189         for (let target of WI.targets)
2190             target.RuntimeAgent.enableTypeProfiler();
2191     } else {
2192         for (let target of WI.targets)
2193             target.RuntimeAgent.disableTypeProfiler();
2194     }
2195 };
2196
2197 WI._enableControlFlowProfilerSettingChanged = function(event)
2198 {
2199     if (WI.settings.enableControlFlowProfiler.value) {
2200         for (let target of WI.targets)
2201             target.RuntimeAgent.enableControlFlowProfiler();
2202     } else {
2203         for (let target of WI.targets)
2204             target.RuntimeAgent.disableControlFlowProfiler();
2205     }
2206 };
2207
2208 WI._resourceCachingDisabledSettingChanged = function(event)
2209 {
2210     NetworkAgent.setResourceCachingDisabled(WI.settings.resourceCachingDisabled.value);
2211 };
2212
2213 WI.elementDragStart = function(element, dividerDrag, elementDragEnd, event, cursor, eventTarget)
2214 {
2215     if (WI._elementDraggingEventListener || WI._elementEndDraggingEventListener)
2216         WI.elementDragEnd(event);
2217
2218     if (element) {
2219         // Install glass pane
2220         if (WI._elementDraggingGlassPane)
2221             WI._elementDraggingGlassPane.remove();
2222
2223         var glassPane = document.createElement("div");
2224         glassPane.style.cssText = "position:absolute;top:0;bottom:0;left:0;right:0;opacity:0;z-index:1";
2225         glassPane.id = "glass-pane-for-drag";
2226         element.ownerDocument.body.appendChild(glassPane);
2227         WI._elementDraggingGlassPane = glassPane;
2228     }
2229
2230     WI._elementDraggingEventListener = dividerDrag;
2231     WI._elementEndDraggingEventListener = elementDragEnd;
2232
2233     var targetDocument = event.target.ownerDocument;
2234
2235     WI._elementDraggingEventTarget = eventTarget || targetDocument;
2236     WI._elementDraggingEventTarget.addEventListener("mousemove", dividerDrag, true);
2237     WI._elementDraggingEventTarget.addEventListener("mouseup", elementDragEnd, true);
2238
2239     targetDocument.body.style.cursor = cursor;
2240
2241     event.preventDefault();
2242 };
2243
2244 WI.elementDragEnd = function(event)
2245 {
2246     WI._elementDraggingEventTarget.removeEventListener("mousemove", WI._elementDraggingEventListener, true);
2247     WI._elementDraggingEventTarget.removeEventListener("mouseup", WI._elementEndDraggingEventListener, true);
2248
2249     event.target.ownerDocument.body.style.removeProperty("cursor");
2250
2251     if (WI._elementDraggingGlassPane)
2252         WI._elementDraggingGlassPane.remove();
2253
2254     delete WI._elementDraggingGlassPane;
2255     delete WI._elementDraggingEventTarget;
2256     delete WI._elementDraggingEventListener;
2257     delete WI._elementEndDraggingEventListener;
2258
2259     event.preventDefault();
2260 };
2261
2262 WI.createMessageTextView = function(message, isError)
2263 {
2264     var messageElement = document.createElement("div");
2265     messageElement.className = "message-text-view";
2266     if (isError)
2267         messageElement.classList.add("error");
2268
2269     let textElement = messageElement.appendChild(document.createElement("div"));
2270     textElement.className = "message";
2271     textElement.textContent = message;
2272
2273     return messageElement;
2274 };
2275
2276 WI.createNavigationItemHelp = function(formatString, navigationItem)
2277 {
2278     console.assert(typeof formatString === "string");
2279     console.assert(navigationItem instanceof WI.NavigationItem);
2280
2281     function append(a, b) {
2282         a.append(b);
2283         return a;
2284     }
2285
2286     let containerElement = document.createElement("div");
2287     containerElement.className = "navigation-item-help";
2288     containerElement.__navigationItem = navigationItem;
2289
2290     let wrapperElement = document.createElement("div");
2291     wrapperElement.className = "navigation-bar";
2292     wrapperElement.appendChild(navigationItem.element);
2293
2294     String.format(formatString, [wrapperElement], String.standardFormatters, containerElement, append);
2295     return containerElement;
2296 };
2297
2298 WI.createGoToArrowButton = function()
2299 {
2300     var button = document.createElement("button");
2301     button.addEventListener("mousedown", (event) => { event.stopPropagation(); }, true);
2302     button.className = "go-to-arrow";
2303     button.tabIndex = -1;
2304     return button;
2305 };
2306
2307 WI.createSourceCodeLocationLink = function(sourceCodeLocation, options = {})
2308 {
2309     console.assert(sourceCodeLocation);
2310     if (!sourceCodeLocation)
2311         return null;
2312
2313     var linkElement = document.createElement("a");
2314     linkElement.className = "go-to-link";
2315     WI.linkifyElement(linkElement, sourceCodeLocation, options);
2316     sourceCodeLocation.populateLiveDisplayLocationTooltip(linkElement);
2317
2318     if (options.useGoToArrowButton)
2319         linkElement.appendChild(WI.createGoToArrowButton());
2320     else
2321         sourceCodeLocation.populateLiveDisplayLocationString(linkElement, "textContent", options.columnStyle, options.nameStyle, options.prefix);
2322
2323     if (options.dontFloat)
2324         linkElement.classList.add("dont-float");
2325
2326     return linkElement;
2327 };
2328
2329 WI.linkifyLocation = function(url, sourceCodePosition, options = {})
2330 {
2331     var sourceCode = WI.sourceCodeForURL(url);
2332
2333     if (!sourceCode) {
2334         var anchor = document.createElement("a");
2335         anchor.href = url;
2336         anchor.lineNumber = sourceCodePosition.lineNumber;
2337         if (options.className)
2338             anchor.className = options.className;
2339         anchor.append(WI.displayNameForURL(url) + ":" + sourceCodePosition.lineNumber);
2340         return anchor;
2341     }
2342
2343     let sourceCodeLocation = sourceCode.createSourceCodeLocation(sourceCodePosition.lineNumber, sourceCodePosition.columnNumber);
2344     let linkElement = WI.createSourceCodeLocationLink(sourceCodeLocation, {
2345         ...options,
2346         dontFloat: true,
2347     });
2348
2349     if (options.className)
2350         linkElement.classList.add(options.className);
2351
2352     return linkElement;
2353 };
2354
2355 WI.linkifyElement = function(linkElement, sourceCodeLocation, options = {}) {
2356     console.assert(sourceCodeLocation);
2357
2358     function showSourceCodeLocation(event)
2359     {
2360         event.stopPropagation();
2361         event.preventDefault();
2362
2363         if (event.metaKey)
2364             this.showOriginalUnformattedSourceCodeLocation(sourceCodeLocation, options);
2365         else
2366             this.showSourceCodeLocation(sourceCodeLocation, options);
2367     }
2368
2369     linkElement.addEventListener("click", showSourceCodeLocation.bind(this));
2370     linkElement.addEventListener("contextmenu", (event) => {
2371         let contextMenu = WI.ContextMenu.createFromEvent(event);
2372         WI.appendContextMenuItemsForSourceCode(contextMenu, sourceCodeLocation);
2373     });
2374 };
2375
2376 WI.sourceCodeForURL = function(url)
2377 {
2378     var sourceCode = WI.networkManager.resourceForURL(url);
2379     if (!sourceCode) {
2380         sourceCode = WI.debuggerManager.scriptsForURL(url, WI.assumingMainTarget())[0];
2381         if (sourceCode)
2382             sourceCode = sourceCode.resource || sourceCode;
2383     }
2384     return sourceCode || null;
2385 };
2386
2387 WI.linkifyURLAsNode = function(url, linkText, className)
2388 {
2389     let a = document.createElement("a");
2390     a.href = url;
2391     a.className = className || "";
2392     a.textContent = linkText || url;
2393     a.style.maxWidth = "100%";
2394     return a;
2395 };
2396
2397 WI.linkifyStringAsFragmentWithCustomLinkifier = function(string, linkifier)
2398 {
2399     var container = document.createDocumentFragment();
2400     var linkStringRegEx = /(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\/\/|www\.)[\w$\-_+*'=\|\/\\(){}[\]%@&#~,:;.!?]{2,}[\w$\-_+*=\|\/\\({%@&#~]/;
2401     var lineColumnRegEx = /:(\d+)(:(\d+))?$/;
2402
2403     while (string) {
2404         var linkString = linkStringRegEx.exec(string);
2405         if (!linkString)
2406             break;
2407
2408         linkString = linkString[0];
2409         var linkIndex = string.indexOf(linkString);
2410         var nonLink = string.substring(0, linkIndex);
2411         container.append(nonLink);
2412
2413         if (linkString.startsWith("data:") || linkString.startsWith("javascript:") || linkString.startsWith("mailto:")) {
2414             container.append(linkString);
2415             string = string.substring(linkIndex + linkString.length, string.length);
2416             continue;
2417         }
2418
2419         var title = linkString;
2420         var realURL = linkString.startsWith("www.") ? "http://" + linkString : linkString;
2421         var lineColumnMatch = lineColumnRegEx.exec(realURL);
2422         if (lineColumnMatch)
2423             realURL = realURL.substring(0, realURL.length - lineColumnMatch[0].length);
2424
2425         var lineNumber;
2426         if (lineColumnMatch)
2427             lineNumber = parseInt(lineColumnMatch[1]) - 1;
2428
2429         var linkNode = linkifier(title, realURL, lineNumber);
2430         container.appendChild(linkNode);
2431         string = string.substring(linkIndex + linkString.length, string.length);
2432     }
2433
2434     if (string)
2435         container.append(string);
2436
2437     return container;
2438 };
2439
2440 WI.linkifyStringAsFragment = function(string)
2441 {
2442     function linkifier(title, url, lineNumber)
2443     {
2444         var urlNode = WI.linkifyURLAsNode(url, title, undefined);
2445         if (lineNumber !== undefined)
2446             urlNode.lineNumber = lineNumber;
2447
2448         return urlNode;
2449     }
2450
2451     return WI.linkifyStringAsFragmentWithCustomLinkifier(string, linkifier);
2452 };
2453
2454 WI.createResourceLink = function(resource, className)
2455 {
2456     function handleClick(event)
2457     {
2458         event.stopPropagation();
2459         event.preventDefault();
2460
2461         WI.showRepresentedObject(resource);
2462     }
2463
2464     let linkNode = document.createElement("a");
2465     linkNode.classList.add("resource-link", className);
2466     linkNode.title = resource.url;
2467     linkNode.textContent = (resource.urlComponents.lastPathComponent || resource.url).insertWordBreakCharacters();
2468     linkNode.addEventListener("click", handleClick.bind(this));
2469     return linkNode;
2470 };
2471
2472 WI._undoKeyboardShortcut = function(event)
2473 {
2474     if (!this.isEditingAnyField() && !this.isEventTargetAnEditableField(event)) {
2475         this.undo();
2476         event.preventDefault();
2477     }
2478 };
2479
2480 WI._redoKeyboardShortcut = function(event)
2481 {
2482     if (!this.isEditingAnyField() && !this.isEventTargetAnEditableField(event)) {
2483         this.redo();
2484         event.preventDefault();
2485     }
2486 };
2487
2488 WI.undo = function()
2489 {
2490     DOMAgent.undo();
2491 };
2492
2493 WI.redo = function()
2494 {
2495     DOMAgent.redo();
2496 };
2497
2498 WI.highlightRangesWithStyleClass = function(element, resultRanges, styleClass, changes)
2499 {
2500     changes = changes || [];
2501     var highlightNodes = [];
2502     var lineText = element.textContent;
2503     var ownerDocument = element.ownerDocument;
2504     var textNodeSnapshot = ownerDocument.evaluate(".//text()", element, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
2505
2506     var snapshotLength = textNodeSnapshot.snapshotLength;
2507     if (snapshotLength === 0)
2508         return highlightNodes;
2509
2510     var nodeRanges = [];
2511     var rangeEndOffset = 0;
2512     for (var i = 0; i < snapshotLength; ++i) {
2513         var range = {};
2514         range.offset = rangeEndOffset;
2515         range.length = textNodeSnapshot.snapshotItem(i).textContent.length;
2516         rangeEndOffset = range.offset + range.length;
2517         nodeRanges.push(range);
2518     }
2519
2520     var startIndex = 0;
2521     for (var i = 0; i < resultRanges.length; ++i) {
2522         var startOffset = resultRanges[i].offset;
2523         var endOffset = startOffset + resultRanges[i].length;
2524
2525         while (startIndex < snapshotLength && nodeRanges[startIndex].offset + nodeRanges[startIndex].length <= startOffset)
2526             startIndex++;
2527         var endIndex = startIndex;
2528         while (endIndex < snapshotLength && nodeRanges[endIndex].offset + nodeRanges[endIndex].length < endOffset)
2529             endIndex++;
2530         if (endIndex === snapshotLength)
2531             break;
2532
2533         var highlightNode = ownerDocument.createElement("span");
2534         highlightNode.className = styleClass;
2535         highlightNode.textContent = lineText.substring(startOffset, endOffset);
2536
2537         var lastTextNode = textNodeSnapshot.snapshotItem(endIndex);
2538         var lastText = lastTextNode.textContent;
2539         lastTextNode.textContent = lastText.substring(endOffset - nodeRanges[endIndex].offset);
2540         changes.push({node: lastTextNode, type: "changed", oldText: lastText, newText: lastTextNode.textContent});
2541
2542         if (startIndex === endIndex) {
2543             lastTextNode.parentElement.insertBefore(highlightNode, lastTextNode);
2544             changes.push({node: highlightNode, type: "added", nextSibling: lastTextNode, parent: lastTextNode.parentElement});
2545             highlightNodes.push(highlightNode);
2546
2547             var prefixNode = ownerDocument.createTextNode(lastText.substring(0, startOffset - nodeRanges[startIndex].offset));
2548             lastTextNode.parentElement.insertBefore(prefixNode, highlightNode);
2549             changes.push({node: prefixNode, type: "added", nextSibling: highlightNode, parent: lastTextNode.parentElement});
2550         } else {
2551             var firstTextNode = textNodeSnapshot.snapshotItem(startIndex);
2552             var firstText = firstTextNode.textContent;
2553             var anchorElement = firstTextNode.nextSibling;
2554
2555             firstTextNode.parentElement.insertBefore(highlightNode, anchorElement);
2556             changes.push({node: highlightNode, type: "added", nextSibling: anchorElement, parent: firstTextNode.parentElement});
2557             highlightNodes.push(highlightNode);
2558
2559             firstTextNode.textContent = firstText.substring(0, startOffset - nodeRanges[startIndex].offset);
2560             changes.push({node: firstTextNode, type: "changed", oldText: firstText, newText: firstTextNode.textContent});
2561
2562             for (var j = startIndex + 1; j < endIndex; j++) {
2563                 var textNode = textNodeSnapshot.snapshotItem(j);
2564                 var text = textNode.textContent;
2565                 textNode.textContent = "";
2566                 changes.push({node: textNode, type: "changed", oldText: text, newText: textNode.textContent});
2567             }
2568         }
2569         startIndex = endIndex;
2570         nodeRanges[startIndex].offset = endOffset;
2571         nodeRanges[startIndex].length = lastTextNode.textContent.length;
2572
2573     }
2574     return highlightNodes;
2575 };
2576
2577 WI.revertDOMChanges = function(domChanges)
2578 {
2579     for (var i = domChanges.length - 1; i >= 0; --i) {
2580         var entry = domChanges[i];
2581         switch (entry.type) {
2582         case "added":
2583             entry.node.remove();
2584             break;
2585         case "changed":
2586             entry.node.textContent = entry.oldText;
2587             break;
2588         }
2589     }
2590 };
2591
2592 WI.archiveMainFrame = function()
2593 {
2594     this._downloadingPage = true;
2595     this._updateDownloadToolbarButton();
2596
2597     PageAgent.archive((error, data) => {
2598         this._downloadingPage = false;
2599         this._updateDownloadToolbarButton();
2600
2601         if (error)
2602             return;
2603
2604         let mainFrame = WI.networkManager.mainFrame;
2605         let archiveName = mainFrame.mainResource.urlComponents.host || mainFrame.mainResource.displayName || "Archive";
2606         let url = "web-inspector:///" + encodeURI(archiveName) + ".webarchive";
2607
2608         InspectorFrontendHost.save(url, data, true, true);
2609     });
2610 };
2611
2612 WI.canArchiveMainFrame = function()
2613 {
2614     if (this.sharedApp.debuggableType !== WI.DebuggableType.Web)
2615         return false;
2616
2617     if (!WI.networkManager.mainFrame || !WI.networkManager.mainFrame.mainResource)
2618         return false;
2619
2620     return WI.Resource.typeFromMIMEType(WI.networkManager.mainFrame.mainResource.mimeType) === WI.Resource.Type.Document;
2621 };
2622
2623 WI.addWindowKeydownListener = function(listener)
2624 {
2625     if (typeof listener.handleKeydownEvent !== "function")
2626         return;
2627
2628     this._windowKeydownListeners.push(listener);
2629
2630     this._updateWindowKeydownListener();
2631 };
2632
2633 WI.removeWindowKeydownListener = function(listener)
2634 {
2635     this._windowKeydownListeners.remove(listener);
2636
2637     this._updateWindowKeydownListener();
2638 };
2639
2640 WI._updateWindowKeydownListener = function()
2641 {
2642     if (this._windowKeydownListeners.length === 1)
2643         window.addEventListener("keydown", WI._sharedWindowKeydownListener, true);
2644     else if (!this._windowKeydownListeners.length)
2645         window.removeEventListener("keydown", WI._sharedWindowKeydownListener, true);
2646 };
2647
2648 WI._sharedWindowKeydownListener = function(event)
2649 {
2650     for (var i = WI._windowKeydownListeners.length - 1; i >= 0; --i) {
2651         if (WI._windowKeydownListeners[i].handleKeydownEvent(event)) {
2652             event.stopImmediatePropagation();
2653             event.preventDefault();
2654             break;
2655         }
2656     }
2657 };
2658
2659 WI.reportInternalError = function(errorOrString, details = {})
2660 {
2661     // The 'details' object includes additional information from the caller as free-form string keys and values.
2662     // Each key and value will be shown in the uncaught exception reporter, console error message, or in
2663     // a pre-filled bug report generated for this internal error.
2664
2665     let error = errorOrString instanceof Error ? errorOrString : new Error(errorOrString);
2666     error.details = details;
2667
2668     // The error will be displayed in the Uncaught Exception Reporter sheet if DebugUI is enabled.
2669     if (WI.isDebugUIEnabled()) {
2670         // This assert allows us to stop the debugger at an internal exception. It doesn't re-throw
2671         // exceptions because the original exception would be lost through window.onerror.
2672         // This workaround can be removed once <https://webkit.org/b/158192> is fixed.
2673         console.assert(false, "An internal exception was thrown.", error);
2674         handleInternalException(error);
2675     } else
2676         console.error(error);
2677 };
2678
2679 Object.defineProperty(WI, "targets",
2680 {
2681     get() { return WI.targetManager.targets; }
2682 });
2683
2684 // Many places assume the main target because they cannot yet be
2685 // used by reached by Worker debugging. Eventually, once all
2686 // Worker domains have been implemented, all of these must be
2687 // handled properly.
2688 WI.assumingMainTarget = function()
2689 {
2690     return WI.mainTarget;
2691 };
2692
2693 // OpenResourceDialog delegate
2694
2695 WI.dialogWasDismissedWithRepresentedObject = function(dialog, representedObject)
2696 {
2697     if (!representedObject)
2698         return;
2699
2700     WI.showRepresentedObject(representedObject, dialog.cookie);
2701 };
2702
2703 WI.DockConfiguration = {
2704     Right: "right",
2705     Left: "left",
2706     Bottom: "bottom",
2707     Undocked: "undocked",
2708 };