2011-04-06 Andrey Kosyakov <caseq@chromium.org>
[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.mainResourceDOMContentTime = time;
993 }
994
995 WebInspector.loadEventFired = function(time)
996 {
997     this.panels.audits.mainResourceLoadTime = time;
998     this.panels.network.mainResourceLoadTime = time;
999     this.panels.resources.loadEventFired();
1000     this.mainResourceLoadTime = time;
1001 }
1002
1003 WebInspector.searchingForNodeWasEnabled = function()
1004 {
1005     this.panels.elements.searchingForNodeWasEnabled();
1006 }
1007
1008 WebInspector.searchingForNodeWasDisabled = function()
1009 {
1010     this.panels.elements.searchingForNodeWasDisabled();
1011 }
1012
1013 WebInspector.reset = function()
1014 {
1015     this.debuggerModel.reset();
1016
1017     for (var panelName in this.panels) {
1018         var panel = this.panels[panelName];
1019         if ("reset" in panel)
1020             panel.reset();
1021     }
1022
1023     this.resources = {};
1024     this.highlightDOMNode(0);
1025
1026     this.console.clearMessages();
1027     this.extensionServer.notifyInspectorReset();
1028 }
1029
1030 WebInspector.bringToFront = function()
1031 {
1032     InspectorFrontendHost.bringToFront();
1033 }
1034
1035 WebInspector.inspectedURLChanged = function(url)
1036 {
1037     InspectorFrontendHost.inspectedURLChanged(url);
1038     this.settings.inspectedURLChanged(url);
1039     this.extensionServer.notifyInspectedURLChanged(url);
1040 }
1041
1042 WebInspector.didCreateWorker = function()
1043 {
1044     var workersPane = WebInspector.panels.scripts.sidebarPanes.workers;
1045     workersPane.addWorker.apply(workersPane, arguments);
1046 }
1047
1048 WebInspector.didDestroyWorker = function()
1049 {
1050     var workersPane = WebInspector.panels.scripts.sidebarPanes.workers;
1051     workersPane.removeWorker.apply(workersPane, arguments);
1052 }
1053
1054 WebInspector.log = function(message, messageLevel)
1055 {
1056     // remember 'this' for setInterval() callback
1057     var self = this;
1058
1059     // return indication if we can actually log a message
1060     function isLogAvailable()
1061     {
1062         return WebInspector.ConsoleMessage && WebInspector.RemoteObject && self.console;
1063     }
1064
1065     // flush the queue of pending messages
1066     function flushQueue()
1067     {
1068         var queued = WebInspector.log.queued;
1069         if (!queued)
1070             return;
1071
1072         for (var i = 0; i < queued.length; ++i)
1073             logMessage(queued[i]);
1074
1075         delete WebInspector.log.queued;
1076     }
1077
1078     // flush the queue if it console is available
1079     // - this function is run on an interval
1080     function flushQueueIfAvailable()
1081     {
1082         if (!isLogAvailable())
1083             return;
1084
1085         clearInterval(WebInspector.log.interval);
1086         delete WebInspector.log.interval;
1087
1088         flushQueue();
1089     }
1090
1091     // actually log the message
1092     function logMessage(message)
1093     {
1094         var repeatCount = 1;
1095         if (message == WebInspector.log.lastMessage)
1096             repeatCount = WebInspector.log.repeatCount + 1;
1097
1098         WebInspector.log.lastMessage = message;
1099         WebInspector.log.repeatCount = repeatCount;
1100
1101         // ConsoleMessage expects a proxy object
1102         message = new WebInspector.RemoteObject.fromPrimitiveValue(message);
1103
1104         // post the message
1105         var msg = new WebInspector.ConsoleMessage(
1106             WebInspector.ConsoleMessage.MessageSource.Other,
1107             WebInspector.ConsoleMessage.MessageType.Log,
1108             messageLevel || WebInspector.ConsoleMessage.MessageLevel.Debug,
1109             -1,
1110             null,
1111             repeatCount,
1112             null,
1113             [message],
1114             null);
1115
1116         self.console.addMessage(msg);
1117     }
1118
1119     // if we can't log the message, queue it
1120     if (!isLogAvailable()) {
1121         if (!WebInspector.log.queued)
1122             WebInspector.log.queued = [];
1123
1124         WebInspector.log.queued.push(message);
1125
1126         if (!WebInspector.log.interval)
1127             WebInspector.log.interval = setInterval(flushQueueIfAvailable, 1000);
1128
1129         return;
1130     }
1131
1132     // flush the pending queue if any
1133     flushQueue();
1134
1135     // log the message
1136     logMessage(message);
1137 }
1138
1139 WebInspector.drawLoadingPieChart = function(canvas, percent) {
1140     var g = canvas.getContext("2d");
1141     var darkColor = "rgb(122, 168, 218)";
1142     var lightColor = "rgb(228, 241, 251)";
1143     var cx = 8;
1144     var cy = 8;
1145     var r = 7;
1146
1147     g.beginPath();
1148     g.arc(cx, cy, r, 0, Math.PI * 2, false);
1149     g.closePath();
1150
1151     g.lineWidth = 1;
1152     g.strokeStyle = darkColor;
1153     g.fillStyle = lightColor;
1154     g.fill();
1155     g.stroke();
1156
1157     var startangle = -Math.PI / 2;
1158     var endangle = startangle + (percent * Math.PI * 2);
1159
1160     g.beginPath();
1161     g.moveTo(cx, cy);
1162     g.arc(cx, cy, r, startangle, endangle, false);
1163     g.closePath();
1164
1165     g.fillStyle = darkColor;
1166     g.fill();
1167 }
1168
1169 WebInspector.inspect = function(payload, hints)
1170 {
1171     var object = WebInspector.RemoteObject.fromPayload(payload);
1172     if (object.type === "node") {
1173         // Request node from backend and focus it.
1174         object.pushNodeToFrontend(WebInspector.updateFocusedNode.bind(WebInspector), object.release.bind(object));
1175         return;
1176     }
1177
1178     if (hints.databaseId) {
1179         WebInspector.currentPanel = WebInspector.panels.resources;
1180         WebInspector.panels.resources.selectDatabase(hints.databaseId);
1181     } else if (hints.domStorageId) {
1182         WebInspector.currentPanel = WebInspector.panels.resources;
1183         WebInspector.panels.resources.selectDOMStorage(hints.domStorageId);
1184     }
1185
1186     object.release();
1187 }
1188
1189 WebInspector.updateFocusedNode = function(nodeId)
1190 {
1191     this._updateFocusedNode(nodeId);
1192     this.highlightDOMNodeForTwoSeconds(nodeId);
1193 }
1194
1195 WebInspector.displayNameForURL = function(url)
1196 {
1197     if (!url)
1198         return "";
1199
1200     var resource = this.resourceForURL(url);
1201     if (resource)
1202         return resource.displayName;
1203
1204     if (!WebInspector.mainResource)
1205         return url.trimURL("");
1206
1207     var lastPathComponent = WebInspector.mainResource.lastPathComponent;
1208     var index = WebInspector.mainResource.url.indexOf(lastPathComponent);
1209     if (index !== -1 && index + lastPathComponent.length === WebInspector.mainResource.url.length) {
1210         var baseURL = WebInspector.mainResource.url.substring(0, index);
1211         if (url.indexOf(baseURL) === 0)
1212             return url.substring(index);
1213     }
1214
1215     return url.trimURL(WebInspector.mainResource.domain);
1216 }
1217
1218 WebInspector._showAnchorLocation = function(anchor)
1219 {
1220     var preferedPanel = this.panels[anchor.getAttribute("preferred_panel") || "resources"];
1221     if (WebInspector._showAnchorLocationInPanel(anchor, preferedPanel))
1222         return true;
1223     if (preferedPanel !== this.panels.resources && WebInspector._showAnchorLocationInPanel(anchor, this.panels.resources))
1224         return true;
1225     return false;
1226 }
1227
1228 WebInspector._showAnchorLocationInPanel = function(anchor, panel)
1229 {
1230     if (!panel.canShowAnchorLocation(anchor))
1231         return false;
1232
1233     // FIXME: support webkit-html-external-link links here.
1234     if (anchor.hasStyleClass("webkit-html-external-link")) {
1235         anchor.removeStyleClass("webkit-html-external-link");
1236         anchor.addStyleClass("webkit-html-resource-link");
1237     }
1238
1239     this.currentPanel = panel;
1240     if (this.drawer)
1241         this.drawer.immediatelyFinishAnimation();
1242     this.currentPanel.showAnchorLocation(anchor);
1243     return true;
1244 }
1245
1246 WebInspector.linkifyStringAsFragment = function(string)
1247 {
1248     var container = document.createDocumentFragment();
1249     var linkStringRegEx = /(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\/\/|www\.)[\w$\-_+*'=\|\/\\(){}[\]%@&#~,:;.!?]{2,}[\w$\-_+*=\|\/\\({%@&#~]/;
1250     var lineColumnRegEx = /:(\d+)(:(\d+))?$/;
1251
1252     while (string) {
1253         var linkString = linkStringRegEx.exec(string);
1254         if (!linkString)
1255             break;
1256
1257         linkString = linkString[0];
1258         var title = linkString;
1259         var linkIndex = string.indexOf(linkString);
1260         var nonLink = string.substring(0, linkIndex);
1261         container.appendChild(document.createTextNode(nonLink));
1262
1263         var profileStringMatches = WebInspector.ProfileType.URLRegExp.exec(title);
1264         if (profileStringMatches)
1265             title = WebInspector.panels.profiles.displayTitleForProfileLink(profileStringMatches[2], profileStringMatches[1]);
1266
1267         var realURL = (linkString.indexOf("www.") === 0 ? "http://" + linkString : linkString);
1268         var lineColumnMatch = lineColumnRegEx.exec(realURL);
1269         if (lineColumnMatch)
1270             realURL = realURL.substring(0, realURL.length - lineColumnMatch[0].length);
1271
1272         var hasResourceWithURL = !!WebInspector.resourceForURL(realURL);
1273         var urlNode = WebInspector.linkifyURLAsNode(realURL, title, null, hasResourceWithURL);
1274         container.appendChild(urlNode);
1275         if (lineColumnMatch) {
1276             urlNode.setAttribute("line_number", lineColumnMatch[1]);
1277             urlNode.setAttribute("preferred_panel", "scripts");
1278         }
1279         string = string.substring(linkIndex + linkString.length, string.length);
1280     }
1281
1282     if (string)
1283         container.appendChild(document.createTextNode(string));
1284
1285     return container;
1286 }
1287
1288 WebInspector.showProfileForURL = function(url)
1289 {
1290     WebInspector.showPanel("profiles");
1291     WebInspector.panels.profiles.showProfileForURL(url);
1292 }
1293
1294 WebInspector.linkifyURLAsNode = function(url, linkText, classes, isExternal, tooltipText)
1295 {
1296     if (!linkText)
1297         linkText = url;
1298     classes = (classes ? classes + " " : "");
1299     classes += isExternal ? "webkit-html-external-link" : "webkit-html-resource-link";
1300
1301     var a = document.createElement("a");
1302     a.href = url;
1303     a.className = classes;
1304     if (typeof tooltipText === "undefined")
1305         a.title = url;
1306     else if (typeof tooltipText !== "string" || tooltipText.length)
1307         a.title = tooltipText;
1308     a.textContent = linkText;
1309
1310     return a;
1311 }
1312
1313 WebInspector.linkifyURL = function(url, linkText, classes, isExternal, tooltipText)
1314 {
1315     // Use the DOM version of this function so as to avoid needing to escape attributes.
1316     // FIXME:  Get rid of linkifyURL entirely.
1317     return WebInspector.linkifyURLAsNode(url, linkText, classes, isExternal, tooltipText).outerHTML;
1318 }
1319
1320 WebInspector.linkifyResourceAsNode = function(url, preferredPanel, lineNumber, classes, tooltipText)
1321 {
1322     var linkText = WebInspector.displayNameForURL(url);
1323     if (lineNumber)
1324         linkText += ":" + lineNumber;
1325     var node = WebInspector.linkifyURLAsNode(url, linkText, classes, false, tooltipText);
1326     node.setAttribute("line_number", lineNumber);
1327     node.setAttribute("preferred_panel", preferredPanel);
1328     return node;
1329 }
1330
1331 WebInspector.resourceURLForRelatedNode = function(node, url)
1332 {
1333     if (!url || url.indexOf("://") > 0)
1334         return url;
1335
1336     for (var frameOwnerCandidate = node; frameOwnerCandidate; frameOwnerCandidate = frameOwnerCandidate.parentNode) {
1337         if (frameOwnerCandidate.documentURL) {
1338             var result = WebInspector.completeURL(frameOwnerCandidate.documentURL, url);
1339             if (result)
1340                 return result;
1341             break;
1342         }
1343     }
1344
1345     // documentURL not found or has bad value
1346     var resourceURL = url;
1347     function callback(resource)
1348     {
1349         if (resource.path === url) {
1350             resourceURL = resource.url;
1351             return true;
1352         }
1353     }
1354     WebInspector.forAllResources(callback);
1355     return resourceURL;
1356 }
1357
1358 WebInspector.completeURL = function(baseURL, href)
1359 {
1360     if (href) {
1361         // Return absolute URLs as-is.
1362         var parsedHref = href.asParsedURL();
1363         if (parsedHref && parsedHref.scheme)
1364             return href;
1365     }
1366
1367     var parsedURL = baseURL.asParsedURL();
1368     if (parsedURL) {
1369         var path = href;
1370         if (path.charAt(0) !== "/") {
1371             var basePath = parsedURL.path;
1372             // A href of "?foo=bar" implies "basePath?foo=bar".
1373             // With "basePath?a=b" and "?foo=bar" we should get "basePath?foo=bar".
1374             var prefix;
1375             if (path.charAt(0) === "?") {
1376                 var basePathCutIndex = basePath.indexOf("?");
1377                 if (basePathCutIndex !== -1)
1378                     prefix = basePath.substring(0, basePathCutIndex);
1379                 else
1380                     prefix = basePath;
1381             } else
1382                 prefix = basePath.substring(0, basePath.lastIndexOf("/")) + "/";
1383
1384             path = prefix + path;
1385         } else if (path.length > 1 && path.charAt(1) === "/") {
1386             // href starts with "//" which is a full URL with the protocol dropped (use the baseURL protocol).
1387             return parsedURL.scheme + ":" + path;
1388         }
1389         return parsedURL.scheme + "://" + parsedURL.host + (parsedURL.port ? (":" + parsedURL.port) : "") + path;
1390     }
1391     return null;
1392 }
1393
1394 WebInspector.addMainEventListeners = function(doc)
1395 {
1396     doc.defaultView.addEventListener("focus", this.windowFocused.bind(this), false);
1397     doc.defaultView.addEventListener("blur", this.windowBlurred.bind(this), false);
1398     doc.addEventListener("click", this.documentClick.bind(this), true);
1399 }
1400
1401 WebInspector.frontendReused = function()
1402 {
1403     this.networkManager.frontendReused();
1404     this.resourceTreeModel.frontendReused();
1405     WebInspector.panels.network.clear();
1406     this.reset();
1407 }
1408
1409 WebInspector.UIString = function(string)
1410 {
1411     if (window.localizedStrings && string in window.localizedStrings)
1412         string = window.localizedStrings[string];
1413     else {
1414         if (!(string in WebInspector.missingLocalizedStrings)) {
1415             if (!WebInspector.InspectorBackendStub)
1416                 console.warn("Localized string \"" + string + "\" not found.");
1417             WebInspector.missingLocalizedStrings[string] = true;
1418         }
1419
1420         if (Preferences.showMissingLocalizedStrings)
1421             string += " (not localized)";
1422     }
1423
1424     return String.vsprintf(string, Array.prototype.slice.call(arguments, 1));
1425 }
1426
1427 WebInspector.formatLocalized = function(format, substitutions, formatters, initialValue, append)
1428 {
1429     return String.format(WebInspector.UIString(format), substitutions, formatters, initialValue, append);
1430 }
1431
1432 WebInspector.isMac = function()
1433 {
1434     if (!("_isMac" in this))
1435         this._isMac = WebInspector.platform === "mac";
1436
1437     return this._isMac;
1438 }
1439
1440 WebInspector.isBeingEdited = function(element)
1441 {
1442     return element.__editing;
1443 }
1444
1445 WebInspector.isEditingAnyField = function()
1446 {
1447     return this.__editing;
1448 }
1449
1450 // Available config fields (all optional):
1451 // context: Object - an arbitrary context object to be passed to the commit and cancel handlers
1452 // commitHandler: Function - handles editing "commit" outcome
1453 // cancelHandler: Function - handles editing "cancel" outcome
1454 // customFinishHandler: Function - custom finish handler for the editing session (invoked on keydown)
1455 // pasteHandler: Function - handles the "paste" event, return values are the same as those for customFinishHandler
1456 // multiline: Boolean - whether the edited element is multiline
1457 WebInspector.startEditing = function(element, config)
1458 {
1459     if (element.__editing)
1460         return;
1461     element.__editing = true;
1462     WebInspector.__editing = true;
1463
1464     config = config || {};
1465     var committedCallback = config.commitHandler;
1466     var cancelledCallback = config.cancelHandler;
1467     var pasteCallback = config.pasteHandler;
1468     var context = config.context;
1469     var oldText = getContent(element);
1470     var moveDirection = "";
1471
1472     element.addStyleClass("editing");
1473
1474     var oldTabIndex = element.tabIndex;
1475     if (element.tabIndex < 0)
1476         element.tabIndex = 0;
1477
1478     function blurEventListener() {
1479         editingCommitted.call(element);
1480     }
1481
1482     function getContent(element) {
1483         if (element.tagName === "INPUT" && element.type === "text")
1484             return element.value;
1485         else
1486             return element.textContent;
1487     }
1488
1489     function cleanUpAfterEditing() {
1490         delete this.__editing;
1491         delete WebInspector.__editing;
1492
1493         this.removeStyleClass("editing");
1494         this.tabIndex = oldTabIndex;
1495         this.scrollTop = 0;
1496         this.scrollLeft = 0;
1497
1498         element.removeEventListener("blur", blurEventListener, false);
1499         element.removeEventListener("keydown", keyDownEventListener, true);
1500         if (pasteCallback)
1501             element.removeEventListener("paste", pasteEventListener, true);
1502
1503         if (element === WebInspector.currentFocusElement || element.isAncestor(WebInspector.currentFocusElement))
1504             WebInspector.currentFocusElement = WebInspector.previousFocusElement;
1505     }
1506
1507     function editingCancelled() {
1508         if (this.tagName === "INPUT" && this.type === "text")
1509             this.value = oldText;
1510         else
1511             this.textContent = oldText;
1512
1513         cleanUpAfterEditing.call(this);
1514
1515         if (cancelledCallback)
1516             cancelledCallback(this, context);
1517     }
1518
1519     function editingCommitted() {
1520         cleanUpAfterEditing.call(this);
1521
1522         if (committedCallback)
1523             committedCallback(this, getContent(this), oldText, context, moveDirection);
1524     }
1525
1526     function defaultFinishHandler(event)
1527     {
1528         var isMetaOrCtrl = WebInspector.isMac() ?
1529             event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey :
1530             event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey;
1531         if (isEnterKey(event) && (event.isMetaOrCtrlForTest || !config.multiline || isMetaOrCtrl))
1532             return "commit";
1533         else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code)
1534             return "cancel";
1535         else if (event.keyIdentifier === "U+0009") // Tab key
1536             return "move-" + (event.shiftKey ? "backward" : "forward");
1537     }
1538
1539     function handleEditingResult(result, event)
1540     {
1541         if (result === "commit") {
1542             editingCommitted.call(element);
1543             event.preventDefault();
1544             event.stopPropagation();
1545         } else if (result === "cancel") {
1546             editingCancelled.call(element);
1547             event.preventDefault();
1548             event.stopPropagation();
1549         } else if (result && result.indexOf("move-") === 0) {
1550             moveDirection = result.substring(5);
1551             if (event.keyIdentifier !== "U+0009")
1552                 blurEventListener();
1553         }
1554     }
1555
1556     function pasteEventListener(event)
1557     {
1558         var result = pasteCallback(event);
1559         handleEditingResult(result, event);
1560     }
1561
1562     function keyDownEventListener(event)
1563     {
1564         var handler = config.customFinishHandler || defaultFinishHandler;
1565         var result = handler(event);
1566         handleEditingResult(result, event);
1567     }
1568
1569     element.addEventListener("blur", blurEventListener, false);
1570     element.addEventListener("keydown", keyDownEventListener, true);
1571     if (pasteCallback)
1572         element.addEventListener("paste", pasteEventListener, true);
1573
1574     WebInspector.currentFocusElement = element;
1575     return {
1576         cancel: editingCancelled.bind(element),
1577         commit: editingCommitted.bind(element)
1578     };
1579 }
1580
1581 WebInspector._toolbarItemClicked = function(event)
1582 {
1583     var toolbarItem = event.currentTarget;
1584     this.currentPanel = toolbarItem.panel;
1585 }
1586
1587 // This table maps MIME types to the Resource.Types which are valid for them.
1588 // The following line:
1589 //    "text/html":                {0: 1},
1590 // means that text/html is a valid MIME type for resources that have type
1591 // WebInspector.Resource.Type.Document (which has a value of 0).
1592 WebInspector.MIMETypes = {
1593     "text/html":                   {0: true},
1594     "text/xml":                    {0: true},
1595     "text/plain":                  {0: true},
1596     "application/xhtml+xml":       {0: true},
1597     "text/css":                    {1: true},
1598     "text/xsl":                    {1: true},
1599     "image/jpeg":                  {2: true},
1600     "image/png":                   {2: true},
1601     "image/gif":                   {2: true},
1602     "image/bmp":                   {2: true},
1603     "image/vnd.microsoft.icon":    {2: true},
1604     "image/x-icon":                {2: true},
1605     "image/x-xbitmap":             {2: true},
1606     "font/ttf":                    {3: true},
1607     "font/opentype":               {3: true},
1608     "application/x-font-type1":    {3: true},
1609     "application/x-font-ttf":      {3: true},
1610     "application/x-font-woff":     {3: true},
1611     "application/x-truetype-font": {3: true},
1612     "text/javascript":             {4: true},
1613     "text/ecmascript":             {4: true},
1614     "application/javascript":      {4: true},
1615     "application/ecmascript":      {4: true},
1616     "application/x-javascript":    {4: true},
1617     "text/javascript1.1":          {4: true},
1618     "text/javascript1.2":          {4: true},
1619     "text/javascript1.3":          {4: true},
1620     "text/jscript":                {4: true},
1621     "text/livescript":             {4: true},
1622 }
1623
1624 WebInspector.PanelHistory = function()
1625 {
1626     this._history = [];
1627     this._historyIterator = -1;
1628 }
1629
1630 WebInspector.PanelHistory.prototype = {
1631     canGoBack: function()
1632     {
1633         return this._historyIterator > 0;
1634     },
1635
1636     goBack: function()
1637     {
1638         this._inHistory = true;
1639         WebInspector.currentPanel = WebInspector.panels[this._history[--this._historyIterator]];
1640         delete this._inHistory;
1641     },
1642
1643     canGoForward: function()
1644     {
1645         return this._historyIterator < this._history.length - 1;
1646     },
1647
1648     goForward: function()
1649     {
1650         this._inHistory = true;
1651         WebInspector.currentPanel = WebInspector.panels[this._history[++this._historyIterator]];
1652         delete this._inHistory;
1653     },
1654
1655     setPanel: function(panelName)
1656     {
1657         if (this._inHistory)
1658             return;
1659
1660         this._history.splice(this._historyIterator + 1, this._history.length - this._historyIterator - 1);
1661         if (!this._history.length || this._history[this._history.length - 1] !== panelName)
1662             this._history.push(panelName);
1663         this._historyIterator = this._history.length - 1;
1664     }
1665 }