Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / third_party / WebKit / Source / devtools / front_end / components / InspectorView.js
blob625dc90cb1dd3f628a9c8917edcd6ad22c088e87
1 /*
2  * Copyright (C) 2011 Google Inc. All rights reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions are
6  * met:
7  *
8  *     * Redistributions of source code must retain the above copyright
9  * notice, this list of conditions and the following disclaimer.
10  *     * Redistributions in binary form must reproduce the above
11  * copyright notice, this list of conditions and the following disclaimer
12  * in the documentation and/or other materials provided with the
13  * distribution.
14  *     * Neither the name of Google Inc. nor the names of its
15  * contributors may be used to endorse or promote products derived from
16  * this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
19  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
20  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
21  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
22  * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
23  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
24  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
25  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
26  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
27  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
29  */
31 /**
32  * @constructor
33  * @extends {WebInspector.VBox}
34  */
35 WebInspector.InspectorView = function()
37     WebInspector.VBox.call(this);
38     WebInspector.Dialog.setModalHostView(this);
39     WebInspector.GlassPane.DefaultFocusedViewStack.push(this);
40     this.setMinimumSize(240, 72);
42     // DevTools sidebar is a vertical split of panels tabbed pane and a drawer.
43     this._drawerSplitWidget = new WebInspector.SplitWidget(false, true, "Inspector.drawerSplitViewState", 200, 200);
44     this._drawerSplitWidget.hideSidebar();
45     this._drawerSplitWidget.enableShowModeSaving();
46     this._drawerSplitWidget.show(this.element);
48     this._tabbedPane = new WebInspector.TabbedPane();
49     this._tabbedPane.registerRequiredCSS("components/inspectorViewTabbedPane.css");
50     this._tabbedPane.element.classList.add("inspector-view-tabbed-pane");
51     this._tabbedPane.setTabSlider(true);
52     this._tabbedPane.setAllowTabReorder(true);
53     this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabOrderChanged, this._persistPanelOrder, this);
54     this._tabOrderSetting = WebInspector.settings.createSetting("InspectorView.panelOrder", {});
55     this._drawerSplitWidget.setMainWidget(this._tabbedPane);
56     this._drawer = new WebInspector.Drawer(this._drawerSplitWidget);
58     this._panels = {};
59     // Used by tests.
60     WebInspector["panels"] = this._panels;
62     this._history = [];
63     this._historyIterator = -1;
64     this._keyDownBound = this._keyDown.bind(this);
65     this._keyPressBound = this._keyPress.bind(this);
66     /** @type {!Object.<string, !WebInspector.PanelDescriptor>} */
67     this._panelDescriptors = {};
68     /** @type {!Object.<string, !Promise.<!WebInspector.Panel> >} */
69     this._panelPromises = {};
71     // Windows and Mac have two different definitions of '[' and ']', so accept both of each.
72     this._openBracketIdentifiers = ["U+005B", "U+00DB"].keySet();
73     this._closeBracketIdentifiers = ["U+005D", "U+00DD"].keySet();
74     this._lastActivePanelSetting = WebInspector.settings.createSetting("lastActivePanel", "elements");
76     InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Events.ShowConsole, showConsole.bind(this));
77     this._loadPanelDesciptors();
79     /**
80      * @this {WebInspector.InspectorView}
81      */
82     function showConsole()
83     {
84         this.showPanel("console");
85     }
87     WebInspector.targetManager.addEventListener(WebInspector.TargetManager.Events.SuspendStateChanged, this._onSuspendStateChanged.bind(this));
90 WebInspector.InspectorView.prototype = {
91     wasShown: function()
92     {
93         this.element.ownerDocument.addEventListener("keydown", this._keyDownBound, false);
94         this.element.ownerDocument.addEventListener("keypress", this._keyPressBound, false);
95     },
97     willHide: function()
98     {
99         this.element.ownerDocument.removeEventListener("keydown", this._keyDownBound, false);
100         this.element.ownerDocument.removeEventListener("keypress", this._keyPressBound, false);
101     },
103     _loadPanelDesciptors: function()
104     {
105         /**
106          * @param {!Runtime.Extension} extension
107          * @this {!WebInspector.InspectorView}
108          */
109         function processPanelExtensions(extension)
110         {
111             var descriptor = new WebInspector.RuntimeExtensionPanelDescriptor(extension);
112             var weight = this._tabOrderSetting.get()[descriptor.name()];
113             if (weight === undefined)
114                 weight = extension.descriptor()["order"];
115             if (weight === undefined)
116                 weight = 10000;
117             panelsByWeight.set(weight, descriptor);
118         }
120         WebInspector.startBatchUpdate();
121         /** @type {!Map.<number, !WebInspector.PanelDescriptor>} */
122         var panelsByWeight = new Map();
123         self.runtime.extensions(WebInspector.PanelFactory).forEach(processPanelExtensions.bind(this));
124         var sortedPanelOrders = panelsByWeight.keysArray().sort();
125         for (var order of sortedPanelOrders) {
126             var panelDescriptor = panelsByWeight.get(order);
127             if (panelDescriptor)
128                 this._innerAddPanel(panelDescriptor);
129         }
130         WebInspector.endBatchUpdate();
131     },
133     createToolbars: function()
134     {
135         this._leftToolbar = new WebInspector.ExtensibleToolbar("main-toolbar-left");
136         this._leftToolbar.element.classList.add("inspector-view-toolbar", "inspector-view-toolbar-left");
138         this._tabbedPane.insertBeforeTabStrip(this._leftToolbar.element);
140         var rightToolbarContainer = createElementWithClass("div", "hbox flex-none flex-centered");
141         this._tabbedPane.appendAfterTabStrip(rightToolbarContainer);
143         this._rightToolbar = new WebInspector.ExtensibleToolbar("main-toolbar-right");
144         this._rightToolbar.element.classList.add("inspector-view-toolbar", "flex-none");
145         rightToolbarContainer.appendChild(this._rightToolbar.element);
146     },
148     /**
149      * @param {!WebInspector.PanelDescriptor} panelDescriptor
150      * @param {number=} index
151      */
152     _innerAddPanel: function(panelDescriptor, index)
153     {
154         var panelName = panelDescriptor.name();
155         this._panelDescriptors[panelName] = panelDescriptor;
156         this._tabbedPane.appendTab(panelName, panelDescriptor.title(), new WebInspector.Widget(), undefined, undefined, undefined, index);
157         if (this._lastActivePanelSetting.get() === panelName)
158             this._tabbedPane.selectTab(panelName);
159     },
161     /**
162      * @param {!WebInspector.PanelDescriptor} panelDescriptor
163      */
164     addPanel: function(panelDescriptor)
165     {
166         var weight = this._tabOrderSetting.get()[panelDescriptor.name()];
167         this._innerAddPanel(panelDescriptor, weight);
168     },
170     /**
171      * @param {string} panelName
172      * @return {boolean}
173      */
174     hasPanel: function(panelName)
175     {
176         return !!this._panelDescriptors[panelName];
177     },
179     /**
180      * @param {string} panelName
181      * @return {!Promise.<!WebInspector.Panel>}
182      */
183     panel: function(panelName)
184     {
185         var panelDescriptor = this._panelDescriptors[panelName];
186         if (!panelDescriptor)
187             return Promise.reject(new Error("Can't load panel without the descriptor: " + panelName));
189         var promise = this._panelPromises[panelName];
190         if (promise)
191             return promise;
193         promise = panelDescriptor.panel();
194         this._panelPromises[panelName] = promise;
196         promise.then(cachePanel.bind(this));
198         /**
199          * @param {!WebInspector.Panel} panel
200          * @return {!WebInspector.Panel}
201          * @this {WebInspector.InspectorView}
202          */
203         function cachePanel(panel)
204         {
205             delete this._panelPromises[panelName];
206             this._panels[panelName] = panel;
207             return panel;
208         }
209         return promise;
210     },
212     /**
213      * @param {!WebInspector.Event} event
214      */
215     _onSuspendStateChanged: function(event)
216     {
217         this._currentPanelLocked = WebInspector.targetManager.allTargetsSuspended();
218         this._tabbedPane.setCurrentTabLocked(this._currentPanelLocked);
219         if (this._leftToolbar)
220             this._leftToolbar.setEnabled(!this._currentPanelLocked);
221         if (this._rightToolbar)
222             this._rightToolbar.setEnabled(!this._currentPanelLocked);
223     },
225     /**
226      * The returned Promise is resolved with null if another showPanel()
227      * gets called while this.panel(panelName) Promise is in flight.
228      *
229      * @param {string} panelName
230      * @return {!Promise.<?WebInspector.Panel>}
231      */
232     showPanel: function(panelName)
233     {
234         if (this._currentPanelLocked) {
235             if (this._currentPanel !== this._panels[panelName])
236                 return Promise.reject(new Error("Current panel locked"));
237             return Promise.resolve(this._currentPanel);
238         }
240         this._panelForShowPromise = this.panel(panelName);
241         return this._panelForShowPromise.then(setCurrentPanelIfNecessary.bind(this, this._panelForShowPromise));
243         /**
244          * @param {!Promise.<!WebInspector.Panel>} panelPromise
245          * @param {!WebInspector.Panel} panel
246          * @return {?WebInspector.Panel}
247          * @this {WebInspector.InspectorView}
248          */
249         function setCurrentPanelIfNecessary(panelPromise, panel)
250         {
251             if (this._panelForShowPromise !== panelPromise)
252                 return null;
254             this.setCurrentPanel(panel);
255             return panel;
256         }
257     },
259     /**
260      * @param {string} panelName
261      * @param {string} iconType
262      * @param {string=} iconTooltip
263      */
264     setPanelIcon: function(panelName, iconType, iconTooltip)
265     {
266         this._tabbedPane.setTabIcon(panelName, iconType, iconTooltip);
267     },
269     /**
270      * @return {!WebInspector.Panel}
271      */
272     currentPanel: function()
273     {
274         return this._currentPanel;
275     },
277     showInitialPanel: function()
278     {
279         if (InspectorFrontendHost.isUnderTest())
280             return;
281         this._showInitialPanel();
282     },
284     _showInitialPanel: function()
285     {
286         this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, this._tabSelected, this);
287         this._tabSelected();
288         this._drawer.initialPanelShown();
289     },
291     /**
292      * @param {string} panelName
293      */
294     showInitialPanelForTest: function(panelName)
295     {
296         this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, this._tabSelected, this);
297         this.setCurrentPanel(this._panels[panelName]);
298         this._drawer.initialPanelShown();
299     },
301     _tabSelected: function()
302     {
303         var panelName = this._tabbedPane.selectedTabId;
304         if (!panelName)
305             return;
307         this.showPanel(panelName);
308     },
310     /**
311      * @param {!WebInspector.Panel} panel
312      * @param {boolean=} suppressBringToFront
313      * @return {!WebInspector.Panel}
314      */
315     setCurrentPanel: function(panel, suppressBringToFront)
316     {
317         delete this._panelForShowPromise;
319         if (this._currentPanelLocked) {
320             console.error("Current panel is locked");
321             return this._currentPanel;
322         }
324         if (!suppressBringToFront)
325             InspectorFrontendHost.bringToFront();
327         if (this._currentPanel === panel)
328             return panel;
330         this._currentPanel = panel;
331         if (!this._panels[panel.name])
332             this._panels[panel.name] = panel;
333         this._tabbedPane.changeTabView(panel.name, panel);
334         this._tabbedPane.removeEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, this._tabSelected, this);
335         this._tabbedPane.selectTab(panel.name);
336         this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, this._tabSelected, this);
338         this._lastActivePanelSetting.set(panel.name);
339         this._pushToHistory(panel.name);
340         WebInspector.userMetrics.panelShown(panel.name);
341         panel.focus();
342         return panel;
343     },
345     /**
346      * @param {string} id
347      */
348     closeViewInDrawer: function(id)
349     {
350         this._drawer.closeView(id);
351     },
353     /**
354      * @param {string} id
355      * @param {string} title
356      * @param {!WebInspector.Widget} view
357      */
358     showCloseableViewInDrawer: function(id, title, view)
359     {
360         this._drawer.showCloseableView(id, title, view);
361     },
363     showDrawer: function()
364     {
365         this._drawer.showDrawer();
366     },
368     /**
369      * @return {boolean}
370      */
371     drawerVisible: function()
372     {
373         return this._drawer.isShowing();
374     },
376     /**
377      * @param {string} id
378      * @param {boolean=} immediate
379      */
380     showViewInDrawer: function(id, immediate)
381     {
382         this._drawer.showView(id, immediate);
383     },
385     /**
386      * @return {?string}
387      */
388     selectedViewInDrawer: function()
389     {
390         return this._drawer.selectedViewId();
391     },
393     closeDrawer: function()
394     {
395         this._drawer.closeDrawer();
396     },
398     /**
399      * @override
400      * @return {!Element}
401      */
402     defaultFocusedElement: function()
403     {
404         return this._currentPanel ? this._currentPanel.defaultFocusedElement() : null;
405     },
407     _keyPress: function(event)
408     {
409         // BUG 104250: Windows 7 posts a WM_CHAR message upon the Ctrl+']' keypress.
410         // Any charCode < 32 is not going to be a valid keypress.
411         if (event.charCode < 32 && WebInspector.isWin())
412             return;
413         clearTimeout(this._keyDownTimer);
414         delete this._keyDownTimer;
415     },
417     _keyDown: function(event)
418     {
419         if (!WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event))
420             return;
422         var keyboardEvent = /** @type {!KeyboardEvent} */ (event);
423         // Ctrl/Cmd + 1-9 should show corresponding panel.
424         var panelShortcutEnabled = WebInspector.moduleSetting("shortcutPanelSwitch").get();
425         if (panelShortcutEnabled && !event.shiftKey && !event.altKey) {
426             var panelIndex = -1;
427             if (event.keyCode > 0x30 && event.keyCode < 0x3A)
428                 panelIndex = event.keyCode - 0x31;
429             else if (event.keyCode > 0x60 && event.keyCode < 0x6A && keyboardEvent.location === KeyboardEvent.DOM_KEY_LOCATION_NUMPAD)
430                 panelIndex = event.keyCode - 0x61;
431             if (panelIndex !== -1) {
432                 var panelName = this._tabbedPane.allTabs()[panelIndex];
433                 if (panelName) {
434                     if (!WebInspector.Dialog.currentInstance() && !this._currentPanelLocked)
435                         this.showPanel(panelName);
436                     event.consume(true);
437                 }
438                 return;
439             }
440         }
442         // BUG85312: On French AZERTY keyboards, AltGr-]/[ combinations (synonymous to Ctrl-Alt-]/[ on Windows) are used to enter ]/[,
443         // so for a ]/[-related keydown we delay the panel switch using a timer, to see if there is a keypress event following this one.
444         // If there is, we cancel the timer and do not consider this a panel switch.
445         if (!WebInspector.isWin() || (!this._openBracketIdentifiers[event.keyIdentifier] && !this._closeBracketIdentifiers[event.keyIdentifier])) {
446             this._keyDownInternal(event);
447             return;
448         }
450         this._keyDownTimer = setTimeout(this._keyDownInternal.bind(this, event), 0);
451     },
453     _keyDownInternal: function(event)
454     {
455         if (this._currentPanelLocked)
456             return;
458         var direction = 0;
460         if (this._openBracketIdentifiers[event.keyIdentifier])
461             direction = -1;
463         if (this._closeBracketIdentifiers[event.keyIdentifier])
464             direction = 1;
466         if (!direction)
467             return;
469         if (!event.shiftKey && !event.altKey) {
470             if (!WebInspector.Dialog.currentInstance())
471                 this._changePanelInDirection(direction);
472             event.consume(true);
473             return;
474         }
476         if (event.altKey && this._moveInHistory(direction))
477             event.consume(true);
478     },
480     /**
481      * @param {number} direction
482      */
483     _changePanelInDirection: function(direction)
484     {
485         var panelOrder = this._tabbedPane.allTabs();
486         var index = panelOrder.indexOf(this.currentPanel().name);
487         index = (index + panelOrder.length + direction) % panelOrder.length;
488         this.showPanel(panelOrder[index]);
489     },
491     /**
492      * @param {number} move
493      */
494     _moveInHistory: function(move)
495     {
496         var newIndex = this._historyIterator + move;
497         if (newIndex >= this._history.length || newIndex < 0)
498             return false;
500         this._inHistory = true;
501         this._historyIterator = newIndex;
502         if (!WebInspector.Dialog.currentInstance())
503             this.setCurrentPanel(this._panels[this._history[this._historyIterator]]);
504         delete this._inHistory;
506         return true;
507     },
509     _pushToHistory: function(panelName)
510     {
511         if (this._inHistory)
512             return;
514         this._history.splice(this._historyIterator + 1, this._history.length - this._historyIterator - 1);
515         if (!this._history.length || this._history[this._history.length - 1] !== panelName)
516             this._history.push(panelName);
517         this._historyIterator = this._history.length - 1;
518     },
520     onResize: function()
521     {
522         WebInspector.Dialog.modalHostRepositioned();
523     },
525     /**
526      * @return {!Element}
527      */
528     topResizerElement: function()
529     {
530         return this._tabbedPane.headerElement();
531     },
533     toolbarItemResized: function()
534     {
535         this._tabbedPane.headerResized();
536     },
538     /**
539      * @param {!WebInspector.Event} event
540      */
541     _persistPanelOrder: function(event)
542     {
543         var tabs = /** @type {!Array.<!WebInspector.TabbedPaneTab>} */(event.data);
544         var tabOrders = this._tabOrderSetting.get();
545         for (var i = 0; i < tabs.length; i++)
546             tabOrders[tabs[i].id] = i;
547         this._tabOrderSetting.set(tabOrders);
548     },
550     __proto__: WebInspector.VBox.prototype
554  * @type {!WebInspector.InspectorView}
555  */
556 WebInspector.inspectorView;
559  * @constructor
560  * @implements {WebInspector.ActionDelegate}
561  */
562 WebInspector.InspectorView.DrawerToggleActionDelegate = function()
566 WebInspector.InspectorView.DrawerToggleActionDelegate.prototype = {
567     /**
568      * @override
569      * @param {!WebInspector.Context} context
570      * @param {string} actionId
571      */
572     handleAction: function(context, actionId)
573     {
574         if (WebInspector.inspectorView.drawerVisible())
575             WebInspector.inspectorView.closeDrawer();
576         else
577             WebInspector.inspectorView.showDrawer();
578     }