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