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 */
30
31/**
32 * @constructor
33 * @extends {WebInspector.VBox}
34 */
35WebInspector.InspectorView = function()
36{
37    WebInspector.VBox.call(this);
38    WebInspector.Dialog.setModalHostView(this);
39    WebInspector.GlassPane.DefaultFocusedViewStack.push(this);
40    this.setMinimumSize(180, 72);
41
42    // DevTools sidebar is a vertical split of panels tabbed pane and a drawer.
43    this._drawerSplitView = new WebInspector.SplitView(false, true, "Inspector.drawerSplitViewState", 200, 200);
44    this._drawerSplitView.hideSidebar();
45    this._drawerSplitView.enableShowModeSaving();
46    this._drawerSplitView.show(this.element);
47
48    this._tabbedPane = new WebInspector.TabbedPane();
49    this._tabbedPane.setRetainTabOrder(true, self.runtime.orderComparator(WebInspector.Panel, "name", "order"));
50    this._tabbedPane.show(this._drawerSplitView.mainElement());
51    this._drawer = new WebInspector.Drawer(this._drawerSplitView);
52
53    // Patch tabbed pane header with toolbar actions.
54    this._toolbarElement = document.createElement("div");
55    this._toolbarElement.className = "toolbar toolbar-background toolbar-colors";
56    var headerElement = this._tabbedPane.headerElement();
57    headerElement.parentElement.insertBefore(this._toolbarElement, headerElement);
58
59    this._leftToolbarElement = this._toolbarElement.createChild("div", "toolbar-controls-left");
60    this._toolbarElement.appendChild(headerElement);
61    this._rightToolbarElement = this._toolbarElement.createChild("div", "toolbar-controls-right");
62    this._toolbarItems = [];
63
64    this._closeButtonToolbarItem = document.createElementWithClass("div", "toolbar-close-button-item");
65    var closeButtonElement = this._closeButtonToolbarItem.createChild("div", "close-button");
66    closeButtonElement.addEventListener("click", InspectorFrontendHost.closeWindow.bind(InspectorFrontendHost), true);
67    this._rightToolbarElement.appendChild(this._closeButtonToolbarItem);
68
69    this._panels = {};
70    // Used by tests.
71    WebInspector["panels"] = this._panels;
72
73    this._history = [];
74    this._historyIterator = -1;
75    document.addEventListener("keydown", this._keyDown.bind(this), false);
76    document.addEventListener("keypress", this._keyPress.bind(this), false);
77    this._panelDescriptors = {};
78
79    // Windows and Mac have two different definitions of '[' and ']', so accept both of each.
80    this._openBracketIdentifiers = ["U+005B", "U+00DB"].keySet();
81    this._closeBracketIdentifiers = ["U+005D", "U+00DD"].keySet();
82    this._lastActivePanelSetting = WebInspector.settings.createSetting("lastActivePanel", "elements");
83
84    // FIXME(399531): enable timelineOnTraceEvents experiment when running layout tests under inspector/tracing/. This code
85    // should be removed along with the old Timeline implementation once we move tracing based Timeline out of experimental.
86    if ("tracing" === this._lastActivePanelSetting.get()) {
87        Runtime.experiments.setEnabled("timelineOnTraceEvents", true);
88        this._lastActivePanelSetting.set("timeline");
89    }
90
91    this._loadPanelDesciptors();
92
93    InspectorFrontendHost.events.addEventListener(InspectorFrontendHostAPI.Events.ShowConsole, this.showPanel.bind(this, "console"));
94};
95
96WebInspector.InspectorView.prototype = {
97    _loadPanelDesciptors: function()
98    {
99        WebInspector.startBatchUpdate();
100        self.runtime.extensions(WebInspector.Panel).forEach(processPanelExtensions.bind(this));
101        /**
102         * @param {!Runtime.Extension} extension
103         * @this {!WebInspector.InspectorView}
104         */
105        function processPanelExtensions(extension)
106        {
107            this.addPanel(new WebInspector.RuntimeExtensionPanelDescriptor(extension));
108        }
109        WebInspector.endBatchUpdate();
110    },
111
112    /**
113     * @param {!WebInspector.StatusBarItem} item
114     */
115    appendToLeftToolbar: function(item)
116    {
117        this._toolbarItems.push(item);
118        this._leftToolbarElement.appendChild(item.element);
119    },
120
121    /**
122     * @param {!WebInspector.StatusBarItem} item
123     */
124    appendToRightToolbar: function(item)
125    {
126        this._toolbarItems.push(item);
127        this._rightToolbarElement.insertBefore(item.element, this._closeButtonToolbarItem);
128    },
129
130    /**
131     * @param {!WebInspector.PanelDescriptor} panelDescriptor
132     */
133    addPanel: function(panelDescriptor)
134    {
135        var panelName = panelDescriptor.name();
136        this._panelDescriptors[panelName] = panelDescriptor;
137        this._tabbedPane.appendTab(panelName, panelDescriptor.title(), new WebInspector.View());
138        if (this._lastActivePanelSetting.get() === panelName)
139            this._tabbedPane.selectTab(panelName);
140    },
141
142    /**
143     * @param {string} panelName
144     * @return {boolean}
145     */
146    hasPanel: function(panelName)
147    {
148        return !!this._panelDescriptors[panelName];
149    },
150
151    /**
152     * @param {string} panelName
153     * @return {?WebInspector.Panel}
154     */
155    panel: function(panelName)
156    {
157        var panelDescriptor = this._panelDescriptors[panelName];
158        var panelOrder = this._tabbedPane.allTabs();
159        if (!panelDescriptor && panelOrder.length)
160            panelDescriptor = this._panelDescriptors[panelOrder[0]];
161        var panel = panelDescriptor ? panelDescriptor.panel() : null;
162        if (panel)
163            this._panels[panelName] = panel;
164        return panel;
165    },
166
167    /**
168     * @param {boolean} locked
169     */
170    setCurrentPanelLocked: function(locked)
171    {
172        this._currentPanelLocked = locked;
173        this._tabbedPane.setCurrentTabLocked(locked);
174        for (var i = 0; i < this._toolbarItems.length; ++i)
175            this._toolbarItems[i].setEnabled(!locked);
176    },
177
178    /**
179     * @param {string} panelName
180     * @return {?WebInspector.Panel}
181     */
182    showPanel: function(panelName)
183    {
184        if (this._currentPanelLocked)
185            return this._currentPanel === this._panels[panelName] ? this._currentPanel : null;
186
187        var panel = this.panel(panelName);
188        if (panel)
189            this.setCurrentPanel(panel);
190        return panel;
191    },
192
193    /**
194     * @return {!WebInspector.Panel}
195     */
196    currentPanel: function()
197    {
198        return this._currentPanel;
199    },
200
201    showInitialPanel: function()
202    {
203        this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, this._tabSelected, this);
204        this._tabSelected();
205        this._drawer.initialPanelShown();
206    },
207
208    _tabSelected: function()
209    {
210        var panelName = this._tabbedPane.selectedTabId;
211        if (!panelName)
212            return;
213        var panel = this._panelDescriptors[this._tabbedPane.selectedTabId].panel();
214        this._panels[panelName] = panel;
215        this._tabbedPane.changeTabView(panelName, panel);
216
217        this._currentPanel = panel;
218        this._lastActivePanelSetting.set(panel.name);
219        this._pushToHistory(panel.name);
220        WebInspector.userMetrics.panelShown(panel.name);
221        panel.focus();
222    },
223
224    /**
225     * @param {!WebInspector.Panel} x
226     */
227    setCurrentPanel: function(x)
228    {
229        if (this._currentPanelLocked)
230            return;
231        InspectorFrontendHost.bringToFront();
232        if (this._currentPanel === x)
233            return;
234
235        this._tabbedPane.changeTabView(x.name, x);
236        this._tabbedPane.selectTab(x.name);
237    },
238
239    /**
240     * @param {string} id
241     */
242    closeViewInDrawer: function(id)
243    {
244        this._drawer.closeView(id);
245    },
246
247    /**
248     * @param {string} id
249     * @param {string} title
250     * @param {!WebInspector.View} view
251     */
252    showCloseableViewInDrawer: function(id, title, view)
253    {
254        this._drawer.showCloseableView(id, title, view);
255    },
256
257    showDrawer: function()
258    {
259        this._drawer.showDrawer();
260    },
261
262    /**
263     * @return {boolean}
264     */
265    drawerVisible: function()
266    {
267        return this._drawer.isShowing();
268    },
269
270    /**
271     * @param {string} id
272     * @param {boolean=} immediate
273     */
274    showViewInDrawer: function(id, immediate)
275    {
276        this._drawer.showView(id, immediate);
277    },
278
279    /**
280     * @return {?string}
281     */
282    selectedViewInDrawer: function()
283    {
284        return this._drawer.selectedViewId();
285    },
286
287    closeDrawer: function()
288    {
289        this._drawer.closeDrawer();
290    },
291
292    /**
293     * @return {!Element}
294     */
295    defaultFocusedElement: function()
296    {
297        return this._currentPanel ? this._currentPanel.defaultFocusedElement() : null;
298    },
299
300    _keyPress: function(event)
301    {
302        // BUG 104250: Windows 7 posts a WM_CHAR message upon the Ctrl+']' keypress.
303        // Any charCode < 32 is not going to be a valid keypress.
304        if (event.charCode < 32 && WebInspector.isWin())
305            return;
306        clearTimeout(this._keyDownTimer);
307        delete this._keyDownTimer;
308    },
309
310    _keyDown: function(event)
311    {
312        if (!WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event))
313            return;
314
315        var keyboardEvent = /** @type {!KeyboardEvent} */ (event);
316        // Ctrl/Cmd + 1-9 should show corresponding panel.
317        var panelShortcutEnabled = WebInspector.settings.shortcutPanelSwitch.get();
318        if (panelShortcutEnabled && !event.shiftKey && !event.altKey) {
319            var panelIndex = -1;
320            if (event.keyCode > 0x30 && event.keyCode < 0x3A)
321                panelIndex = event.keyCode - 0x31;
322            else if (event.keyCode > 0x60 && event.keyCode < 0x6A && keyboardEvent.location === KeyboardEvent.DOM_KEY_LOCATION_NUMPAD)
323                panelIndex = event.keyCode - 0x61;
324            if (panelIndex !== -1) {
325                var panelName = this._tabbedPane.allTabs()[panelIndex];
326                if (panelName) {
327                    if (!WebInspector.Dialog.currentInstance() && !this._currentPanelLocked)
328                        this.showPanel(panelName);
329                    event.consume(true);
330                }
331                return;
332            }
333        }
334
335        // BUG85312: On French AZERTY keyboards, AltGr-]/[ combinations (synonymous to Ctrl-Alt-]/[ on Windows) are used to enter ]/[,
336        // so for a ]/[-related keydown we delay the panel switch using a timer, to see if there is a keypress event following this one.
337        // If there is, we cancel the timer and do not consider this a panel switch.
338        if (!WebInspector.isWin() || (!this._openBracketIdentifiers[event.keyIdentifier] && !this._closeBracketIdentifiers[event.keyIdentifier])) {
339            this._keyDownInternal(event);
340            return;
341        }
342
343        this._keyDownTimer = setTimeout(this._keyDownInternal.bind(this, event), 0);
344    },
345
346    _keyDownInternal: function(event)
347    {
348        if (this._currentPanelLocked)
349            return;
350
351        var direction = 0;
352
353        if (this._openBracketIdentifiers[event.keyIdentifier])
354            direction = -1;
355
356        if (this._closeBracketIdentifiers[event.keyIdentifier])
357            direction = 1;
358
359        if (!direction)
360            return;
361
362        if (!event.shiftKey && !event.altKey) {
363            if (!WebInspector.Dialog.currentInstance())
364                this._changePanelInDirection(direction);
365            event.consume(true);
366            return;
367        }
368
369        if (event.altKey && this._moveInHistory(direction))
370            event.consume(true)
371    },
372
373    _changePanelInDirection: function(direction)
374    {
375        var panelOrder = this._tabbedPane.allTabs();
376        var index = panelOrder.indexOf(this.currentPanel().name);
377        index = (index + panelOrder.length + direction) % panelOrder.length;
378        this.showPanel(panelOrder[index]);
379    },
380
381    _moveInHistory: function(move)
382    {
383        var newIndex = this._historyIterator + move;
384        if (newIndex >= this._history.length || newIndex < 0)
385            return false;
386
387        this._inHistory = true;
388        this._historyIterator = newIndex;
389        if (!WebInspector.Dialog.currentInstance())
390            this.setCurrentPanel(this._panels[this._history[this._historyIterator]]);
391        delete this._inHistory;
392
393        return true;
394    },
395
396    _pushToHistory: function(panelName)
397    {
398        if (this._inHistory)
399            return;
400
401        this._history.splice(this._historyIterator + 1, this._history.length - this._historyIterator - 1);
402        if (!this._history.length || this._history[this._history.length - 1] !== panelName)
403            this._history.push(panelName);
404        this._historyIterator = this._history.length - 1;
405    },
406
407    onResize: function()
408    {
409        WebInspector.Dialog.modalHostRepositioned();
410    },
411
412    /**
413     * @return {!Element}
414     */
415    topResizerElement: function()
416    {
417        return this._tabbedPane.headerElement();
418    },
419
420    toolbarItemResized: function()
421    {
422        this._tabbedPane.headerResized();
423    },
424
425    __proto__: WebInspector.VBox.prototype
426};
427
428/**
429 * @type {!WebInspector.InspectorView}
430 */
431WebInspector.inspectorView;
432
433/**
434 * @constructor
435 * @implements {WebInspector.ActionDelegate}
436 */
437WebInspector.InspectorView.DrawerToggleActionDelegate = function()
438{
439}
440
441WebInspector.InspectorView.DrawerToggleActionDelegate.prototype = {
442    /**
443     * @return {boolean}
444     */
445    handleAction: function()
446    {
447        if (WebInspector.inspectorView.drawerVisible()) {
448            WebInspector.inspectorView.closeDrawer();
449            return true;
450        }
451        WebInspector.inspectorView.showDrawer();
452        return true;
453    }
454}
455
456/**
457 * @constructor
458 * @implements {WebInspector.StatusBarItem.Provider}
459 */
460WebInspector.InspectorView.ToggleDrawerButtonProvider = function()
461{
462}
463
464WebInspector.InspectorView.ToggleDrawerButtonProvider.prototype = {
465    /**
466     * @return {?WebInspector.StatusBarItem}
467     */
468    item: function()
469    {
470        return WebInspector.inspectorView._drawer.toggleButton();
471    }
472}
473