inspector.js revision 65f03d4f644ce73618e5f4f50dd694b26f55ae12
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
51var 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    get currentPanel()
142    {
143        return this._currentPanel;
144    },
145
146    set currentPanel(x)
147    {
148        if (this._currentPanel === x)
149            return;
150
151        if (this._currentPanel)
152            this._currentPanel.hide();
153
154        this._currentPanel = x;
155
156        this.updateSearchLabel();
157
158        if (x) {
159            x.show();
160
161            if (this.currentQuery) {
162                if (x.performSearch) {
163                    function performPanelSearch()
164                    {
165                        this.updateSearchMatchesCount();
166
167                        x.currentQuery = this.currentQuery;
168                        x.performSearch(this.currentQuery);
169                    }
170
171                    // Perform the search on a timeout so the panel switches fast.
172                    setTimeout(performPanelSearch.bind(this), 0);
173                } else {
174                    // Update to show Not found for panels that can't be searched.
175                    this.updateSearchMatchesCount();
176                }
177            }
178        }
179
180        for (var panelName in WebInspector.panels) {
181            if (WebInspector.panels[panelName] === x) {
182                WebInspector.settings.lastActivePanel = panelName;
183                this._panelHistory.setPanel(panelName);
184            }
185        }
186    },
187
188    createJSBreakpointsSidebarPane: function()
189    {
190        var pane = new WebInspector.BreakpointsSidebarPane(WebInspector.UIString("Breakpoints"));
191        function breakpointAdded(event)
192        {
193            pane.addBreakpointItem(new WebInspector.BreakpointItem(event.data));
194        }
195        WebInspector.debuggerModel.addEventListener(WebInspector.DebuggerModel.Events.BreakpointAdded, breakpointAdded);
196        return pane;
197    },
198
199    createDOMBreakpointsSidebarPane: function()
200    {
201        var pane = new WebInspector.BreakpointsSidebarPane(WebInspector.UIString("DOM Breakpoints"));
202        function breakpointAdded(event)
203        {
204            pane.addBreakpointItem(new WebInspector.BreakpointItem(event.data));
205        }
206        WebInspector.breakpointManager.addEventListener(WebInspector.BreakpointManager.Events.DOMBreakpointAdded, breakpointAdded);
207        return pane;
208    },
209
210    createXHRBreakpointsSidebarPane: function()
211    {
212        var pane = new WebInspector.XHRBreakpointsSidebarPane();
213        function breakpointAdded(event)
214        {
215            pane.addBreakpointItem(new WebInspector.BreakpointItem(event.data));
216        }
217        WebInspector.breakpointManager.addEventListener(WebInspector.BreakpointManager.Events.XHRBreakpointAdded, breakpointAdded);
218        return pane;
219    },
220
221    _createPanels: function()
222    {
223        var hiddenPanels = (InspectorFrontendHost.hiddenPanels() || "").split(',');
224        if (hiddenPanels.indexOf("elements") === -1)
225            this.panels.elements = new WebInspector.ElementsPanel();
226        if (hiddenPanels.indexOf("resources") === -1)
227            this.panels.resources = new WebInspector.ResourcesPanel();
228        if (hiddenPanels.indexOf("network") === -1)
229            this.panels.network = new WebInspector.NetworkPanel();
230        if (hiddenPanels.indexOf("scripts") === -1)
231            this.panels.scripts = new WebInspector.ScriptsPanel();
232        if (hiddenPanels.indexOf("timeline") === -1)
233            this.panels.timeline = new WebInspector.TimelinePanel();
234        if (hiddenPanels.indexOf("profiles") === -1) {
235            this.panels.profiles = new WebInspector.ProfilesPanel();
236            this.panels.profiles.registerProfileType(new WebInspector.CPUProfileType());
237            if (Preferences.heapProfilerPresent)
238                this.panels.profiles.registerProfileType(new WebInspector.HeapSnapshotProfileType());
239        }
240        if (hiddenPanels.indexOf("audits") === -1)
241            this.panels.audits = new WebInspector.AuditsPanel();
242        if (hiddenPanels.indexOf("console") === -1)
243            this.panels.console = new WebInspector.ConsolePanel();
244    },
245
246    get attached()
247    {
248        return this._attached;
249    },
250
251    set attached(x)
252    {
253        if (this._attached === x)
254            return;
255
256        this._attached = x;
257
258        this.updateSearchLabel();
259
260        var dockToggleButton = document.getElementById("dock-status-bar-item");
261        var body = document.body;
262
263        if (x) {
264            body.removeStyleClass("detached");
265            body.addStyleClass("attached");
266            dockToggleButton.title = WebInspector.UIString("Undock into separate window.");
267        } else {
268            body.removeStyleClass("attached");
269            body.addStyleClass("detached");
270            dockToggleButton.title = WebInspector.UIString("Dock to main window.");
271        }
272        if (this.drawer)
273            this.drawer.resize();
274    },
275
276    get errors()
277    {
278        return this._errors || 0;
279    },
280
281    set errors(x)
282    {
283        x = Math.max(x, 0);
284
285        if (this._errors === x)
286            return;
287        this._errors = x;
288        this._updateErrorAndWarningCounts();
289    },
290
291    get warnings()
292    {
293        return this._warnings || 0;
294    },
295
296    set warnings(x)
297    {
298        x = Math.max(x, 0);
299
300        if (this._warnings === x)
301            return;
302        this._warnings = x;
303        this._updateErrorAndWarningCounts();
304    },
305
306    _updateErrorAndWarningCounts: function()
307    {
308        var errorWarningElement = document.getElementById("error-warning-count");
309        if (!errorWarningElement)
310            return;
311
312        if (!this.errors && !this.warnings) {
313            errorWarningElement.addStyleClass("hidden");
314            return;
315        }
316
317        errorWarningElement.removeStyleClass("hidden");
318
319        errorWarningElement.removeChildren();
320
321        if (this.errors) {
322            var errorElement = document.createElement("span");
323            errorElement.id = "error-count";
324            errorElement.textContent = this.errors;
325            errorWarningElement.appendChild(errorElement);
326        }
327
328        if (this.warnings) {
329            var warningsElement = document.createElement("span");
330            warningsElement.id = "warning-count";
331            warningsElement.textContent = this.warnings;
332            errorWarningElement.appendChild(warningsElement);
333        }
334
335        if (this.errors) {
336            if (this.warnings) {
337                if (this.errors == 1) {
338                    if (this.warnings == 1)
339                        errorWarningElement.title = WebInspector.UIString("%d error, %d warning", this.errors, this.warnings);
340                    else
341                        errorWarningElement.title = WebInspector.UIString("%d error, %d warnings", this.errors, this.warnings);
342                } else if (this.warnings == 1)
343                    errorWarningElement.title = WebInspector.UIString("%d errors, %d warning", this.errors, this.warnings);
344                else
345                    errorWarningElement.title = WebInspector.UIString("%d errors, %d warnings", this.errors, this.warnings);
346            } else if (this.errors == 1)
347                errorWarningElement.title = WebInspector.UIString("%d error", this.errors);
348            else
349                errorWarningElement.title = WebInspector.UIString("%d errors", this.errors);
350        } else if (this.warnings == 1)
351            errorWarningElement.title = WebInspector.UIString("%d warning", this.warnings);
352        else if (this.warnings)
353            errorWarningElement.title = WebInspector.UIString("%d warnings", this.warnings);
354        else
355            errorWarningElement.title = null;
356    },
357
358    get styleChanges()
359    {
360        return this._styleChanges;
361    },
362
363    set styleChanges(x)
364    {
365        x = Math.max(x, 0);
366
367        if (this._styleChanges === x)
368            return;
369        this._styleChanges = x;
370        this._updateChangesCount();
371    },
372
373    _updateChangesCount: function()
374    {
375        // TODO: Remove immediate return when enabling the Changes Panel
376        return;
377
378        var changesElement = document.getElementById("changes-count");
379        if (!changesElement)
380            return;
381
382        if (!this.styleChanges) {
383            changesElement.addStyleClass("hidden");
384            return;
385        }
386
387        changesElement.removeStyleClass("hidden");
388        changesElement.removeChildren();
389
390        if (this.styleChanges) {
391            var styleChangesElement = document.createElement("span");
392            styleChangesElement.id = "style-changes-count";
393            styleChangesElement.textContent = this.styleChanges;
394            changesElement.appendChild(styleChangesElement);
395        }
396
397        if (this.styleChanges) {
398            if (this.styleChanges === 1)
399                changesElement.title = WebInspector.UIString("%d style change", this.styleChanges);
400            else
401                changesElement.title = WebInspector.UIString("%d style changes", this.styleChanges);
402        }
403    },
404
405    highlightDOMNode: function(nodeId)
406    {
407        if ("_hideDOMNodeHighlightTimeout" in this) {
408            clearTimeout(this._hideDOMNodeHighlightTimeout);
409            delete this._hideDOMNodeHighlightTimeout;
410        }
411
412        if (this._highlightedDOMNodeId === nodeId)
413            return;
414
415        this._highlightedDOMNodeId = nodeId;
416        if (nodeId)
417            InspectorBackend.highlightDOMNode(nodeId);
418        else
419            InspectorBackend.hideDOMNodeHighlight();
420    },
421
422    highlightDOMNodeForTwoSeconds: function(nodeId)
423    {
424        this.highlightDOMNode(nodeId);
425        this._hideDOMNodeHighlightTimeout = setTimeout(this.highlightDOMNode.bind(this, 0), 2000);
426    },
427
428    wireElementWithDOMNode: function(element, nodeId)
429    {
430        element.addEventListener("click", this._updateFocusedNode.bind(this, nodeId), false);
431        element.addEventListener("mouseover", this.highlightDOMNode.bind(this, nodeId), false);
432        element.addEventListener("mouseout", this.highlightDOMNode.bind(this, 0), false);
433    },
434
435    _updateFocusedNode: function(nodeId)
436    {
437        this.currentPanel = this.panels.elements;
438        this.panels.elements.updateFocusedNode(nodeId);
439    },
440
441    get networkResources()
442    {
443        return this.panels.network.resources;
444    },
445
446    networkResourceById: function(id)
447    {
448        return this.panels.network.resourceById(id);
449    },
450
451    forAllResources: function(callback)
452    {
453        WebInspector.resourceTreeModel.forAllResources(callback);
454    },
455
456    resourceForURL: function(url)
457    {
458        return this.resourceTreeModel.resourceForURL(url);
459    }
460}
461
462WebInspector.PlatformFlavor = {
463    WindowsVista: "windows-vista",
464    MacTiger: "mac-tiger",
465    MacLeopard: "mac-leopard",
466    MacSnowLeopard: "mac-snowleopard"
467};
468
469(function parseQueryParameters()
470{
471    WebInspector.queryParamsObject = {};
472    var queryParams = window.location.search;
473    if (!queryParams)
474        return;
475    var params = queryParams.substring(1).split("&");
476    for (var i = 0; i < params.length; ++i) {
477        var pair = params[i].split("=");
478        WebInspector.queryParamsObject[pair[0]] = pair[1];
479    }
480})();
481
482WebInspector.loaded = function()
483{
484    if ("page" in WebInspector.queryParamsObject) {
485        WebInspector.socket = new WebSocket("ws://" + window.location.host + "/devtools/page/" + WebInspector.queryParamsObject.page);
486        WebInspector.socket.onmessage = function(message) { InspectorBackend.dispatch(message.data); }
487        WebInspector.socket.onerror = function(error) { console.error(error); }
488        WebInspector.socket.onopen = function() {
489            InspectorFrontendHost.sendMessageToBackend = WebInspector.socket.send.bind(WebInspector.socket);
490            InspectorFrontendHost.loaded = WebInspector.socket.send.bind(WebInspector.socket, "loaded");
491            WebInspector.doLoadedDone();
492        }
493        return;
494    }
495    WebInspector.doLoadedDone();
496}
497
498WebInspector.doLoadedDone = function()
499{
500    InspectorFrontendHost.loaded();
501
502    var platform = WebInspector.platform;
503    document.body.addStyleClass("platform-" + platform);
504    var flavor = WebInspector.platformFlavor;
505    if (flavor)
506        document.body.addStyleClass("platform-" + flavor);
507    var port = WebInspector.port;
508    document.body.addStyleClass("port-" + port);
509
510    WebInspector.settings = new WebInspector.Settings();
511
512    this._registerShortcuts();
513
514    // set order of some sections explicitly
515    WebInspector.shortcutsHelp.section(WebInspector.UIString("Console"));
516    WebInspector.shortcutsHelp.section(WebInspector.UIString("Elements Panel"));
517
518    this.drawer = new WebInspector.Drawer();
519    this.console = new WebInspector.ConsoleView(this.drawer);
520    // TODO: Uncomment when enabling the Changes Panel
521    // this.changes = new WebInspector.ChangesView(this.drawer);
522    // TODO: Remove class="hidden" from inspector.html on button#changes-status-bar-item
523    this.drawer.visibleView = this.console;
524    this.resourceTreeModel = new WebInspector.ResourceTreeModel();
525    this.networkManager = new WebInspector.NetworkManager(this.resourceTreeModel);
526    this.domAgent = new WebInspector.DOMAgent();
527
528    InspectorBackend.registerDomainDispatcher("Inspector", this);
529
530    this.resourceCategories = {
531        documents: new WebInspector.ResourceCategory("documents", WebInspector.UIString("Documents"), "rgb(47,102,236)"),
532        stylesheets: new WebInspector.ResourceCategory("stylesheets", WebInspector.UIString("Stylesheets"), "rgb(157,231,119)"),
533        images: new WebInspector.ResourceCategory("images", WebInspector.UIString("Images"), "rgb(164,60,255)"),
534        scripts: new WebInspector.ResourceCategory("scripts", WebInspector.UIString("Scripts"), "rgb(255,121,0)"),
535        xhr: new WebInspector.ResourceCategory("xhr", WebInspector.UIString("XHR"), "rgb(231,231,10)"),
536        fonts: new WebInspector.ResourceCategory("fonts", WebInspector.UIString("Fonts"), "rgb(255,82,62)"),
537        websockets: new WebInspector.ResourceCategory("websockets", WebInspector.UIString("WebSocket"), "rgb(186,186,186)"), // FIXME: Decide the color.
538        other: new WebInspector.ResourceCategory("other", WebInspector.UIString("Other"), "rgb(186,186,186)")
539    };
540
541    this.cssModel = new WebInspector.CSSStyleModel();
542    this.debuggerModel = new WebInspector.DebuggerModel();
543
544    this.breakpointManager = new WebInspector.BreakpointManager();
545
546    this.panels = {};
547    this._createPanels();
548    this._panelHistory = new WebInspector.PanelHistory();
549
550    var toolbarElement = document.getElementById("toolbar");
551    var previousToolbarItem = toolbarElement.children[0];
552
553    this.panelOrder = [];
554    for (var panelName in this.panels)
555        previousToolbarItem = WebInspector.addPanelToolbarIcon(toolbarElement, this.panels[panelName], previousToolbarItem);
556
557    this.Tips = {
558        ResourceNotCompressed: {id: 0, message: WebInspector.UIString("You could save bandwidth by having your web server compress this transfer with gzip or zlib.")}
559    };
560
561    this.Warnings = {
562        IncorrectMIMEType: {id: 0, message: WebInspector.UIString("Resource interpreted as %s but transferred with MIME type %s.")}
563    };
564
565    this.addMainEventListeners(document);
566
567    window.addEventListener("resize", this.windowResize.bind(this), true);
568
569    document.addEventListener("focus", this.focusChanged.bind(this), true);
570    document.addEventListener("keydown", this.documentKeyDown.bind(this), false);
571    document.addEventListener("beforecopy", this.documentCanCopy.bind(this), true);
572    document.addEventListener("copy", this.documentCopy.bind(this), true);
573    document.addEventListener("contextmenu", this.contextMenuEventFired.bind(this), true);
574
575    var dockToggleButton = document.getElementById("dock-status-bar-item");
576    dockToggleButton.addEventListener("click", this.toggleAttach.bind(this), false);
577
578    if (this.attached)
579        dockToggleButton.title = WebInspector.UIString("Undock into separate window.");
580    else
581        dockToggleButton.title = WebInspector.UIString("Dock to main window.");
582
583    var errorWarningCount = document.getElementById("error-warning-count");
584    errorWarningCount.addEventListener("click", this.showConsole.bind(this), false);
585    this._updateErrorAndWarningCounts();
586
587    this.styleChanges = 0;
588    // TODO: Uncomment when enabling the Changes Panel
589    // var changesElement = document.getElementById("changes-count");
590    // changesElement.addEventListener("click", this.showChanges.bind(this), false);
591    // this._updateErrorAndWarningCounts();
592
593    var searchField = document.getElementById("search");
594    searchField.addEventListener("search", this.performSearch.bind(this), false); // when the search is emptied
595    searchField.addEventListener("mousedown", this._searchFieldManualFocus.bind(this), false); // when the search field is manually selected
596    searchField.addEventListener("keydown", this._searchKeyDown.bind(this), true);
597
598    toolbarElement.addEventListener("mousedown", this.toolbarDragStart, true);
599    document.getElementById("close-button-left").addEventListener("click", this.close, true);
600    document.getElementById("close-button-right").addEventListener("click", this.close, true);
601
602    this.extensionServer.initExtensions();
603
604    function populateInspectorState(inspectorState)
605    {
606        WebInspector.monitoringXHREnabled = inspectorState.monitoringXHREnabled;
607        if ("pauseOnExceptionsState" in inspectorState)
608            WebInspector.panels.scripts.updatePauseOnExceptionsState(inspectorState.pauseOnExceptionsState);
609    }
610    InspectorBackend.getInspectorState(populateInspectorState);
611
612    function onPopulateScriptObjects()
613    {
614        if (!WebInspector.currentPanel)
615            WebInspector.showPanel(WebInspector.settings.lastActivePanel);
616    }
617    InspectorBackend.populateScriptObjects(onPopulateScriptObjects);
618
619    InspectorBackend.setConsoleMessagesEnabled(true);
620
621    function propertyNamesCallback(names)
622    {
623        WebInspector.cssNameCompletions = new WebInspector.CSSCompletions(names);
624    }
625    // As a DOMAgent method, this needs to happen after the frontend has loaded and the agent is available.
626    InspectorBackend.getSupportedCSSProperties(propertyNamesCallback);
627}
628
629WebInspector.addPanelToolbarIcon = function(toolbarElement, panel, previousToolbarItem)
630{
631    var panelToolbarItem = panel.toolbarItem;
632    this.panelOrder.push(panel);
633    panelToolbarItem.addEventListener("click", this._toolbarItemClicked.bind(this));
634    if (previousToolbarItem)
635        toolbarElement.insertBefore(panelToolbarItem, previousToolbarItem.nextSibling);
636    else
637        toolbarElement.insertBefore(panelToolbarItem, toolbarElement.firstChild);
638    return panelToolbarItem;
639}
640
641var windowLoaded = function()
642{
643    var localizedStringsURL = InspectorFrontendHost.localizedStringsURL();
644    if (localizedStringsURL) {
645        var localizedStringsScriptElement = document.createElement("script");
646        localizedStringsScriptElement.addEventListener("load", WebInspector.loaded.bind(WebInspector), false);
647        localizedStringsScriptElement.type = "text/javascript";
648        localizedStringsScriptElement.src = localizedStringsURL;
649        document.head.appendChild(localizedStringsScriptElement);
650    } else
651        WebInspector.loaded();
652
653    window.removeEventListener("DOMContentLoaded", windowLoaded, false);
654    delete windowLoaded;
655};
656
657window.addEventListener("DOMContentLoaded", windowLoaded, false);
658
659WebInspector.dispatch = function(message) {
660    // We'd like to enforce asynchronous interaction between the inspector controller and the frontend.
661    // This is important to LayoutTests.
662    function delayDispatch()
663    {
664        InspectorBackend.dispatch(message);
665        WebInspector.pendingDispatches--;
666    }
667    WebInspector.pendingDispatches++;
668    setTimeout(delayDispatch, 0);
669}
670
671WebInspector.dispatchMessageFromBackend = function(messageObject)
672{
673    WebInspector.dispatch(messageObject);
674}
675
676WebInspector.windowResize = function(event)
677{
678    if (this.currentPanel)
679        this.currentPanel.resize();
680    this.drawer.resize();
681}
682
683WebInspector.windowFocused = function(event)
684{
685    // Fires after blur, so when focusing on either the main inspector
686    // or an <iframe> within the inspector we should always remove the
687    // "inactive" class.
688    if (event.target.document.nodeType === Node.DOCUMENT_NODE)
689        document.body.removeStyleClass("inactive");
690}
691
692WebInspector.windowBlurred = function(event)
693{
694    // Leaving the main inspector or an <iframe> within the inspector.
695    // We can add "inactive" now, and if we are moving the focus to another
696    // part of the inspector then windowFocused will correct this.
697    if (event.target.document.nodeType === Node.DOCUMENT_NODE)
698        document.body.addStyleClass("inactive");
699}
700
701WebInspector.focusChanged = function(event)
702{
703    this.currentFocusElement = event.target;
704}
705
706WebInspector.setAttachedWindow = function(attached)
707{
708    this.attached = attached;
709}
710
711WebInspector.close = function(event)
712{
713    if (this._isClosing)
714        return;
715    this._isClosing = true;
716    InspectorFrontendHost.closeWindow();
717}
718
719WebInspector.disconnectFromBackend = function()
720{
721    InspectorFrontendHost.disconnectFromBackend();
722}
723
724WebInspector.documentClick = function(event)
725{
726    var anchor = event.target.enclosingNodeOrSelfWithNodeName("a");
727    if (!anchor || anchor.target === "_blank")
728        return;
729
730    // Prevent the link from navigating, since we don't do any navigation by following links normally.
731    event.preventDefault();
732    event.stopPropagation();
733
734    function followLink()
735    {
736        // FIXME: support webkit-html-external-link links here.
737        if (WebInspector.canShowSourceLine(anchor.href, anchor.getAttribute("line_number"), anchor.getAttribute("preferred_panel"))) {
738            if (anchor.hasStyleClass("webkit-html-external-link")) {
739                anchor.removeStyleClass("webkit-html-external-link");
740                anchor.addStyleClass("webkit-html-resource-link");
741            }
742
743            WebInspector.showSourceLine(anchor.href, anchor.getAttribute("line_number"), anchor.getAttribute("preferred_panel"));
744            return;
745        }
746
747        const profileMatch = WebInspector.ProfileType.URLRegExp.exec(anchor.href);
748        if (profileMatch) {
749            WebInspector.showProfileForURL(anchor.href);
750            return;
751        }
752
753        var parsedURL = anchor.href.asParsedURL();
754        if (parsedURL && parsedURL.scheme === "webkit-link-action") {
755            if (parsedURL.host === "show-panel") {
756                var panel = parsedURL.path.substring(1);
757                if (WebInspector.panels[panel])
758                    WebInspector.showPanel(panel);
759            }
760            return;
761        }
762
763        WebInspector.showPanel("resources");
764    }
765
766    if (WebInspector.followLinkTimeout)
767        clearTimeout(WebInspector.followLinkTimeout);
768
769    if (anchor.preventFollowOnDoubleClick) {
770        // Start a timeout if this is the first click, if the timeout is canceled
771        // before it fires, then a double clicked happened or another link was clicked.
772        if (event.detail === 1)
773            WebInspector.followLinkTimeout = setTimeout(followLink, 333);
774        return;
775    }
776
777    followLink();
778}
779
780WebInspector.openResource = function(resourceURL, inResourcesPanel)
781{
782    var resource = WebInspector.resourceForURL(resourceURL);
783    if (inResourcesPanel && resource) {
784        WebInspector.panels.resources.showResource(resource);
785        WebInspector.showPanel("resources");
786    } else
787        InspectorBackend.openInInspectedWindow(resource ? resource.url : resourceURL);
788}
789
790WebInspector._registerShortcuts = function()
791{
792    var shortcut = WebInspector.KeyboardShortcut;
793    var section = WebInspector.shortcutsHelp.section(WebInspector.UIString("All Panels"));
794    var keys = [
795        shortcut.shortcutToString("]", shortcut.Modifiers.CtrlOrMeta),
796        shortcut.shortcutToString("[", shortcut.Modifiers.CtrlOrMeta)
797    ];
798    section.addRelatedKeys(keys, WebInspector.UIString("Next/previous panel"));
799    section.addKey(shortcut.shortcutToString(shortcut.Keys.Esc), WebInspector.UIString("Toggle console"));
800    section.addKey(shortcut.shortcutToString("f", shortcut.Modifiers.CtrlOrMeta), WebInspector.UIString("Search"));
801    if (WebInspector.isMac()) {
802        keys = [
803            shortcut.shortcutToString("g", shortcut.Modifiers.Meta),
804            shortcut.shortcutToString("g", shortcut.Modifiers.Meta | shortcut.Modifiers.Shift)
805        ];
806        section.addRelatedKeys(keys, WebInspector.UIString("Find next/previous"));
807    }
808}
809
810WebInspector.documentKeyDown = function(event)
811{
812    var isInputElement = event.target.nodeName === "INPUT";
813    var isInEditMode = event.target.enclosingNodeOrSelfWithClass("text-prompt") || WebInspector.isEditingAnyField();
814    const helpKey = WebInspector.isMac() ? "U+003F" : "U+00BF"; // "?" for both platforms
815
816    if (event.keyIdentifier === "F1" ||
817        (event.keyIdentifier === helpKey && event.shiftKey && (!isInEditMode && !isInputElement || event.metaKey))) {
818        WebInspector.shortcutsHelp.show();
819        event.stopPropagation();
820        event.preventDefault();
821        return;
822    }
823
824    if (WebInspector.isEditingAnyField())
825        return;
826
827    if (this.currentFocusElement && this.currentFocusElement.handleKeyEvent) {
828        this.currentFocusElement.handleKeyEvent(event);
829        if (event.handled) {
830            event.preventDefault();
831            return;
832        }
833    }
834
835    if (this.currentPanel && this.currentPanel.handleShortcut) {
836        this.currentPanel.handleShortcut(event);
837        if (event.handled) {
838            event.preventDefault();
839            return;
840        }
841    }
842
843    var isMac = WebInspector.isMac();
844    switch (event.keyIdentifier) {
845        case "Left":
846            var isBackKey = !isInEditMode && (isMac ? event.metaKey : event.ctrlKey);
847            if (isBackKey && this._panelHistory.canGoBack()) {
848                this._panelHistory.goBack();
849                event.preventDefault();
850            }
851            break;
852
853        case "Right":
854            var isForwardKey = !isInEditMode && (isMac ? event.metaKey : event.ctrlKey);
855            if (isForwardKey && this._panelHistory.canGoForward()) {
856                this._panelHistory.goForward();
857                event.preventDefault();
858            }
859            break;
860
861        case "U+001B": // Escape key
862            event.preventDefault();
863            if (this.drawer.fullPanel)
864                return;
865
866            this.drawer.visible = !this.drawer.visible;
867            break;
868
869        case "U+0046": // F key
870            if (isMac)
871                var isFindKey = event.metaKey && !event.ctrlKey && !event.altKey && !event.shiftKey;
872            else
873                var isFindKey = event.ctrlKey && !event.metaKey && !event.altKey && !event.shiftKey;
874
875            if (isFindKey) {
876                WebInspector.focusSearchField();
877                event.preventDefault();
878            }
879            break;
880
881        case "F3":
882            if (!isMac) {
883                WebInspector.focusSearchField();
884                event.preventDefault();
885            }
886            break;
887
888        case "U+0047": // G key
889            if (isMac && event.metaKey && !event.ctrlKey && !event.altKey) {
890                if (event.shiftKey) {
891                    if (this.currentPanel.jumpToPreviousSearchResult)
892                        this.currentPanel.jumpToPreviousSearchResult();
893                } else if (this.currentPanel.jumpToNextSearchResult)
894                    this.currentPanel.jumpToNextSearchResult();
895                event.preventDefault();
896            }
897            break;
898
899        // Windows and Mac have two different definitions of [, so accept both.
900        case "U+005B":
901        case "U+00DB": // [ key
902            if (isMac)
903                var isRotateLeft = event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey;
904            else
905                var isRotateLeft = event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey;
906
907            if (isRotateLeft) {
908                var index = this.panelOrder.indexOf(this.currentPanel);
909                index = (index === 0) ? this.panelOrder.length - 1 : index - 1;
910                this.panelOrder[index].toolbarItem.click();
911                event.preventDefault();
912            }
913
914            break;
915
916        // Windows and Mac have two different definitions of ], so accept both.
917        case "U+005D":
918        case "U+00DD":  // ] key
919            if (isMac)
920                var isRotateRight = event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey;
921            else
922                var isRotateRight = event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey;
923
924            if (isRotateRight) {
925                var index = this.panelOrder.indexOf(this.currentPanel);
926                index = (index + 1) % this.panelOrder.length;
927                this.panelOrder[index].toolbarItem.click();
928                event.preventDefault();
929            }
930
931            break;
932
933        case "U+0052": // R key
934            if ((event.metaKey && isMac) || (event.ctrlKey && !isMac)) {
935                InspectorBackend.reloadPage();
936                event.preventDefault();
937            }
938            break;
939        case "F5":
940            if (!isMac)
941                InspectorBackend.reloadPage();
942            break;
943    }
944}
945
946WebInspector.documentCanCopy = function(event)
947{
948    if (this.currentPanel && this.currentPanel.handleCopyEvent)
949        event.preventDefault();
950}
951
952WebInspector.documentCopy = function(event)
953{
954    if (this.currentPanel && this.currentPanel.handleCopyEvent)
955        this.currentPanel.handleCopyEvent(event);
956}
957
958WebInspector.contextMenuEventFired = function(event)
959{
960    if (event.handled || event.target.hasStyleClass("popup-glasspane"))
961        event.preventDefault();
962}
963
964WebInspector.animateStyle = function(animations, duration, callback)
965{
966    var interval;
967    var complete = 0;
968
969    const intervalDuration = (1000 / 30); // 30 frames per second.
970    const animationsLength = animations.length;
971    const propertyUnit = {opacity: ""};
972    const defaultUnit = "px";
973
974    function cubicInOut(t, b, c, d)
975    {
976        if ((t/=d/2) < 1) return c/2*t*t*t + b;
977        return c/2*((t-=2)*t*t + 2) + b;
978    }
979
980    // Pre-process animations.
981    for (var i = 0; i < animationsLength; ++i) {
982        var animation = animations[i];
983        var element = null, start = null, end = null, key = null;
984        for (key in animation) {
985            if (key === "element")
986                element = animation[key];
987            else if (key === "start")
988                start = animation[key];
989            else if (key === "end")
990                end = animation[key];
991        }
992
993        if (!element || !end)
994            continue;
995
996        if (!start) {
997            var computedStyle = element.ownerDocument.defaultView.getComputedStyle(element);
998            start = {};
999            for (key in end)
1000                start[key] = parseInt(computedStyle.getPropertyValue(key));
1001            animation.start = start;
1002        } else
1003            for (key in start)
1004                element.style.setProperty(key, start[key] + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
1005    }
1006
1007    function animateLoop()
1008    {
1009        // Advance forward.
1010        complete += intervalDuration;
1011        var next = complete + intervalDuration;
1012
1013        // Make style changes.
1014        for (var i = 0; i < animationsLength; ++i) {
1015            var animation = animations[i];
1016            var element = animation.element;
1017            var start = animation.start;
1018            var end = animation.end;
1019            if (!element || !end)
1020                continue;
1021
1022            var style = element.style;
1023            for (key in end) {
1024                var endValue = end[key];
1025                if (next < duration) {
1026                    var startValue = start[key];
1027                    var newValue = cubicInOut(complete, startValue, endValue - startValue, duration);
1028                    style.setProperty(key, newValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
1029                } else
1030                    style.setProperty(key, endValue + (key in propertyUnit ? propertyUnit[key] : defaultUnit));
1031            }
1032        }
1033
1034        // End condition.
1035        if (complete >= duration) {
1036            clearInterval(interval);
1037            if (callback)
1038                callback();
1039        }
1040    }
1041
1042    interval = setInterval(animateLoop, intervalDuration);
1043    return interval;
1044}
1045
1046WebInspector.updateSearchLabel = function()
1047{
1048    if (!this.currentPanel)
1049        return;
1050
1051    var newLabel = WebInspector.UIString("Search %s", this.currentPanel.toolbarItemLabel);
1052    if (this.attached)
1053        document.getElementById("search").setAttribute("placeholder", newLabel);
1054    else {
1055        document.getElementById("search").removeAttribute("placeholder");
1056        document.getElementById("search-toolbar-label").textContent = newLabel;
1057    }
1058}
1059
1060WebInspector.focusSearchField = function()
1061{
1062    var searchField = document.getElementById("search");
1063    searchField.focus();
1064    searchField.select();
1065}
1066
1067WebInspector.toggleAttach = function()
1068{
1069    if (!this.attached)
1070        InspectorFrontendHost.requestAttachWindow();
1071    else
1072        InspectorFrontendHost.requestDetachWindow();
1073}
1074
1075WebInspector.toolbarDragStart = function(event)
1076{
1077    if ((!WebInspector.attached && WebInspector.platformFlavor !== WebInspector.PlatformFlavor.MacLeopard && WebInspector.platformFlavor !== WebInspector.PlatformFlavor.MacSnowLeopard) || WebInspector.port == "qt")
1078        return;
1079
1080    var target = event.target;
1081    if (target.hasStyleClass("toolbar-item") && target.hasStyleClass("toggleable"))
1082        return;
1083
1084    var toolbar = document.getElementById("toolbar");
1085    if (target !== toolbar && !target.hasStyleClass("toolbar-item"))
1086        return;
1087
1088    toolbar.lastScreenX = event.screenX;
1089    toolbar.lastScreenY = event.screenY;
1090
1091    WebInspector.elementDragStart(toolbar, WebInspector.toolbarDrag, WebInspector.toolbarDragEnd, event, (WebInspector.attached ? "row-resize" : "default"));
1092}
1093
1094WebInspector.toolbarDragEnd = function(event)
1095{
1096    var toolbar = document.getElementById("toolbar");
1097
1098    WebInspector.elementDragEnd(event);
1099
1100    delete toolbar.lastScreenX;
1101    delete toolbar.lastScreenY;
1102}
1103
1104WebInspector.toolbarDrag = function(event)
1105{
1106    var toolbar = document.getElementById("toolbar");
1107
1108    if (WebInspector.attached) {
1109        var height = window.innerHeight - (event.screenY - toolbar.lastScreenY);
1110
1111        InspectorFrontendHost.setAttachedWindowHeight(height);
1112    } else {
1113        var x = event.screenX - toolbar.lastScreenX;
1114        var y = event.screenY - toolbar.lastScreenY;
1115
1116        // We cannot call window.moveBy here because it restricts the movement
1117        // of the window at the edges.
1118        InspectorFrontendHost.moveWindowBy(x, y);
1119    }
1120
1121    toolbar.lastScreenX = event.screenX;
1122    toolbar.lastScreenY = event.screenY;
1123
1124    event.preventDefault();
1125}
1126
1127WebInspector.elementDragStart = function(element, dividerDrag, elementDragEnd, event, cursor)
1128{
1129    if (this._elementDraggingEventListener || this._elementEndDraggingEventListener)
1130        this.elementDragEnd(event);
1131
1132    this._elementDraggingEventListener = dividerDrag;
1133    this._elementEndDraggingEventListener = elementDragEnd;
1134
1135    document.addEventListener("mousemove", dividerDrag, true);
1136    document.addEventListener("mouseup", elementDragEnd, true);
1137
1138    document.body.style.cursor = cursor;
1139
1140    event.preventDefault();
1141}
1142
1143WebInspector.elementDragEnd = function(event)
1144{
1145    document.removeEventListener("mousemove", this._elementDraggingEventListener, true);
1146    document.removeEventListener("mouseup", this._elementEndDraggingEventListener, true);
1147
1148    document.body.style.removeProperty("cursor");
1149
1150    delete this._elementDraggingEventListener;
1151    delete this._elementEndDraggingEventListener;
1152
1153    event.preventDefault();
1154}
1155
1156WebInspector.toggleSearchingForNode = function()
1157{
1158    if (this.panels.elements) {
1159        this.showPanel("elements");
1160        this.panels.elements.toggleSearchingForNode();
1161    }
1162}
1163
1164WebInspector.showConsole = function()
1165{
1166    this.drawer.showView(this.console);
1167}
1168
1169WebInspector.showChanges = function()
1170{
1171    this.drawer.showView(this.changes);
1172}
1173
1174WebInspector.showPanel = function(panel)
1175{
1176    if (!(panel in this.panels))
1177        panel = "elements";
1178    this.currentPanel = this.panels[panel];
1179}
1180
1181WebInspector.domContentEventFired = function(time)
1182{
1183    this.panels.audits.mainResourceDOMContentTime = time;
1184    if (this.panels.network)
1185        this.panels.network.mainResourceDOMContentTime = time;
1186    this.extensionServer.notifyPageDOMContentLoaded((time - WebInspector.mainResource.startTime) * 1000);
1187    this.mainResourceDOMContentTime = time;
1188}
1189
1190WebInspector.loadEventFired = function(time)
1191{
1192    this.panels.audits.mainResourceLoadTime = time;
1193    if (this.panels.network)
1194        this.panels.network.mainResourceLoadTime = time;
1195    this.extensionServer.notifyPageLoaded((time - WebInspector.mainResource.startTime) * 1000);
1196    this.mainResourceLoadTime = time;
1197}
1198
1199WebInspector.searchingForNodeWasEnabled = function()
1200{
1201    this.panels.elements.searchingForNodeWasEnabled();
1202}
1203
1204WebInspector.searchingForNodeWasDisabled = function()
1205{
1206    this.panels.elements.searchingForNodeWasDisabled();
1207}
1208
1209WebInspector.reset = function()
1210{
1211    this.debuggerModel.reset();
1212
1213    for (var panelName in this.panels) {
1214        var panel = this.panels[panelName];
1215        if ("reset" in panel)
1216            panel.reset();
1217    }
1218
1219    this.resources = {};
1220    this.highlightDOMNode(0);
1221
1222    this.console.clearMessages();
1223    this.extensionServer.notifyInspectorReset();
1224}
1225
1226WebInspector.bringToFront = function()
1227{
1228    InspectorFrontendHost.bringToFront();
1229}
1230
1231WebInspector.inspectedURLChanged = function(url)
1232{
1233    InspectorFrontendHost.inspectedURLChanged(url);
1234    this.settings.inspectedURLChanged(url);
1235    this.extensionServer.notifyInspectedURLChanged();
1236}
1237
1238WebInspector.log = function(message, messageLevel)
1239{
1240    // remember 'this' for setInterval() callback
1241    var self = this;
1242
1243    // return indication if we can actually log a message
1244    function isLogAvailable()
1245    {
1246        return WebInspector.ConsoleMessage && WebInspector.RemoteObject && self.console;
1247    }
1248
1249    // flush the queue of pending messages
1250    function flushQueue()
1251    {
1252        var queued = WebInspector.log.queued;
1253        if (!queued)
1254            return;
1255
1256        for (var i = 0; i < queued.length; ++i)
1257            logMessage(queued[i]);
1258
1259        delete WebInspector.log.queued;
1260    }
1261
1262    // flush the queue if it console is available
1263    // - this function is run on an interval
1264    function flushQueueIfAvailable()
1265    {
1266        if (!isLogAvailable())
1267            return;
1268
1269        clearInterval(WebInspector.log.interval);
1270        delete WebInspector.log.interval;
1271
1272        flushQueue();
1273    }
1274
1275    // actually log the message
1276    function logMessage(message)
1277    {
1278        var repeatCount = 1;
1279        if (message == WebInspector.log.lastMessage)
1280            repeatCount = WebInspector.log.repeatCount + 1;
1281
1282        WebInspector.log.lastMessage = message;
1283        WebInspector.log.repeatCount = repeatCount;
1284
1285        // ConsoleMessage expects a proxy object
1286        message = new WebInspector.RemoteObject.fromPrimitiveValue(message);
1287
1288        // post the message
1289        var msg = new WebInspector.ConsoleMessage(
1290            WebInspector.ConsoleMessage.MessageSource.Other,
1291            WebInspector.ConsoleMessage.MessageType.Log,
1292            messageLevel || WebInspector.ConsoleMessage.MessageLevel.Debug,
1293            -1,
1294            null,
1295            repeatCount,
1296            null,
1297            [message],
1298            null);
1299
1300        self.console.addMessage(msg);
1301    }
1302
1303    // if we can't log the message, queue it
1304    if (!isLogAvailable()) {
1305        if (!WebInspector.log.queued)
1306            WebInspector.log.queued = [];
1307
1308        WebInspector.log.queued.push(message);
1309
1310        if (!WebInspector.log.interval)
1311            WebInspector.log.interval = setInterval(flushQueueIfAvailable, 1000);
1312
1313        return;
1314    }
1315
1316    // flush the pending queue if any
1317    flushQueue();
1318
1319    // log the message
1320    logMessage(message);
1321}
1322
1323WebInspector.drawLoadingPieChart = function(canvas, percent) {
1324    var g = canvas.getContext("2d");
1325    var darkColor = "rgb(122, 168, 218)";
1326    var lightColor = "rgb(228, 241, 251)";
1327    var cx = 8;
1328    var cy = 8;
1329    var r = 7;
1330
1331    g.beginPath();
1332    g.arc(cx, cy, r, 0, Math.PI * 2, false);
1333    g.closePath();
1334
1335    g.lineWidth = 1;
1336    g.strokeStyle = darkColor;
1337    g.fillStyle = lightColor;
1338    g.fill();
1339    g.stroke();
1340
1341    var startangle = -Math.PI / 2;
1342    var endangle = startangle + (percent * Math.PI * 2);
1343
1344    g.beginPath();
1345    g.moveTo(cx, cy);
1346    g.arc(cx, cy, r, startangle, endangle, false);
1347    g.closePath();
1348
1349    g.fillStyle = darkColor;
1350    g.fill();
1351}
1352
1353WebInspector.updateFocusedNode = function(nodeId)
1354{
1355    this._updateFocusedNode(nodeId);
1356    this.highlightDOMNodeForTwoSeconds(nodeId);
1357}
1358
1359WebInspector.displayNameForURL = function(url)
1360{
1361    if (!url)
1362        return "";
1363
1364    var resource = this.resourceForURL(url);
1365    if (resource)
1366        return resource.displayName;
1367
1368    if (!WebInspector.mainResource)
1369        return url.trimURL("");
1370
1371    var lastPathComponent = WebInspector.mainResource.lastPathComponent;
1372    var index = WebInspector.mainResource.url.indexOf(lastPathComponent);
1373    if (index !== -1 && index + lastPathComponent.length === WebInspector.mainResource.url.length) {
1374        var baseURL = WebInspector.mainResource.url.substring(0, index);
1375        if (url.indexOf(baseURL) === 0)
1376            return url.substring(index);
1377    }
1378
1379    return url.trimURL(WebInspector.mainResource.domain);
1380}
1381
1382WebInspector._choosePanelToShowSourceLine = function(url, line, preferredPanel)
1383{
1384    preferredPanel = preferredPanel || "resources";
1385
1386    var panel = this.panels[preferredPanel];
1387    if (panel && panel.canShowSourceLine(url, line))
1388        return panel;
1389    panel = this.panels.resources;
1390    return panel.canShowSourceLine(url, line) ? panel : null;
1391}
1392
1393WebInspector.canShowSourceLine = function(url, line, preferredPanel)
1394{
1395    return !!this._choosePanelToShowSourceLine(url, line, preferredPanel);
1396}
1397
1398WebInspector.showSourceLine = function(url, line, preferredPanel)
1399{
1400    this.currentPanel = this._choosePanelToShowSourceLine(url, line, preferredPanel);
1401    if (!this.currentPanel)
1402        return false;
1403    this.currentPanel.showSourceLine(url, line);
1404    return true;
1405}
1406
1407WebInspector.linkifyStringAsFragment = function(string)
1408{
1409    var container = document.createDocumentFragment();
1410    var linkStringRegEx = /(?:[a-zA-Z][a-zA-Z0-9+.-]{2,}:\/\/|www\.)[\w$\-_+*'=\|\/\\(){}[\]%@&#~,:;.!?]{2,}[\w$\-_+*=\|\/\\({%@&#~]/;
1411    var lineColumnRegEx = /:(\d+)(:(\d+))?$/;
1412
1413    while (string) {
1414        var linkString = linkStringRegEx.exec(string);
1415        if (!linkString)
1416            break;
1417
1418        linkString = linkString[0];
1419        var title = linkString;
1420        var linkIndex = string.indexOf(linkString);
1421        var nonLink = string.substring(0, linkIndex);
1422        container.appendChild(document.createTextNode(nonLink));
1423
1424        var profileStringMatches = WebInspector.ProfileType.URLRegExp.exec(title);
1425        if (profileStringMatches)
1426            title = WebInspector.panels.profiles.displayTitleForProfileLink(profileStringMatches[2], profileStringMatches[1]);
1427
1428        var realURL = (linkString.indexOf("www.") === 0 ? "http://" + linkString : linkString);
1429        var lineColumnMatch = lineColumnRegEx.exec(realURL);
1430        if (lineColumnMatch)
1431            realURL = realURL.substring(0, realURL.length - lineColumnMatch[0].length);
1432
1433        var hasResourceWithURL = !!WebInspector.resourceForURL(realURL);
1434        var urlNode = WebInspector.linkifyURLAsNode(realURL, title, null, hasResourceWithURL);
1435        container.appendChild(urlNode);
1436        if (lineColumnMatch) {
1437            urlNode.setAttribute("line_number", lineColumnMatch[1]);
1438            urlNode.setAttribute("preferred_panel", "scripts");
1439        }
1440        string = string.substring(linkIndex + linkString.length, string.length);
1441    }
1442
1443    if (string)
1444        container.appendChild(document.createTextNode(string));
1445
1446    return container;
1447}
1448
1449WebInspector.showProfileForURL = function(url)
1450{
1451    WebInspector.showPanel("profiles");
1452    WebInspector.panels.profiles.showProfileForURL(url);
1453}
1454
1455WebInspector.linkifyURLAsNode = function(url, linkText, classes, isExternal, tooltipText)
1456{
1457    if (!linkText)
1458        linkText = url;
1459    classes = (classes ? classes + " " : "");
1460    classes += isExternal ? "webkit-html-external-link" : "webkit-html-resource-link";
1461
1462    var a = document.createElement("a");
1463    a.href = url;
1464    a.className = classes;
1465    if (typeof tooltipText === "undefined")
1466        a.title = url;
1467    else if (typeof tooltipText !== "string" || tooltipText.length)
1468        a.title = tooltipText;
1469    a.textContent = linkText;
1470
1471    return a;
1472}
1473
1474WebInspector.linkifyURL = function(url, linkText, classes, isExternal, tooltipText)
1475{
1476    // Use the DOM version of this function so as to avoid needing to escape attributes.
1477    // FIXME:  Get rid of linkifyURL entirely.
1478    return WebInspector.linkifyURLAsNode(url, linkText, classes, isExternal, tooltipText).outerHTML;
1479}
1480
1481WebInspector.linkifyResourceAsNode = function(url, preferredPanel, lineNumber, classes, tooltipText)
1482{
1483    var linkText = WebInspector.displayNameForURL(url);
1484    if (lineNumber)
1485        linkText += ":" + lineNumber;
1486    var node = WebInspector.linkifyURLAsNode(url, linkText, classes, false, tooltipText);
1487    node.setAttribute("line_number", lineNumber);
1488    node.setAttribute("preferred_panel", preferredPanel);
1489    return node;
1490}
1491
1492WebInspector.resourceURLForRelatedNode = function(node, url)
1493{
1494    if (!url || url.indexOf("://") > 0)
1495        return url;
1496
1497    for (var frameOwnerCandidate = node; frameOwnerCandidate; frameOwnerCandidate = frameOwnerCandidate.parentNode) {
1498        if (frameOwnerCandidate.documentURL) {
1499            var result = WebInspector.completeURL(frameOwnerCandidate.documentURL, url);
1500            if (result)
1501                return result;
1502            break;
1503        }
1504    }
1505
1506    // documentURL not found or has bad value
1507    var resourceURL = url;
1508    function callback(resource)
1509    {
1510        if (resource.path === url) {
1511            resourceURL = resource.url;
1512            return true;
1513        }
1514    }
1515    WebInspector.forAllResources(callback);
1516    return resourceURL;
1517}
1518
1519WebInspector.completeURL = function(baseURL, href)
1520{
1521    if (href) {
1522        // Return absolute URLs as-is.
1523        var parsedHref = href.asParsedURL();
1524        if (parsedHref && parsedHref.scheme)
1525            return href;
1526    }
1527
1528    var parsedURL = baseURL.asParsedURL();
1529    if (parsedURL) {
1530        var path = href;
1531        if (path.charAt(0) !== "/") {
1532            var basePath = parsedURL.path;
1533            path = basePath.substring(0, basePath.lastIndexOf("/")) + "/" + path;
1534        } else if (path.length > 1 && path.charAt(1) === "/") {
1535            // href starts with "//" which is a full URL with the protocol dropped (use the baseURL protocol).
1536            return parsedURL.scheme + ":" + path;
1537        }
1538        return parsedURL.scheme + "://" + parsedURL.host + (parsedURL.port ? (":" + parsedURL.port) : "") + path;
1539    }
1540    return null;
1541}
1542
1543WebInspector.addMainEventListeners = function(doc)
1544{
1545    doc.defaultView.addEventListener("focus", this.windowFocused.bind(this), false);
1546    doc.defaultView.addEventListener("blur", this.windowBlurred.bind(this), false);
1547    doc.addEventListener("click", this.documentClick.bind(this), true);
1548}
1549
1550WebInspector._searchFieldManualFocus = function(event)
1551{
1552    this.currentFocusElement = event.target;
1553    this._previousFocusElement = event.target;
1554}
1555
1556WebInspector._searchKeyDown = function(event)
1557{
1558    // Escape Key will clear the field and clear the search results
1559    if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code) {
1560        // If focus belongs here and text is empty - nothing to do, return unhandled.
1561        if (event.target.value === "" && this.currentFocusElement === this.previousFocusElement)
1562            return;
1563        event.preventDefault();
1564        event.stopPropagation();
1565        // When search was selected manually and is currently blank, we'd like Esc stay unhandled
1566        // and hit console drawer handler.
1567        event.target.value = "";
1568
1569        this.performSearch(event);
1570        this.currentFocusElement = this.previousFocusElement;
1571        if (this.currentFocusElement === event.target)
1572            this.currentFocusElement.select();
1573        return false;
1574    }
1575
1576    if (!isEnterKey(event))
1577        return false;
1578
1579    // Select all of the text so the user can easily type an entirely new query.
1580    event.target.select();
1581
1582    // Only call performSearch if the Enter key was pressed. Otherwise the search
1583    // performance is poor because of searching on every key. The search field has
1584    // the incremental attribute set, so we still get incremental searches.
1585    this.performSearch(event);
1586
1587    // Call preventDefault since this was the Enter key. This prevents a "search" event
1588    // from firing for key down. This stops performSearch from being called twice in a row.
1589    event.preventDefault();
1590}
1591
1592WebInspector.performSearch = function(event)
1593{
1594    var forceSearch = event.keyIdentifier === "Enter";
1595    this.doPerformSearch(event.target.value, forceSearch, event.shiftKey, false);
1596}
1597
1598WebInspector.doPerformSearch = function(query, forceSearch, isBackwardSearch, repeatSearch)
1599{
1600    var isShortSearch = (query.length < 3);
1601
1602    // Clear a leftover short search flag due to a non-conflicting forced search.
1603    if (isShortSearch && this.shortSearchWasForcedByKeyEvent && this.currentQuery !== query)
1604        delete this.shortSearchWasForcedByKeyEvent;
1605
1606    // Indicate this was a forced search on a short query.
1607    if (isShortSearch && forceSearch)
1608        this.shortSearchWasForcedByKeyEvent = true;
1609
1610    if (!query || !query.length || (!forceSearch && isShortSearch)) {
1611        // Prevent clobbering a short search forced by the user.
1612        if (this.shortSearchWasForcedByKeyEvent) {
1613            delete this.shortSearchWasForcedByKeyEvent;
1614            return;
1615        }
1616
1617        delete this.currentQuery;
1618
1619        for (var panelName in this.panels) {
1620            var panel = this.panels[panelName];
1621            var hadCurrentQuery = !!panel.currentQuery;
1622            delete panel.currentQuery;
1623            if (hadCurrentQuery && panel.searchCanceled)
1624                panel.searchCanceled();
1625        }
1626
1627        this.updateSearchMatchesCount();
1628
1629        return;
1630    }
1631
1632    if (!repeatSearch && query === this.currentPanel.currentQuery && this.currentPanel.currentQuery === this.currentQuery) {
1633        // When this is the same query and a forced search, jump to the next
1634        // search result for a good user experience.
1635        if (forceSearch) {
1636            if (!isBackwardSearch && this.currentPanel.jumpToNextSearchResult)
1637                this.currentPanel.jumpToNextSearchResult();
1638            else if (isBackwardSearch && this.currentPanel.jumpToPreviousSearchResult)
1639                this.currentPanel.jumpToPreviousSearchResult();
1640        }
1641        return;
1642    }
1643
1644    this.currentQuery = query;
1645
1646    this.updateSearchMatchesCount();
1647
1648    if (!this.currentPanel.performSearch)
1649        return;
1650
1651    this.currentPanel.currentQuery = query;
1652    this.currentPanel.performSearch(query);
1653}
1654
1655WebInspector.frontendReused = function()
1656{
1657    this.networkManager.reset();
1658    this.reset();
1659}
1660
1661WebInspector.addNodesToSearchResult = function(nodeIds)
1662{
1663    WebInspector.panels.elements.addNodesToSearchResult(nodeIds);
1664}
1665
1666WebInspector.updateSearchMatchesCount = function(matches, panel)
1667{
1668    if (!panel)
1669        panel = this.currentPanel;
1670
1671    panel.currentSearchMatches = matches;
1672
1673    if (panel !== this.currentPanel)
1674        return;
1675
1676    if (!this.currentPanel.currentQuery) {
1677        document.getElementById("search-results-matches").addStyleClass("hidden");
1678        return;
1679    }
1680
1681    if (matches) {
1682        if (matches === 1)
1683            var matchesString = WebInspector.UIString("1 match");
1684        else
1685            var matchesString = WebInspector.UIString("%d matches", matches);
1686    } else
1687        var matchesString = WebInspector.UIString("Not Found");
1688
1689    var matchesToolbarElement = document.getElementById("search-results-matches");
1690    matchesToolbarElement.removeStyleClass("hidden");
1691    matchesToolbarElement.textContent = matchesString;
1692}
1693
1694WebInspector.UIString = function(string)
1695{
1696    if (window.localizedStrings && string in window.localizedStrings)
1697        string = window.localizedStrings[string];
1698    else {
1699        if (!(string in WebInspector.missingLocalizedStrings)) {
1700            if (!WebInspector.InspectorBackendStub)
1701                console.error("Localized string \"" + string + "\" not found.");
1702            WebInspector.missingLocalizedStrings[string] = true;
1703        }
1704
1705        if (Preferences.showMissingLocalizedStrings)
1706            string += " (not localized)";
1707    }
1708
1709    return String.vsprintf(string, Array.prototype.slice.call(arguments, 1));
1710}
1711
1712WebInspector.formatLocalized = function(format, substitutions, formatters, initialValue, append)
1713{
1714    return String.format(WebInspector.UIString(format), substitutions, formatters, initialValue, append);
1715}
1716
1717WebInspector.isMac = function()
1718{
1719    if (!("_isMac" in this))
1720        this._isMac = WebInspector.platform === "mac";
1721
1722    return this._isMac;
1723}
1724
1725WebInspector.isBeingEdited = function(element)
1726{
1727    return element.__editing;
1728}
1729
1730WebInspector.isEditingAnyField = function()
1731{
1732    return this.__editing;
1733}
1734
1735// Available config fields (all optional):
1736// context: Object - an arbitrary context object to be passed to the commit and cancel handlers
1737// commitHandler: Function - handles editing "commit" outcome
1738// cancelHandler: Function - handles editing "cancel" outcome
1739// customFinishHandler: Function - custom finish handler for the editing session (invoked on keydown)
1740// pasteHandler: Function - handles the "paste" event, return values are the same as those for customFinishHandler
1741// multiline: Boolean - whether the edited element is multiline
1742WebInspector.startEditing = function(element, config)
1743{
1744    if (element.__editing)
1745        return;
1746    element.__editing = true;
1747    WebInspector.__editing = true;
1748
1749    config = config || {};
1750    var committedCallback = config.commitHandler;
1751    var cancelledCallback = config.cancelHandler;
1752    var pasteCallback = config.pasteHandler;
1753    var context = config.context;
1754    var oldText = getContent(element);
1755    var moveDirection = "";
1756
1757    element.addStyleClass("editing");
1758
1759    var oldTabIndex = element.tabIndex;
1760    if (element.tabIndex < 0)
1761        element.tabIndex = 0;
1762
1763    function blurEventListener() {
1764        editingCommitted.call(element);
1765    }
1766
1767    function getContent(element) {
1768        if (element.tagName === "INPUT" && element.type === "text")
1769            return element.value;
1770        else
1771            return element.textContent;
1772    }
1773
1774    function cleanUpAfterEditing() {
1775        delete this.__editing;
1776        delete WebInspector.__editing;
1777
1778        this.removeStyleClass("editing");
1779        this.tabIndex = oldTabIndex;
1780        this.scrollTop = 0;
1781        this.scrollLeft = 0;
1782
1783        element.removeEventListener("blur", blurEventListener, false);
1784        element.removeEventListener("keydown", keyDownEventListener, true);
1785        if (pasteCallback)
1786            element.removeEventListener("paste", pasteEventListener, true);
1787
1788        if (element === WebInspector.currentFocusElement || element.isAncestor(WebInspector.currentFocusElement))
1789            WebInspector.currentFocusElement = WebInspector.previousFocusElement;
1790    }
1791
1792    function editingCancelled() {
1793        if (this.tagName === "INPUT" && this.type === "text")
1794            this.value = oldText;
1795        else
1796            this.textContent = oldText;
1797
1798        cleanUpAfterEditing.call(this);
1799
1800        if (cancelledCallback)
1801            cancelledCallback(this, context);
1802    }
1803
1804    function editingCommitted() {
1805        cleanUpAfterEditing.call(this);
1806
1807        if (committedCallback)
1808            committedCallback(this, getContent(this), oldText, context, moveDirection);
1809    }
1810
1811    function defaultFinishHandler(event)
1812    {
1813        var isMetaOrCtrl = WebInspector.isMac() ?
1814            event.metaKey && !event.shiftKey && !event.ctrlKey && !event.altKey :
1815            event.ctrlKey && !event.shiftKey && !event.metaKey && !event.altKey;
1816        if (isEnterKey(event) && (!config.multiline || isMetaOrCtrl))
1817            return "commit";
1818        else if (event.keyCode === WebInspector.KeyboardShortcut.Keys.Esc.code)
1819            return "cancel";
1820        else if (event.keyIdentifier === "U+0009") // Tab key
1821            return "move-" + (event.shiftKey ? "backward" : "forward");
1822    }
1823
1824    function handleEditingResult(result, event)
1825    {
1826        if (result === "commit") {
1827            editingCommitted.call(element);
1828            event.preventDefault();
1829            event.stopPropagation();
1830        } else if (result === "cancel") {
1831            editingCancelled.call(element);
1832            event.preventDefault();
1833            event.stopPropagation();
1834        } else if (result && result.indexOf("move-") === 0) {
1835            moveDirection = result.substring(5);
1836            if (event.keyIdentifier !== "U+0009")
1837                blurEventListener();
1838        }
1839    }
1840
1841    function pasteEventListener(event)
1842    {
1843        var result = pasteCallback(event);
1844        handleEditingResult(result, event);
1845    }
1846
1847    function keyDownEventListener(event)
1848    {
1849        var handler = config.customFinishHandler || defaultFinishHandler;
1850        var result = handler(event);
1851        handleEditingResult(result, event);
1852    }
1853
1854    element.addEventListener("blur", blurEventListener, false);
1855    element.addEventListener("keydown", keyDownEventListener, true);
1856    if (pasteCallback)
1857        element.addEventListener("paste", pasteEventListener, true);
1858
1859    WebInspector.currentFocusElement = element;
1860    return {
1861        cancel: editingCancelled.bind(element),
1862        commit: editingCommitted.bind(element)
1863    };
1864}
1865
1866WebInspector._toolbarItemClicked = function(event)
1867{
1868    var toolbarItem = event.currentTarget;
1869    this.currentPanel = toolbarItem.panel;
1870}
1871
1872// This table maps MIME types to the Resource.Types which are valid for them.
1873// The following line:
1874//    "text/html":                {0: 1},
1875// means that text/html is a valid MIME type for resources that have type
1876// WebInspector.Resource.Type.Document (which has a value of 0).
1877WebInspector.MIMETypes = {
1878    "text/html":                   {0: true},
1879    "text/xml":                    {0: true},
1880    "text/plain":                  {0: true},
1881    "application/xhtml+xml":       {0: true},
1882    "text/css":                    {1: true},
1883    "text/xsl":                    {1: true},
1884    "image/jpeg":                  {2: true},
1885    "image/png":                   {2: true},
1886    "image/gif":                   {2: true},
1887    "image/bmp":                   {2: true},
1888    "image/vnd.microsoft.icon":    {2: true},
1889    "image/x-icon":                {2: true},
1890    "image/x-xbitmap":             {2: true},
1891    "font/ttf":                    {3: true},
1892    "font/opentype":               {3: true},
1893    "application/x-font-type1":    {3: true},
1894    "application/x-font-ttf":      {3: true},
1895    "application/x-truetype-font": {3: true},
1896    "text/javascript":             {4: true},
1897    "text/ecmascript":             {4: true},
1898    "application/javascript":      {4: true},
1899    "application/ecmascript":      {4: true},
1900    "application/x-javascript":    {4: true},
1901    "text/javascript1.1":          {4: true},
1902    "text/javascript1.2":          {4: true},
1903    "text/javascript1.3":          {4: true},
1904    "text/jscript":                {4: true},
1905    "text/livescript":             {4: true},
1906}
1907
1908WebInspector.PanelHistory = function()
1909{
1910    this._history = [];
1911    this._historyIterator = -1;
1912}
1913
1914WebInspector.PanelHistory.prototype = {
1915    canGoBack: function()
1916    {
1917        return this._historyIterator > 0;
1918    },
1919
1920    goBack: function()
1921    {
1922        this._inHistory = true;
1923        WebInspector.currentPanel = WebInspector.panels[this._history[--this._historyIterator]];
1924        delete this._inHistory;
1925    },
1926
1927    canGoForward: function()
1928    {
1929        return this._historyIterator < this._history.length - 1;
1930    },
1931
1932    goForward: function()
1933    {
1934        this._inHistory = true;
1935        WebInspector.currentPanel = WebInspector.panels[this._history[++this._historyIterator]];
1936        delete this._inHistory;
1937    },
1938
1939    setPanel: function(panelName)
1940    {
1941        if (this._inHistory)
1942            return;
1943
1944        this._history.splice(this._historyIterator + 1, this._history.length - this._historyIterator - 1);
1945        if (!this._history.length || this._history[this._history.length - 1] !== panelName)
1946            this._history.push(panelName);
1947        this._historyIterator = this._history.length - 1;
1948    }
1949}
1950