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