Initial patch.
[vuplus_webkit] / Source / WebCore / inspector / front-end / inspector.js
1 /*
2  * Copyright (C) 2006, 2007, 2008 Apple Inc.  All rights reserved.
3  * Copyright (C) 2007 Matt Lilek (pewtermoose@gmail.com).
4  * Copyright (C) 2009 Joseph Pecoraro
5  *
6  * Redistribution and use in source and binary forms, with or without
7  * modification, are permitted provided that the following conditions
8  * are met:
9  *
10  * 1.  Redistributions of source code must retain the above copyright
11  *     notice, this list of conditions and the following disclaimer.
12  * 2.  Redistributions in binary form must reproduce the above copyright
13  *     notice, this list of conditions and the following disclaimer in the
14  *     documentation and/or other materials provided with the distribution.
15  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
16  *     its contributors may be used to endorse or promote products derived
17  *     from this software without specific prior written permission.
18  *
19  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
20  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
21  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
23  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
24  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
25  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
26  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
28  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
30
31 var WebInspector = {
32     resources: {},
33     missingLocalizedStrings: {},
34     pendingDispatches: 0,
35
36     get platform()
37     {
38         if (!("_platform" in this))
39             this._platform = InspectorFrontendHost.platform();
40
41         return this._platform;
42     },
43
44     get platformFlavor()
45     {
46         if (!("_platformFlavor" in this))
47             this._platformFlavor = this._detectPlatformFlavor();
48
49         return this._platformFlavor;
50     },
51
52     _detectPlatformFlavor: function()
53     {
54         const userAgent = navigator.userAgent;
55
56         if (this.platform === "windows") {
57             var match = userAgent.match(/Windows NT (\d+)\.(?:\d+)/);
58             if (match && match[1] >= 6)
59                 return WebInspector.PlatformFlavor.WindowsVista;
60             return null;
61         } else if (this.platform === "mac") {
62             var match = userAgent.match(/Mac OS X\s*(?:(\d+)_(\d+))?/);
63             if (!match || match[1] != 10)
64                 return WebInspector.PlatformFlavor.MacSnowLeopard;
65             switch (Number(match[2])) {
66                 case 4:
67                     return WebInspector.PlatformFlavor.MacTiger;
68                 case 5:
69                     return WebInspector.PlatformFlavor.MacLeopard;
70                 case 6:
71                 default:
72                     return WebInspector.PlatformFlavor.MacSnowLeopard;
73             }
74         }
75
76         return null;
77     },
78
79     get port()
80     {
81         if (!("_port" in this))
82             this._port = InspectorFrontendHost.port();
83
84         return this._port;
85     },
86
87     get previousFocusElement()
88     {
89         return this._previousFocusElement;
90     },
91
92     get currentFocusElement()
93     {
94         return this._currentFocusElement;
95     },
96
97     set currentFocusElement(x)
98     {
99         if (this._currentFocusElement !== x)
100             this._previousFocusElement = this._currentFocusElement;
101         this._currentFocusElement = x;
102
103         if (this._currentFocusElement) {
104             this._currentFocusElement.focus();
105
106             // Make a caret selection inside the new element if there isn't a range selection and
107             // there isn't already a caret selection inside.
108             var selection = window.getSelection();
109             if (selection.isCollapsed && !this._currentFocusElement.isInsertionCaretInside()) {
110                 var selectionRange = this._currentFocusElement.ownerDocument.createRange();
111                 selectionRange.setStart(this._currentFocusElement, 0);
112                 selectionRange.setEnd(this._currentFocusElement, 0);
113
114                 selection.removeAllRanges();
115                 selection.addRange(selectionRange);
116             }
117         } else if (this._previousFocusElement)
118             this._previousFocusElement.blur();
119     },
120
121     currentPanel: function()
122     {
123         return this._currentPanel;
124     },
125
126     setCurrentPanel: function(x)
127     {
128         if (this._currentPanel === x)
129             return;
130
131         if (this._currentPanel)
132             this._currentPanel.hide();
133
134         this._currentPanel = x;
135
136         if (x) {
137             x.show();
138             WebInspector.searchController.activePanelChanged();
139         }
140         for (var panelName in WebInspector.panels) {
141             if (WebInspector.panels[panelName] === x) {
142                 WebInspector.settings.lastActivePanel.set(panelName);
143                 this._panelHistory.setPanel(panelName);
144                 WebInspector.userMetrics.panelShown(panelName);
145             }
146         }
147     },
148
149     _createPanels: function()
150     {
151         if (WebInspector.WorkerManager.isWorkerFrontend()) {
152             this.panels.scripts = new WebInspector.ScriptsPanel(this.debuggerPresentationModel);
153             this.panels.console = new WebInspector.ConsolePanel();
154             return;
155         }
156         var hiddenPanels = (InspectorFrontendHost.hiddenPanels() || "").split(',');
157         if (hiddenPanels.indexOf("elements") === -1)
158             this.panels.elements = new WebInspector.ElementsPanel();
159         if (hiddenPanels.indexOf("resources") === -1)
160             this.panels.resources = new WebInspector.ResourcesPanel();
161         if (hiddenPanels.indexOf("network") === -1)
162             this.panels.network = new WebInspector.NetworkPanel();
163         if (hiddenPanels.indexOf("scripts") === -1)
164             this.panels.scripts = new WebInspector.ScriptsPanel(this.debuggerPresentationModel);
165         if (hiddenPanels.indexOf("timeline") === -1)
166             this.panels.timeline = new WebInspector.TimelinePanel();
167         if (hiddenPanels.indexOf("profiles") === -1)
168             this.panels.profiles = new WebInspector.ProfilesPanel();
169         if (hiddenPanels.indexOf("audits") === -1)
170             this.panels.audits = new WebInspector.AuditsPanel();
171         if (hiddenPanels.indexOf("console") === -1)
172             this.panels.console = new WebInspector.ConsolePanel();
173     },
174
175     _createGlobalStatusBarItems: function()
176     {
177         this._dockToggleButton = new WebInspector.StatusBarButton(this._dockButtonTitle(), "dock-status-bar-item");
178         this._dockToggleButton.addEventListener("click", this._toggleAttach.bind(this), false);
179         this._dockToggleButton.toggled = !this.attached;
180
181         this._settingsButton = new WebInspector.StatusBarButton(WebInspector.UIString("Settings"), "settings-status-bar-item");
182         this._settingsButton.addEventListener("click", this._toggleSettings.bind(this), false);
183
184         var anchoredStatusBar = document.getElementById("anchored-status-bar-items");
185         anchoredStatusBar.appendChild(this._dockToggleButton.element);
186         anchoredStatusBar.appendChild(this.consoleView.toggleConsoleButton.element);
187         if (this.panels.elements)
188             anchoredStatusBar.appendChild(this.panels.elements.nodeSearchButton.element);
189
190         anchoredStatusBar.appendChild(this._settingsButton.element);
191     },
192
193     _dockButtonTitle: function()
194     {
195         return this.attached ? WebInspector.UIString("Undock into separate window.") : WebInspector.UIString("Dock to main window.");
196     },
197
198     _toggleAttach: function()
199     {
200         if (!this._attached) {
201             InspectorFrontendHost.requestAttachWindow();
202             WebInspector.userMetrics.WindowDocked.record();
203         } else {
204             InspectorFrontendHost.requestDetachWindow();
205             WebInspector.userMetrics.WindowUndocked.record();
206         }
207     },
208
209     _toggleSettings: function()
210     {
211         this._settingsButton.toggled = !this._settingsButton.toggled;
212         if (this._settingsButton.toggled)
213             this._showSettingsScreen();
214         else
215             this._hideSettingsScreen();
216     },
217
218     _showShortcutsScreen: function()
219     {
220         this._hideSettingsScreen();
221         WebInspector.shortcutsScreen.show();
222     },
223
224     _hideShortcutsScreen: function()
225     {
226         WebInspector.shortcutsScreen.hide();
227     },
228
229     _showSettingsScreen: function()
230     {
231         this._hideShortcutsScreen();
232         function onhide()
233         {
234             this._settingsButton.toggled = false;
235             delete this._settingsScreen;
236         }
237
238         if (!this._settingsScreen) {
239             this._settingsScreen = new WebInspector.SettingsScreen();
240             this._settingsScreen.show(onhide.bind(this));
241         }
242     },
243
244     _hideSettingsScreen: function()
245     {
246         if (this._settingsScreen) {
247             this._settingsScreen.hide();
248             this._settingsButton.toggled = false;
249             delete this._settingsScreen;
250         }
251     },
252
253     get attached()
254     {
255         return this._attached;
256     },
257
258     set attached(x)
259     {
260         if (this._attached === x)
261             return;
262
263         this._attached = x;
264
265         var body = document.body;
266
267         if (x) {
268             body.removeStyleClass("detached");
269             body.addStyleClass("attached");
270         } else {
271             body.removeStyleClass("attached");
272             body.addStyleClass("detached");
273         }
274
275         if (this._dockToggleButton) {
276             this._dockToggleButton.title = this._dockButtonTitle();
277             this._dockToggleButton.toggled = !x;
278         }
279
280         // This may be called before onLoadedDone, hence the bulk of inspector objects may
281         // not be created yet.
282         if (WebInspector.toolbar)
283             WebInspector.toolbar.attached = x;
284
285         if (WebInspector.searchController)
286             WebInspector.searchController.updateSearchLabel();
287
288         if (WebInspector.drawer)
289             WebInspector.drawer.updateHeight();
290     },
291
292     _updateErrorAndWarningCounts: function()
293     {
294         var errorWarningElement = document.getElementById("error-warning-count");
295         if (!errorWarningElement)
296             return;
297
298         var errors = WebInspector.console.errors;
299         var warnings = WebInspector.console.warnings;
300         if (!errors && !warnings) {
301             errorWarningElement.addStyleClass("hidden");
302             return;
303         }
304
305         errorWarningElement.removeStyleClass("hidden");
306
307         errorWarningElement.removeChildren();
308
309         if (errors) {
310             var errorElement = document.createElement("span");
311             errorElement.id = "error-count";
312             errorElement.textContent = errors;
313             errorWarningElement.appendChild(errorElement);
314         }
315
316         if (warnings) {
317             var warningsElement = document.createElement("span");
318             warningsElement.id = "warning-count";
319             warningsElement.textContent = warnings;
320             errorWarningElement.appendChild(warningsElement);
321         }
322
323         if (errors) {
324             if (warnings) {
325                 if (errors == 1) {
326                     if (warnings == 1)
327                         errorWarningElement.title = WebInspector.UIString("%d error, %d warning", errors, warnings);
328                     else
329                         errorWarningElement.title = WebInspector.UIString("%d error, %d warnings", errors, warnings);
330                 } else if (warnings == 1)
331                     errorWarningElement.title = WebInspector.UIString("%d errors, %d warning", errors, warnings);
332                 else
333                     errorWarningElement.title = WebInspector.UIString("%d errors, %d warnings", errors, warnings);
334             } else if (errors == 1)
335                 errorWarningElement.title = WebInspector.UIString("%d error", errors);
336             else
337                 errorWarningElement.title = WebInspector.UIString("%d errors", errors);
338         } else if (warnings == 1)
339             errorWarningElement.title = WebInspector.UIString("%d warning", warnings);
340         else if (warnings)
341             errorWarningElement.title = WebInspector.UIString("%d warnings", warnings);
342         else
343             errorWarningElement.title = null;
344     },
345
346     buildHighlightConfig: function(mode)
347     {
348         mode = mode || "all";
349         var highlightConfig = { showInfo: mode === "all" };
350         if (mode === "all" || mode === "content") {
351             highlightConfig.contentColor = WebInspector.Color.PageHighlight.Content.toProtocolRGBA();
352             highlightConfig.contentOutlineColor = WebInspector.Color.PageHighlight.ContentOutline.toProtocolRGBA();
353         }
354
355         if (mode === "all" || mode === "padding") {
356             highlightConfig.paddingColor = WebInspector.Color.PageHighlight.Padding.toProtocolRGBA();
357             highlightConfig.paddingOutlineColor = WebInspector.Color.PageHighlight.PaddingOutline.toProtocolRGBA();
358         }
359
360         if (mode === "all" || mode === "border") {
361             highlightConfig.borderColor = WebInspector.Color.PageHighlight.Border.toProtocolRGBA();
362             highlightConfig.borderOutlineColor = WebInspector.Color.PageHighlight.BorderOutline.toProtocolRGBA();
363         }
364
365         if (mode === "all" || mode === "margin") {
366             highlightConfig.marginColor = WebInspector.Color.PageHighlight.Margin.toProtocolRGBA();
367             highlightConfig.marginOutlineColor = WebInspector.Color.PageHighlight.MarginOutline.toProtocolRGBA();
368         }
369
370         return highlightConfig;
371     },
372
373     highlightDOMNode: function(nodeId, mode)
374     {
375         if ("_hideDOMNodeHighlightTimeout" in this) {
376             clearTimeout(this._hideDOMNodeHighlightTimeout);
377             delete this._hideDOMNodeHighlightTimeout;
378         }
379
380         if (this._highlightedDOMNodeId === nodeId)
381             return;
382
383         this._highlightedDOMNodeId = nodeId;
384         if (nodeId)
385             DOMAgent.highlightNode(nodeId, this.buildHighlightConfig(mode));
386         else
387             DOMAgent.hideHighlight();
388     },
389
390     highlightDOMNodeForTwoSeconds: function(nodeId)
391     {
392         this.highlightDOMNode(nodeId);
393         this._hideDOMNodeHighlightTimeout = setTimeout(this.highlightDOMNode.bind(this, 0), 2000);
394     },
395
396     wireElementWithDOMNode: function(element, nodeId)
397     {
398         element.addEventListener("click", this._updateFocusedNode.bind(this, nodeId), false);
399         element.addEventListener("mouseover", this.highlightDOMNode.bind(this, nodeId, "all"), false);
400         element.addEventListener("mouseout", this.highlightDOMNode.bind(this, 0), false);
401     },
402
403     _updateFocusedNode: function(nodeId)
404     {
405         this.setCurrentPanel(this.panels.elements);
406         this.panels.elements.updateFocusedNode(nodeId);
407     },
408
409     networkResourceById: function(id)
410     {
411         return this.panels.network.resourceById(id);
412     },
413
414     forAllResources: function(callback)
415     {
416         WebInspector.resourceTreeModel.forAllResources(callback);
417     },
418
419     resourceForURL: function(url)
420     {
421         return this.resourceTreeModel.resourceForURL(url);
422     },
423
424     openLinkExternallyLabel: function()
425     {
426         return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Open link in new tab" : "Open Link in New Tab");
427     },
428
429     copyLinkAddressLabel: function()
430     {
431         return WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Copy link address" : "Copy Link Address");
432     }
433 }
434
435 WebInspector.Events = {
436     InspectorClosing: "InspectorClosing"
437 }
438
439 {(function parseQueryParameters()
440 {
441     WebInspector.queryParamsObject = {};
442     var queryParams = window.location.search;
443     if (!queryParams)
444         return;
445     var params = queryParams.substring(1).split("&");
446     for (var i = 0; i < params.length; ++i) {
447         var pair = params[i].split("=");
448         WebInspector.queryParamsObject[pair[0]] = pair[1];
449     }
450 })();}
451
452 WebInspector.loaded = function()
453 {
454     if ("page" in WebInspector.queryParamsObject) {
455         var page = WebInspector.queryParamsObject.page;
456         var host = "host" in WebInspector.queryParamsObject ? WebInspector.queryParamsObject.host : window.location.host;
457         WebInspector.socket = new WebSocket("ws://" + host + "/devtools/page/" + page);
458         WebInspector.socket.onmessage = function(message) { InspectorBackend.dispatch(message.data); }
459         WebInspector.socket.onerror = function(error) { console.error(error); }
460         WebInspector.socket.onopen = function() {
461             InspectorFrontendHost.sendMessageToBackend = WebInspector.socket.send.bind(WebInspector.socket);
462             WebInspector.doLoadedDone();
463         }
464         return;
465     }
466     WebInspector.WorkerManager.loaded();
467     WebInspector.doLoadedDone();
468 }
469
470 WebInspector.doLoadedDone = function()
471 {
472     InspectorFrontendHost.loaded();
473
474     this.notifications = new WebInspector.Object();
475
476     var platform = WebInspector.platform;
477     document.body.addStyleClass("platform-" + platform);
478     var flavor = WebInspector.platformFlavor;
479     if (flavor)
480         document.body.addStyleClass("platform-" + flavor);
481     var port = WebInspector.port;
482     document.body.addStyleClass("port-" + port);
483     if (WebInspector.socket)
484         document.body.addStyleClass("remote");
485
486     this._registerShortcuts();
487
488     // set order of some sections explicitly
489     WebInspector.shortcutsScreen.section(WebInspector.UIString("Console"));
490     WebInspector.shortcutsScreen.section(WebInspector.UIString("Elements Panel"));
491
492     this.console = new WebInspector.ConsoleModel();
493     this.console.addEventListener(WebInspector.ConsoleModel.Events.ConsoleCleared, this._updateErrorAndWarningCounts, this);
494     this.console.addEventListener(WebInspector.ConsoleModel.Events.MessageAdded, this._updateErrorAndWarningCounts, this);
495     this.console.addEventListener(WebInspector.ConsoleModel.Events.RepeatCountUpdated, this._updateErrorAndWarningCounts, this);
496
497     this.drawer = new WebInspector.Drawer();
498     this.consoleView = new WebInspector.ConsoleView(this.drawer);
499     this.drawer.visibleView = this.consoleView;
500
501     this.networkManager = new WebInspector.NetworkManager();
502     this.resourceTreeModel = new WebInspector.ResourceTreeModel();
503     this.networkLog = new WebInspector.NetworkLog();
504     this.domAgent = new WebInspector.DOMAgent();
505     new WebInspector.JavaScriptContextManager(this.resourceTreeModel, this.consoleView);
506
507     InspectorBackend.registerInspectorDispatcher(this);
508
509     this.cssModel = new WebInspector.CSSStyleModel();
510     this.debuggerModel = new WebInspector.DebuggerModel();
511     this.debuggerPresentationModel = new WebInspector.DebuggerPresentationModel();
512
513     this.searchController = new WebInspector.SearchController();
514
515     if (Preferences.nativeInstrumentationEnabled)
516         this.domBreakpointsSidebarPane = new WebInspector.DOMBreakpointsSidebarPane();
517
518     this.panels = {};
519     this._createPanels();
520     this._createGlobalStatusBarItems();
521
522     this._panelHistory = new WebInspector.PanelHistory();
523     this.toolbar = new WebInspector.Toolbar();
524     this.toolbar.attached = WebInspector.attached;
525
526     this.panelOrder = [];
527     for (var panelName in this.panels)
528         this.addPanel(this.panels[panelName]);
529
530     this.addMainEventListeners(document);
531
532     window.addEventListener("resize", this.windowResize.bind(this), true);
533
534     var errorWarningCount = document.getElementById("error-warning-count");
535     errorWarningCount.addEventListener("click", this.showConsole.bind(this), false);
536     this._updateErrorAndWarningCounts();
537
538     this.extensionServer.initExtensions();
539
540     this.console.enableAgent();
541
542     DatabaseAgent.enable();
543     DOMStorageAgent.enable();
544
545     WebInspector.showPanel(WebInspector.settings.lastActivePanel.get());
546
547     function propertyNamesCallback(error, names)
548     {
549         if (!error)
550             WebInspector.cssNameCompletions = new WebInspector.CSSCompletions(names);
551     }
552     // As a DOMAgent method, this needs to happen after the frontend has loaded and the agent is available.
553     CSSAgent.getSupportedCSSProperties(propertyNamesCallback);
554 }
555
556 WebInspector.addPanel = function(panel)
557 {
558     this.panelOrder.push(panel);
559     this.toolbar.addPanel(panel);
560 }
561
562 var windowLoaded = function()
563 {
564     var localizedStringsURL = InspectorFrontendHost.localizedStringsURL();
565     if (localizedStringsURL) {
566         var localizedStringsScriptElement = document.createElement("script");
567         localizedStringsScriptElement.addEventListener("load", WebInspector.loaded.bind(WebInspector), false);
568         localizedStringsScriptElement.type = "text/javascript";
569         localizedStringsScriptElement.src = localizedStringsURL;
570         document.head.appendChild(localizedStringsScriptElement);
571     } else
572         WebInspector.loaded();
573
574     WebInspector.setAttachedWindow(WebInspector.queryParamsObject.docked === "true");
575
576     window.removeEventListener("DOMContentLoaded", windowLoaded, false);
577     delete windowLoaded;
578 };
579
580 window.addEventListener("DOMContentLoaded", windowLoaded, false);
581
582 // We'd like to enforce asynchronous interaction between the inspector controller and the frontend.
583 // It is needed to prevent re-entering the backend code.
584 // Also, native dispatches do not guarantee setTimeouts to be serialized, so we
585 // enforce serialization using 'messagesToDispatch' queue. It is also important that JSC debugger
586 // tests require that each command was dispatch within individual timeout callback, so we don't batch them.
587
588 var messagesToDispatch = [];
589
590 WebInspector.dispatchQueueIsEmpty = function() {
591     return messagesToDispatch.length == 0;
592 }
593
594 WebInspector.dispatch = function(message) {
595     messagesToDispatch.push(message);
596     setTimeout(function() {
597         InspectorBackend.dispatch(messagesToDispatch.shift());
598     }, 0);
599 }
600
601 WebInspector.dispatchMessageFromBackend = function(messageObject)
602 {
603     WebInspector.dispatch(messageObject);
604 }
605
606 WebInspector.windowResize = function(event)
607 {
608     if (this.currentPanel())
609         this.currentPanel().doResize();
610     this.drawer.doResize();
611     this.toolbar.resize();
612 }
613
614 WebInspector.windowFocused = function(event)
615 {
616     // Fires after blur, so when focusing on either the main inspector
617     // or an <iframe> within the inspector we should always remove the
618     // "inactive" class.
619     if (event.target.document.nodeType === Node.DOCUMENT_NODE)
620         document.body.removeStyleClass("inactive");
621 }
622
623 WebInspector.windowBlurred = function(event)
624 {
625     // Leaving the main inspector or an <iframe> within the inspector.
626     // We can add "inactive" now, and if we are moving the focus to another
627     // part of the inspector then windowFocused will correct this.
628     if (event.target.document.nodeType === Node.DOCUMENT_NODE)
629         document.body.addStyleClass("inactive");
630 }
631
632 WebInspector.focusChanged = function(event)
633 {
634     this.currentFocusElement = event.target;
635 }
636
637 WebInspector.setAttachedWindow = function(attached)
638 {
639     this.attached = attached;
640 }
641
642 WebInspector.close = function(event)
643 {
644     if (this._isClosing)
645         return;
646     this._isClosing = true;
647     this.notifications.dispatchEventToListeners(WebInspector.Events.InspectorClosing);
648     InspectorFrontendHost.closeWindow();
649 }
650
651 WebInspector.disconnectFromBackend = function()
652 {
653     InspectorFrontendHost.disconnectFromBackend();
654 }
655
656 WebInspector.documentClick = function(event)
657 {
658     var anchor = event.target.enclosingNodeOrSelfWithNodeName("a");
659     if (!anchor || anchor.target === "_blank")
660         return;
661
662     // Prevent the link from navigating, since we don't do any navigation by following links normally.
663     event.preventDefault();
664     event.stopPropagation();
665
666     function followLink()
667     {
668         if (WebInspector._showAnchorLocation(anchor))
669             return;
670
671         const profileMatch = WebInspector.ProfileType.URLRegExp.exec(anchor.href);
672         if (profileMatch) {
673             WebInspector.showProfileForURL(anchor.href);
674             return;
675         }
676
677         var parsedURL = anchor.href.asParsedURL();
678         if (parsedURL && parsedURL.scheme === "webkit-link-action") {
679             if (parsedURL.host === "show-panel") {
680                 var panel = parsedURL.path.substring(1);
681                 if (WebInspector.panels[panel])
682                     WebInspector.showPanel(panel);
683             }
684             return;
685         }
686
687         WebInspector.showPanel("resources");
688     }
689
690     if (WebInspector.followLinkTimeout)
691         clearTimeout(WebInspector.followLinkTimeout);
692
693     if (anchor.preventFollowOnDoubleClick) {
694         // Start a timeout if this is the first click, if the timeout is canceled
695         // before it fires, then a double clicked happened or another link was clicked.
696         if (event.detail === 1)
697             WebInspector.followLinkTimeout = setTimeout(followLink, 333);
698         return;
699     }
700
701     followLink();
702 }
703
704 WebInspector.openResource = function(resourceURL, inResourcesPanel)
705 {
706     var resource = WebInspector.resourceForURL(resourceURL);
707     if (inResourcesPanel && resource) {
708         WebInspector.panels.resources.showResource(resource);
709         WebInspector.showPanel("resources");
710     } else
711         PageAgent.open(resourceURL, true);
712 }
713
714 WebInspector._registerShortcuts = function()
715 {
716     var shortcut = WebInspector.KeyboardShortcut;
717     var section = WebInspector.shortcutsScreen.section(WebInspector.UIString("All Panels"));
718     var keys = [
719         shortcut.shortcutToString("]", shortcut.Modifiers.CtrlOrMeta),
720         shortcut.shortcutToString("[", shortcut.Modifiers.CtrlOrMeta)
721     ];
722     section.addRelatedKeys(keys, WebInspector.UIString("Next/previous panel"));
723     section.addKey(shortcut.shortcutToString(shortcut.Keys.Esc), WebInspector.UIString("Toggle console"));
724     section.addKey(shortcut.shortcutToString("f", shortcut.Modifiers.CtrlOrMeta), WebInspector.UIString("Search"));
725     if (WebInspector.isMac()) {
726         keys = [
727             shortcut.shortcutToString("g", shortcut.Modifiers.Meta),
728             shortcut.shortcutToString("g", shortcut.Modifiers.Meta | shortcut.Modifiers.Shift)
729         ];
730         section.addRelatedKeys(keys, WebInspector.UIString("Find next/previous"));
731     }
732
733     var goToShortcut = WebInspector.GoToLineDialog.createShortcut();
734     section.addKey(goToShortcut.name, WebInspector.UIString("Go to Line"));
735 }
736
737 WebInspector.documentKeyDown = function(event)
738 {
739     var isInputElement = event.target.nodeName === "INPUT";
740     var isInEditMode = event.target.enclosingNodeOrSelfWithClass("text-prompt") || WebInspector.isEditingAnyField();
741     const helpKey = WebInspector.isMac() ? "U+003F" : "U+00BF"; // "?" for both platforms
742
743     if (event.keyIdentifier === "F1" ||
744         (event.keyIdentifier === helpKey && event.shiftKey && (!isInEditMode && !isInputElement || event.metaKey))) {
745         this._showShortcutsScreen();
746         event.stopPropagation();
747         event.preventDefault();
748         return;
749     }
750
751     if (this.currentFocusElement && this.currentFocusElement.handleKeyEvent) {
752         this.currentFocusElement.handleKeyEvent(event);
753         if (event.handled) {
754             event.preventDefault();
755             return;
756         }
757     }
758
759     if (this.currentPanel()) {
760         this.currentPanel().handleShortcut(event);
761         if (event.handled) {
762             event.preventDefault();
763             return;
764         }
765     }
766
767     WebInspector.searchController.handleShortcut(event);
768     if (event.handled) {
769         event.preventDefault();
770         return;
771     }
772
773     if (WebInspector.isEditingAnyField())
774         return;
775
776     var isMac = WebInspector.isMac();
777     switch (event.keyIdentifier) {
778         case "Left":
779             var isBackKey = !isInEditMode && (isMac ? event.metaKey : event.ctrlKey);
780             if (isBackKey && this._panelHistory.canGoBack()) {
781                 this._panelHistory.goBack();
782                 event.preventDefault();
783             }
784             break;
785
786         case "Right":
787             var isForwardKey = !isInEditMode && (isMac ? event.metaKey : event.ctrlKey);
788             if (isForwardKey && this._panelHistory.canGoForward()) {
789                 this._panelHistory.goForward();
790                 event.preventDefault();
791             }
792             break;
793
794         case "U+001B": // Escape key
795             event.preventDefault();
796             if (this.drawer.fullPanel)
797                 return;
798
799             this.drawer.visible = !this.drawer.visible;
800             break;
801
802         // Windows and Mac have two different definitions of [, so accept both.
803         case "U+005B":
804         case "U+00DB": // [ key
805             if (isMac)
806                 var isRotateLeft = event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey;
807             else
808                 var isRotateLeft = event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey;
809
810             if (isRotateLeft) {
811                 var index = this.panelOrder.indexOf(this.currentPanel());
812                 index = (index === 0) ? this.panelOrder.length - 1 : index - 1;
813                 this.panelOrder[index].toolbarItem.click();
814                 event.preventDefault();
815             }
816
817             break;
818
819         // Windows and Mac have two different definitions of ], so accept both.
820         case "U+005D":
821         case "U+00DD":  // ] key
822             if (isMac)
823                 var isRotateRight = event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey;
824             else
825                 var isRotateRight = event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey;
826
827             if (isRotateRight) {
828                 var index = this.panelOrder.indexOf(this.currentPanel());
829                 index = (index + 1) % this.panelOrder.length;
830                 this.panelOrder[index].toolbarItem.click();
831                 event.preventDefault();
832             }
833
834             break;
835
836         case "U+0052": // R key
837             if ((event.metaKey && isMac) || (event.ctrlKey && !isMac)) {
838                 PageAgent.reload(event.shiftKey);
839                 event.preventDefault();
840             }
841             break;
842         case "F5":
843             if (!isMac) {
844                 PageAgent.reload(event.ctrlKey || event.shiftKey);
845                 event.preventDefault();
846             }
847             break;
848     }
849 }
850
851 WebInspector.documentCanCopy = function(event)
852 {
853     if (this.currentPanel() && this.currentPanel().handleCopyEvent)
854         event.preventDefault();
855 }
856
857 WebInspector.documentCopy = function(event)
858 {
859     if (this.currentPanel() && this.currentPanel().handleCopyEvent)
860         this.currentPanel().handleCopyEvent(event);
861 }
862
863 WebInspector.contextMenuEventFired = function(event)
864 {
865     if (event.handled || event.target.hasStyleClass("popup-glasspane"))
866         event.preventDefault();
867 }
868
869 WebInspector.animateStyle = function(animations, duration, callback)
870 {
871     var interval;
872     var complete = 0;
873     var hasCompleted = false;
874
875     const intervalDuration = (1000 / 30); // 30 frames per second.
876     const animationsLength = animations.length;
877     const propertyUnit = {opacity: ""};
878     const defaultUnit = "px";
879
880     function cubicInOut(t, b, c, d)
881     {
882         if ((t/=d/2) < 1) return c/2*t*t*t + b;
883         return c/2*((t-=2)*t*t + 2) + b;
884     }
885
886     // Pre-process animations.
887     for (var i = 0; i < animationsLength; ++i) {
888         var animation = animations[i];
889         var element = null, start = null, end = null, key = null;
890         for (key in animation) {
891             if (key === "element")
892                 element = animation[key];
893             else if (key === "start")
894                 start = animation[key];
895             else if (key === "end")
896                 end = animation[key];
897         }
898
899         if (!element || !end)
900             continue;
901
902         if (!start) {
903             var computedStyle = element.ownerDocument.defaultView.getComputedStyle(element);
904             start = {};
905             for (key in end)
906                 start[key] = parseInt(computedStyle.getPropertyValue(key));
907             animation.start = start;
908         } else
909             for (key in start)
910                 element.style.setProperty(key, start[key] + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
911     }
912
913     function animateLoop()
914     {
915         // Advance forward.
916         complete += intervalDuration;
917         var next = complete + intervalDuration;
918
919         // Make style changes.
920         for (var i = 0; i < animationsLength; ++i) {
921             var animation = animations[i];
922             var element = animation.element;
923             var start = animation.start;
924             var end = animation.end;
925             if (!element || !end)
926                 continue;
927
928             var style = element.style;
929             for (key in end) {
930                 var endValue = end[key];
931                 if (next < duration) {
932                     var startValue = start[key];
933                     var newValue = cubicInOut(complete, startValue, endValue - startValue, duration);
934                     style.setProperty(key, newValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
935                 } else
936                     style.setProperty(key, endValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
937             }
938         }
939
940         // End condition.
941         if (complete >= duration) {
942             hasCompleted = true;
943             clearInterval(interval);
944             if (callback)
945                 callback();
946         }
947     }
948
949     function forceComplete()
950     {
951         if (!hasCompleted) {
952             complete = duration;
953             animateLoop();
954         }
955     }
956
957     function cancel()
958     {
959         hasCompleted = true;
960         clearInterval(interval);
961     }
962
963     interval = setInterval(animateLoop, intervalDuration);
964     return {
965         cancel: cancel,
966         forceComplete: forceComplete
967     };
968 }
969
970 WebInspector.elementDragStart = function(element, dividerDrag, elementDragEnd, event, cursor)
971 {
972     if (this._elementDraggingEventListener || this._elementEndDraggingEventListener)
973         this.elementDragEnd(event);
974
975     this._elementDraggingEventListener = dividerDrag;
976     this._elementEndDraggingEventListener = elementDragEnd;
977
978     var targetDocument = event.target.ownerDocument;
979     targetDocument.addEventListener("mousemove", dividerDrag, true);
980     targetDocument.addEventListener("mouseup", elementDragEnd, true);
981
982     targetDocument.body.style.cursor = cursor;
983
984     event.preventDefault();
985 }
986
987 WebInspector.elementDragEnd = function(event)
988 {
989     var targetDocument = event.target.ownerDocument;
990     targetDocument.removeEventListener("mousemove", this._elementDraggingEventListener, true);
991     targetDocument.removeEventListener("mouseup", this._elementEndDraggingEventListener, true);
992
993     targetDocument.body.style.removeProperty("cursor");
994
995     delete this._elementDraggingEventListener;
996     delete this._elementEndDraggingEventListener;
997
998     event.preventDefault();
999 }
1000
1001 WebInspector.toggleSearchingForNode = function()
1002 {
1003     if (this.panels.elements) {
1004         this.showPanel("elements");
1005         this.panels.elements.toggleSearchingForNode();
1006     }
1007 }
1008
1009 WebInspector.showConsole = function()
1010 {
1011     this.drawer.showView(this.consoleView);
1012 }
1013
1014 WebInspector.showPanel = function(panel)
1015 {
1016     if (!(panel in this.panels)) {
1017         if (WebInspector.WorkerManager.isWorkerFrontend())
1018             panel = "scripts";
1019         else
1020             panel = "elements";
1021     }
1022     this.setCurrentPanel(this.panels[panel]);
1023 }
1024
1025 WebInspector.startUserInitiatedDebugging = function()
1026 {
1027     this.setCurrentPanel(this.panels.scripts);
1028     WebInspector.debuggerModel.enableDebugger();
1029 }
1030
1031 WebInspector.reset = function()
1032 {
1033     this.debuggerModel.reset();
1034     for (var panelName in this.panels) {
1035         var panel = this.panels[panelName];
1036         if ("reset" in panel)
1037             panel.reset();
1038     }
1039
1040     this.highlightDOMNode(0);
1041
1042     if (!WebInspector.settings.preserveConsoleLog.get())
1043         this.console.clearMessages();
1044     this.extensionServer.notifyInspectorReset();
1045     if (this.workerManager)
1046         this.workerManager.reset();
1047 }
1048
1049 WebInspector.bringToFront = function()
1050 {
1051     InspectorFrontendHost.bringToFront();
1052 }
1053
1054 WebInspector.didCreateWorker = function()
1055 {
1056     var workersPane = WebInspector.panels.scripts.sidebarPanes.workers;
1057     if (workersPane)
1058         workersPane.addWorker.apply(workersPane, arguments);
1059 }
1060
1061 WebInspector.didDestroyWorker = function()
1062 {
1063     var workersPane = WebInspector.panels.scripts.sidebarPanes.workers;
1064     if (workersPane)
1065         workersPane.removeWorker.apply(workersPane, arguments);
1066 }
1067
1068 WebInspector.log = function(message, messageLevel)
1069 {
1070     // remember 'this' for setInterval() callback
1071     var self = this;
1072
1073     // return indication if we can actually log a message
1074     function isLogAvailable()
1075     {
1076         return WebInspector.ConsoleMessage && WebInspector.RemoteObject && self.console;
1077     }
1078
1079     // flush the queue of pending messages
1080     function flushQueue()
1081     {
1082         var queued = WebInspector.log.queued;
1083         if (!queued)
1084             return;
1085
1086         for (var i = 0; i < queued.length; ++i)
1087             logMessage(queued[i]);
1088
1089         delete WebInspector.log.queued;
1090     }
1091
1092     // flush the queue if it console is available
1093     // - this function is run on an interval
1094     function flushQueueIfAvailable()
1095     {
1096         if (!isLogAvailable())
1097             return;
1098
1099         clearInterval(WebInspector.log.interval);
1100         delete WebInspector.log.interval;
1101
1102         flushQueue();
1103     }
1104
1105     // actually log the message
1106     function logMessage(message)
1107     {
1108         var repeatCount = 1;
1109         if (message == WebInspector.log.lastMessage)
1110             repeatCount = WebInspector.log.repeatCount + 1;
1111
1112         WebInspector.log.lastMessage = message;
1113         WebInspector.log.repeatCount = repeatCount;
1114
1115         // ConsoleMessage expects a proxy object
1116         message = WebInspector.RemoteObject.fromPrimitiveValue(message);
1117
1118         // post the message
1119         var msg = new WebInspector.ConsoleMessage(
1120             WebInspector.ConsoleMessage.MessageSource.Other,
1121             WebInspector.ConsoleMessage.MessageType.Log,
1122             messageLevel || WebInspector.ConsoleMessage.MessageLevel.Debug,
1123             -1,
1124             null,
1125             repeatCount,
1126             null,
1127             [message],
1128             null);
1129
1130         self.console.addMessage(msg);
1131     }
1132
1133     // if we can't log the message, queue it
1134     if (!isLogAvailable()) {
1135         if (!WebInspector.log.queued)
1136             WebInspector.log.queued = [];
1137
1138         WebInspector.log.queued.push(message);
1139
1140         if (!WebInspector.log.interval)
1141             WebInspector.log.interval = setInterval(flushQueueIfAvailable, 1000);
1142
1143         return;
1144     }
1145
1146     // flush the pending queue if any
1147     flushQueue();
1148
1149     // log the message
1150     logMessage(message);
1151 }
1152
1153 WebInspector.inspect = function(payload, hints)
1154 {
1155     var object = WebInspector.RemoteObject.fromPayload(payload);
1156     if (object.subtype === "node") {
1157         // Request node from backend and focus it.
1158         object.pushNodeToFrontend(WebInspector.updateFocusedNode.bind(WebInspector), object.release.bind(object));
1159         return;
1160     }
1161
1162     if (hints.databaseId) {
1163         WebInspector.setCurrentPanel(WebInspector.panels.resources);
1164         WebInspector.panels.resources.selectDatabase(hints.databaseId);
1165     } else if (hints.domStorageId) {
1166         WebInspector.setCurrentPanel(WebInspector.panels.resources);
1167         WebInspector.panels.resources.selectDOMStorage(hints.domStorageId);
1168     }
1169
1170     object.release();
1171 }
1172
1173 WebInspector.updateFocusedNode = function(nodeId)
1174 {
1175     this._updateFocusedNode(nodeId);
1176     this.highlightDOMNodeForTwoSeconds(nodeId);
1177 }
1178
1179 WebInspector.displayNameForURL = function(url)
1180 {
1181     if (!url)
1182         return "";
1183
1184     var resource = this.resourceForURL(url);
1185     if (resource)
1186         return resource.displayName;
1187
1188     if (!WebInspector.mainResource)
1189         return url.trimURL("");
1190
1191     var lastPathComponent = WebInspector.mainResource.lastPathComponent;
1192     var index = WebInspector.mainResource.url.indexOf(lastPathComponent);
1193     if (index !== -1 && index + lastPathComponent.length === WebInspector.mainResource.url.length) {
1194         var baseURL = WebInspector.mainResource.url.substring(0, index);
1195         if (url.indexOf(baseURL) === 0)
1196             return url.substring(index);
1197     }
1198
1199     return url.trimURL(WebInspector.mainResource.domain);
1200 }
1201
1202 WebInspector._showAnchorLocation = function(anchor)
1203 {
1204     var preferedPanel = this.panels[anchor.getAttribute("preferred_panel") || "resources"];
1205     if (WebInspector._showAnchorLocationInPanel(anchor, preferedPanel))
1206         return true;
1207     if (preferedPanel !== this.panels.resources && WebInspector._showAnchorLocationInPanel(anchor, this.panels.resources))
1208         return true;
1209     return false;
1210 }
1211
1212 WebInspector._showAnchorLocationInPanel = function(anchor, panel)
1213 {
1214     if (!panel.canShowAnchorLocation(anchor))
1215         return false;
1216
1217     // FIXME: support webkit-html-external-link links here.
1218     if (anchor.hasStyleClass("webkit-html-external-link")) {
1219         anchor.removeStyleClass("webkit-html-external-link");
1220         anchor.addStyleClass("webkit-html-resource-link");
1221     }
1222
1223     this.setCurrentPanel(panel);
1224     if (this.drawer)
1225         this.drawer.immediatelyFinishAnimation();
1226     this.currentPanel().showAnchorLocation(anchor);
1227     return true;
1228 }
1229
1230 WebInspector.linkifyStringAsFragment = function(string)
1231 {
1232     var container = document.createDocumentFragment();
1233     var linkStringRegEx = /(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\/\/|www\.)[\w$\-_+*'=\|\/\\(){}[\]%@&#~,:;.!?]{2,}[\w$\-_+*=\|\/\\({%@&#~]/;
1234     var lineColumnRegEx = /:(\d+)(:(\d+))?$/;
1235
1236     while (string) {
1237         var linkString = linkStringRegEx.exec(string);
1238         if (!linkString)
1239             break;
1240
1241         linkString = linkString[0];
1242         var title = linkString;
1243         var linkIndex = string.indexOf(linkString);
1244         var nonLink = string.substring(0, linkIndex);
1245         container.appendChild(document.createTextNode(nonLink));
1246
1247         var profileStringMatches = WebInspector.ProfileType.URLRegExp.exec(title);
1248         if (profileStringMatches)
1249             title = WebInspector.panels.profiles.displayTitleForProfileLink(profileStringMatches[2], profileStringMatches[1]);
1250
1251         var realURL = (linkString.indexOf("www.") === 0 ? "http://" + linkString : linkString);
1252         var lineColumnMatch = lineColumnRegEx.exec(realURL);
1253         if (lineColumnMatch)
1254             realURL = realURL.substring(0, realURL.length - lineColumnMatch[0].length);
1255
1256         var hasResourceWithURL = !!WebInspector.resourceForURL(realURL);
1257         var urlNode = WebInspector.linkifyURLAsNode(realURL, title, null, hasResourceWithURL);
1258         container.appendChild(urlNode);
1259         if (lineColumnMatch) {
1260             urlNode.setAttribute("line_number", lineColumnMatch[1]);
1261             urlNode.setAttribute("preferred_panel", "scripts");
1262         }
1263         string = string.substring(linkIndex + linkString.length, string.length);
1264     }
1265
1266     if (string)
1267         container.appendChild(document.createTextNode(string));
1268
1269     return container;
1270 }
1271
1272 WebInspector.showProfileForURL = function(url)
1273 {
1274     WebInspector.showPanel("profiles");
1275     WebInspector.panels.profiles.showProfileForURL(url);
1276 }
1277
1278 WebInspector.linkifyURLAsNode = function(url, linkText, classes, isExternal, tooltipText)
1279 {
1280     if (!linkText)
1281         linkText = url;
1282     classes = (classes ? classes + " " : "");
1283     classes += isExternal ? "webkit-html-external-link" : "webkit-html-resource-link";
1284
1285     var a = document.createElement("a");
1286     a.href = url;
1287     a.className = classes;
1288     if (typeof tooltipText === "undefined")
1289         a.title = url;
1290     else if (typeof tooltipText !== "string" || tooltipText.length)
1291         a.title = tooltipText;
1292     a.textContent = linkText;
1293     a.style.maxWidth = "100%";
1294     if (isExternal)
1295         a.setAttribute("target", "_blank");
1296
1297     return a;
1298 }
1299
1300 WebInspector.linkifyURL = function(url, linkText, classes, isExternal, tooltipText)
1301 {
1302     // Use the DOM version of this function so as to avoid needing to escape attributes.
1303     // FIXME:  Get rid of linkifyURL entirely.
1304     return WebInspector.linkifyURLAsNode(url, linkText, classes, isExternal, tooltipText).outerHTML;
1305 }
1306
1307 WebInspector.formatLinkText = function(url, lineNumber)
1308 {
1309     var text = WebInspector.displayNameForURL(url);
1310     if (lineNumber !== undefined)
1311         text += ":" + (lineNumber + 1);
1312     return text;
1313 }
1314
1315 WebInspector.linkifyResourceAsNode = function(url, lineNumber, classes, tooltipText)
1316 {
1317     var linkText = this.formatLinkText(url, lineNumber);
1318     var anchor = this.linkifyURLAsNode(url, linkText, classes, false, tooltipText);
1319     anchor.setAttribute("preferred_panel", "resources");
1320     anchor.setAttribute("line_number", lineNumber);
1321     return anchor;
1322 }
1323
1324 WebInspector.resourceURLForRelatedNode = function(node, url)
1325 {
1326     if (!url || url.indexOf("://") > 0)
1327         return url;
1328
1329     for (var frameOwnerCandidate = node; frameOwnerCandidate; frameOwnerCandidate = frameOwnerCandidate.parentNode) {
1330         if (frameOwnerCandidate.documentURL) {
1331             var result = WebInspector.completeURL(frameOwnerCandidate.documentURL, url);
1332             if (result)
1333                 return result;
1334             break;
1335         }
1336     }
1337
1338     // documentURL not found or has bad value
1339     var resourceURL = url;
1340     function callback(resource)
1341     {
1342         if (resource.path === url) {
1343             resourceURL = resource.url;
1344             return true;
1345         }
1346     }
1347     WebInspector.forAllResources(callback);
1348     return resourceURL;
1349 }
1350
1351 WebInspector.populateHrefContextMenu = function(contextMenu, contextNode, event)
1352 {
1353     var anchorElement = event.target.enclosingNodeOrSelfWithClass("webkit-html-resource-link") || event.target.enclosingNodeOrSelfWithClass("webkit-html-external-link");
1354     if (!anchorElement)
1355         return false;
1356
1357     var resourceURL = WebInspector.resourceURLForRelatedNode(contextNode, anchorElement.href);
1358     if (!resourceURL)
1359         return false;
1360
1361     // Add resource-related actions.
1362     contextMenu.appendItem(WebInspector.openLinkExternallyLabel(), WebInspector.openResource.bind(WebInspector, resourceURL, false));
1363     if (WebInspector.resourceForURL(resourceURL))
1364         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Open link in Resources panel" : "Open Link in Resources Panel"), WebInspector.openResource.bind(null, resourceURL, true));
1365     contextMenu.appendItem(WebInspector.copyLinkAddressLabel(), InspectorFrontendHost.copyText.bind(InspectorFrontendHost, resourceURL));
1366     return true;
1367 }
1368
1369 WebInspector.completeURL = function(baseURL, href)
1370 {
1371     if (href) {
1372         // Return absolute URLs as-is.
1373         var parsedHref = href.asParsedURL();
1374         if ((parsedHref && parsedHref.scheme) || href.indexOf("data:") === 0)
1375             return href;
1376     }
1377
1378     var parsedURL = baseURL.asParsedURL();
1379     if (parsedURL) {
1380         var path = href;
1381         if (path.charAt(0) !== "/") {
1382             var basePath = parsedURL.path;
1383             // A href of "?foo=bar" implies "basePath?foo=bar".
1384             // With "basePath?a=b" and "?foo=bar" we should get "basePath?foo=bar".
1385             var prefix;
1386             if (path.charAt(0) === "?") {
1387                 var basePathCutIndex = basePath.indexOf("?");
1388                 if (basePathCutIndex !== -1)
1389                     prefix = basePath.substring(0, basePathCutIndex);
1390                 else
1391                     prefix = basePath;
1392             } else
1393                 prefix = basePath.substring(0, basePath.lastIndexOf("/")) + "/";
1394
1395             path = prefix + path;
1396         } else if (path.length > 1 && path.charAt(1) === "/") {
1397             // href starts with "//" which is a full URL with the protocol dropped (use the baseURL protocol).
1398             return parsedURL.scheme + ":" + path;
1399         }
1400         return parsedURL.scheme + "://" + parsedURL.host + (parsedURL.port ? (":" + parsedURL.port) : "") + path;
1401     }
1402     return null;
1403 }
1404
1405 WebInspector.addMainEventListeners = function(doc)
1406 {
1407     doc.addEventListener("focus", this.focusChanged.bind(this), true);
1408     doc.addEventListener("keydown", this.documentKeyDown.bind(this), false);
1409     doc.addEventListener("beforecopy", this.documentCanCopy.bind(this), true);
1410     doc.addEventListener("copy", this.documentCopy.bind(this), true);
1411     doc.addEventListener("contextmenu", this.contextMenuEventFired.bind(this), true);
1412
1413     doc.defaultView.addEventListener("focus", this.windowFocused.bind(this), false);
1414     doc.defaultView.addEventListener("blur", this.windowBlurred.bind(this), false);
1415     doc.addEventListener("click", this.documentClick.bind(this), true);
1416 }
1417
1418 WebInspector.frontendReused = function()
1419 {
1420     this.resourceTreeModel.frontendReused();
1421     this.reset();
1422 }
1423
1424 WebInspector.UIString = function(string)
1425 {
1426     if (window.localizedStrings && string in window.localizedStrings)
1427         string = window.localizedStrings[string];
1428     else {
1429         if (!(string in WebInspector.missingLocalizedStrings)) {
1430             if (!WebInspector.InspectorBackendStub)
1431                 console.warn("Localized string \"" + string + "\" not found.");
1432             WebInspector.missingLocalizedStrings[string] = true;
1433         }
1434
1435         if (Preferences.showMissingLocalizedStrings)
1436             string += " (not localized)";
1437     }
1438
1439     return String.vsprintf(string, Array.prototype.slice.call(arguments, 1));
1440 }
1441
1442 WebInspector.formatLocalized = function(format, substitutions, formatters, initialValue, append)
1443 {
1444     return String.format(WebInspector.UIString(format), substitutions, formatters, initialValue, append);
1445 }
1446
1447 WebInspector.useLowerCaseMenuTitles = function()
1448 {
1449     return WebInspector.platform === "windows" && Preferences.useLowerCaseMenuTitlesOnWindows;
1450 }
1451
1452 WebInspector.isBeingEdited = function(element)
1453 {
1454     return element.__editing;
1455 }
1456
1457 WebInspector.markBeingEdited = function(element, value)
1458 {
1459     if (value) {
1460         if (element.__editing)
1461             return false;
1462         element.__editing = true;
1463         WebInspector.__editingCount = (WebInspector.__editingCount || 0) + 1;
1464     } else {
1465         if (!element.__editing)
1466             return false;
1467         delete element.__editing;
1468         --WebInspector.__editingCount;
1469     }
1470     return true;
1471 }
1472
1473 WebInspector.isEditingAnyField = function()
1474 {
1475     return !!WebInspector.__editingCount;
1476 }
1477
1478 // Available config fields (all optional):
1479 // context: Object - an arbitrary context object to be passed to the commit and cancel handlers
1480 // commitHandler: Function - handles editing "commit" outcome
1481 // cancelHandler: Function - handles editing "cancel" outcome
1482 // customFinishHandler: Function - custom finish handler for the editing session (invoked on keydown)
1483 // pasteHandler: Function - handles the "paste" event, return values are the same as those for customFinishHandler
1484 // multiline: Boolean - whether the edited element is multiline
1485 WebInspector.startEditing = function(element, config)
1486 {
1487     if (!WebInspector.markBeingEdited(element, true))
1488         return;
1489
1490     config = config || {};
1491     var committedCallback = config.commitHandler;
1492     var cancelledCallback = config.cancelHandler;
1493     var pasteCallback = config.pasteHandler;
1494     var context = config.context;
1495     var oldText = getContent(element);
1496     var moveDirection = "";
1497
1498     element.addStyleClass("editing");
1499
1500     var oldTabIndex = element.tabIndex;
1501     if (element.tabIndex < 0)
1502         element.tabIndex = 0;
1503
1504     function blurEventListener() {
1505         editingCommitted.call(element);
1506     }
1507
1508     function getContent(element) {
1509         if (element.tagName === "INPUT" && element.type === "text")
1510             return element.value;
1511         else
1512             return element.textContent;
1513     }
1514
1515     function cleanUpAfterEditing() {
1516         WebInspector.markBeingEdited(element, false);
1517
1518         this.removeStyleClass("editing");
1519         this.tabIndex = oldTabIndex;
1520         this.scrollTop = 0;
1521         this.scrollLeft = 0;
1522
1523         element.removeEventListener("blur", blurEventListener, false);
1524         element.removeEventListener("keydown", keyDownEventListener, true);
1525         if (pasteCallback)
1526             element.removeEventListener("paste", pasteEventListener, true);
1527
1528         if (element === WebInspector.currentFocusElement || element.isAncestor(WebInspector.currentFocusElement))
1529             WebInspector.currentFocusElement = WebInspector.previousFocusElement;
1530     }
1531
1532     function editingCancelled() {
1533         if (this.tagName === "INPUT" && this.type === "text")
1534             this.value = oldText;
1535         else
1536             this.textContent = oldText;
1537
1538         cleanUpAfterEditing.call(this);
1539
1540         if (cancelledCallback)
1541             cancelledCallback(this, context);
1542     }
1543
1544     function editingCommitted() {
1545         cleanUpAfterEditing.call(this);
1546
1547         if (committedCallback)
1548             committedCallback(this, getContent(this), oldText, context, moveDirection);
1549     }
1550
1551     function defaultFinishHandler(event)
1552     {
1553         var isMetaOrCtrl = WebInspector.isMac() ?
1554             event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey :
1555             event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey;
1556         if (isEnterKey(event) && (event.isMetaOrCtrlForTest || !config.multiline || isMetaOrCtrl))
1557             return "commit";
1558         else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code || event.keyIdentifier === "U+001B")
1559             return "cancel";
1560         else if (event.keyIdentifier === "U+0009") // Tab key
1561             return "move-" + (event.shiftKey ? "backward" : "forward");
1562     }
1563
1564     function handleEditingResult(result, event)
1565     {
1566         if (result === "commit") {
1567             editingCommitted.call(element);
1568             event.preventDefault();
1569             event.stopPropagation();
1570         } else if (result === "cancel") {
1571             editingCancelled.call(element);
1572             event.preventDefault();
1573             event.stopPropagation();
1574         } else if (result && result.indexOf("move-") === 0) {
1575             moveDirection = result.substring(5);
1576             if (event.keyIdentifier !== "U+0009")
1577                 blurEventListener();
1578         }
1579     }
1580
1581     function pasteEventListener(event)
1582     {
1583         var result = pasteCallback(event);
1584         handleEditingResult(result, event);
1585     }
1586
1587     function keyDownEventListener(event)
1588     {
1589         var handler = config.customFinishHandler || defaultFinishHandler;
1590         var result = handler(event);
1591         handleEditingResult(result, event);
1592     }
1593
1594     element.addEventListener("blur", blurEventListener, false);
1595     element.addEventListener("keydown", keyDownEventListener, true);
1596     if (pasteCallback)
1597         element.addEventListener("paste", pasteEventListener, true);
1598
1599     WebInspector.currentFocusElement = element;
1600     return {
1601         cancel: editingCancelled.bind(element),
1602         commit: editingCommitted.bind(element)
1603     };
1604 }
1605
1606 WebInspector._toolbarItemClicked = function(event)
1607 {
1608     var toolbarItem = event.currentTarget;
1609     this.setCurrentPanel(toolbarItem.panel);
1610 }
1611
1612 WebInspector.PanelHistory = function()
1613 {
1614     this._history = [];
1615     this._historyIterator = -1;
1616 }
1617
1618 WebInspector.PanelHistory.prototype = {
1619     canGoBack: function()
1620     {
1621         return this._historyIterator > 0;
1622     },
1623
1624     goBack: function()
1625     {
1626         this._inHistory = true;
1627         WebInspector.setCurrentPanel(WebInspector.panels[this._history[--this._historyIterator]]);
1628         delete this._inHistory;
1629     },
1630
1631     canGoForward: function()
1632     {
1633         return this._historyIterator < this._history.length - 1;
1634     },
1635
1636     goForward: function()
1637     {
1638         this._inHistory = true;
1639         WebInspector.setCurrentPanel(WebInspector.panels[this._history[++this._historyIterator]]);
1640         delete this._inHistory;
1641     },
1642
1643     setPanel: function(panelName)
1644     {
1645         if (this._inHistory)
1646             return;
1647
1648         this._history.splice(this._historyIterator + 1, this._history.length - this._historyIterator - 1);
1649         if (!this._history.length || this._history[this._history.length - 1] !== panelName)
1650             this._history.push(panelName);
1651         this._historyIterator = this._history.length - 1;
1652     }
1653 }
1654
1655 Number.secondsToString = function(seconds, higherResolution)
1656 {
1657     if (seconds === 0)
1658         return "0";
1659
1660     var ms = seconds * 1000;
1661     if (higherResolution && ms < 1000)
1662         return WebInspector.UIString("%.3fms", ms);
1663     else if (ms < 1000)
1664         return WebInspector.UIString("%.0fms", ms);
1665
1666     if (seconds < 60)
1667         return WebInspector.UIString("%.2fs", seconds);
1668
1669     var minutes = seconds / 60;
1670     if (minutes < 60)
1671         return WebInspector.UIString("%.1fmin", minutes);
1672
1673     var hours = minutes / 60;
1674     if (hours < 24)
1675         return WebInspector.UIString("%.1fhrs", hours);
1676
1677     var days = hours / 24;
1678     return WebInspector.UIString("%.1f days", days);
1679 }
1680
1681 Number.bytesToString = function(bytes, higherResolution)
1682 {
1683     if (typeof higherResolution === "undefined")
1684         higherResolution = true;
1685
1686     if (bytes < 1024)
1687         return WebInspector.UIString("%.0fB", bytes);
1688
1689     var kilobytes = bytes / 1024;
1690     if (higherResolution && kilobytes < 1024)
1691         return WebInspector.UIString("%.2fKB", kilobytes);
1692     else if (kilobytes < 1024)
1693         return WebInspector.UIString("%.0fKB", kilobytes);
1694
1695     var megabytes = kilobytes / 1024;
1696     if (higherResolution)
1697         return WebInspector.UIString("%.2fMB", megabytes);
1698     else
1699         return WebInspector.UIString("%.0fMB", megabytes);
1700 }