• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.unshift(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, WebInspector.moduleManager.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";
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
63    if (WebInspector.experimentsSettings.devicesPanel.isEnabled()) {
64        this._remoteDeviceCountElement = this._rightToolbarElement.createChild("div", "hidden");
65        this._remoteDeviceCountElement.addEventListener("click", this.showViewInDrawer.bind(this, "devices", true), false);
66        this._remoteDeviceCountElement.id = "remote-device-count";
67        WebInspector.inspectorFrontendEventSink.addEventListener(WebInspector.InspectorView.Events.DeviceCountChanged, this._onDeviceCountChanged, this);
68    }
69
70    this._errorWarningCountElement = this._rightToolbarElement.createChild("div", "hidden");
71    this._errorWarningCountElement.id = "error-warning-count";
72
73    this._closeButtonToolbarItem = document.createElementWithClass("div", "toolbar-close-button-item");
74    var closeButtonElement = this._closeButtonToolbarItem.createChild("div", "close-button");
75    closeButtonElement.addEventListener("click", InspectorFrontendHost.closeWindow.bind(InspectorFrontendHost), true);
76    this._rightToolbarElement.appendChild(this._closeButtonToolbarItem);
77
78    this.appendToRightToolbar(this._drawer.toggleButtonElement());
79
80    this._panels = {};
81    // Used by tests.
82    WebInspector["panels"] = this._panels;
83
84    this._history = [];
85    this._historyIterator = -1;
86    document.addEventListener("keydown", this._keyDown.bind(this), false);
87    document.addEventListener("keypress", this._keyPress.bind(this), false);
88    this._panelDescriptors = {};
89
90    // Windows and Mac have two different definitions of '[' and ']', so accept both of each.
91    this._openBracketIdentifiers = ["U+005B", "U+00DB"].keySet();
92    this._closeBracketIdentifiers = ["U+005D", "U+00DD"].keySet();
93    this._lastActivePanelSetting = WebInspector.settings.createSetting("lastActivePanel", "elements");
94
95    this._loadPanelDesciptors();
96};
97
98WebInspector.InspectorView.Events = {
99    DeviceCountChanged: "DeviceCountChanged"
100}
101
102WebInspector.InspectorView.prototype = {
103    _loadPanelDesciptors: function()
104    {
105        WebInspector.startBatchUpdate();
106        WebInspector.moduleManager.extensions(WebInspector.Panel).forEach(processPanelExtensions.bind(this));
107        /**
108         * @param {!WebInspector.ModuleManager.Extension} extension
109         * @this {!WebInspector.InspectorView}
110         */
111        function processPanelExtensions(extension)
112        {
113            this.addPanel(new WebInspector.ModuleManagerExtensionPanelDescriptor(extension));
114        }
115        WebInspector.endBatchUpdate();
116    },
117
118    /**
119     * @param {!Element} element
120     */
121    appendToLeftToolbar: function(element)
122    {
123        this._leftToolbarElement.appendChild(element);
124    },
125
126    /**
127     * @param {!Element} element
128     */
129    appendToRightToolbar: function(element)
130    {
131        this._rightToolbarElement.insertBefore(element, this._closeButtonToolbarItem);
132    },
133
134    /**
135     * @param {!WebInspector.PanelDescriptor} panelDescriptor
136     */
137    addPanel: function(panelDescriptor)
138    {
139        var panelName = panelDescriptor.name();
140        this._panelDescriptors[panelName] = panelDescriptor;
141        this._tabbedPane.appendTab(panelName, panelDescriptor.title(), new WebInspector.View());
142        if (this._lastActivePanelSetting.get() === panelName)
143            this._tabbedPane.selectTab(panelName);
144    },
145
146    /**
147     * @param {string} panelName
148     * @return {boolean}
149     */
150    hasPanel: function(panelName)
151    {
152        return !!this._panelDescriptors[panelName];
153    },
154
155    /**
156     * @param {string} panelName
157     * @return {?WebInspector.Panel}
158     */
159    panel: function(panelName)
160    {
161        var panelDescriptor = this._panelDescriptors[panelName];
162        var panelOrder = this._tabbedPane.allTabs();
163        if (!panelDescriptor && panelOrder.length)
164            panelDescriptor = this._panelDescriptors[panelOrder[0]];
165        var panel = panelDescriptor ? panelDescriptor.panel() : null;
166        if (panel)
167            this._panels[panelName] = panel;
168        return panel;
169    },
170
171    /**
172     * @param {string} panelName
173     * @return {?WebInspector.Panel}
174     */
175    showPanel: function(panelName)
176    {
177        var panel = this.panel(panelName);
178        if (panel)
179            this.setCurrentPanel(panel);
180        return panel;
181    },
182
183    /**
184     * @return {!WebInspector.Panel}
185     */
186    currentPanel: function()
187    {
188        return this._currentPanel;
189    },
190
191    showInitialPanel: function()
192    {
193        this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, this._tabSelected, this);
194        this._tabSelected();
195        this._drawer.initialPanelShown();
196    },
197
198    showDrawerEditor: function()
199    {
200        this._drawer.showDrawerEditor();
201    },
202
203    /**
204     * @return {boolean}
205     */
206    isDrawerEditorShown: function()
207    {
208        return this._drawer.isDrawerEditorShown();
209    },
210
211    hideDrawerEditor: function()
212    {
213        this._drawer.hideDrawerEditor();
214    },
215
216    /**
217     * @param {boolean} available
218     */
219    setDrawerEditorAvailable: function(available)
220    {
221        this._drawer.setDrawerEditorAvailable(available);
222    },
223
224    _tabSelected: function()
225    {
226        var panelName = this._tabbedPane.selectedTabId;
227        if (!panelName)
228            return;
229        var panel = this._panelDescriptors[this._tabbedPane.selectedTabId].panel();
230        this._panels[panelName] = panel;
231        this._tabbedPane.changeTabView(panelName, panel);
232
233        this._currentPanel = panel;
234        this._lastActivePanelSetting.set(panel.name);
235        this._pushToHistory(panel.name);
236        WebInspector.userMetrics.panelShown(panel.name);
237        panel.focus();
238    },
239
240    /**
241     * @param {!WebInspector.Panel} x
242     */
243    setCurrentPanel: function(x)
244    {
245        if (this._currentPanel === x)
246            return;
247
248        this._tabbedPane.changeTabView(x.name, x);
249        this._tabbedPane.selectTab(x.name);
250    },
251
252    /**
253     * @param {string} id
254     */
255    closeViewInDrawer: function(id)
256    {
257        this._drawer.closeView(id);
258    },
259
260    /**
261     * @param {string} id
262     * @param {string} title
263     * @param {!WebInspector.View} view
264     */
265    showCloseableViewInDrawer: function(id, title, view)
266    {
267        this._drawer.showCloseableView(id, title, view);
268    },
269
270    showDrawer: function()
271    {
272        this._drawer.showDrawer();
273    },
274
275    /**
276     * @return {boolean}
277     */
278    drawerVisible: function()
279    {
280        return this._drawer.isShowing();
281    },
282
283    /**
284     * @param {string} id
285     * @param {boolean=} immediate
286     */
287    showViewInDrawer: function(id, immediate)
288    {
289        this._drawer.showView(id, immediate);
290    },
291
292    /**
293     * @return {?string}
294     */
295    selectedViewInDrawer: function()
296    {
297        return this._drawer.selectedViewId();
298    },
299
300    closeDrawer: function()
301    {
302        this._drawer.closeDrawer();
303    },
304
305    /**
306     * @return {!Element}
307     */
308    defaultFocusedElement: function()
309    {
310        return this._currentPanel ? this._currentPanel.defaultFocusedElement() : null;
311    },
312
313    _keyPress: function(event)
314    {
315        // BUG 104250: Windows 7 posts a WM_CHAR message upon the Ctrl+']' keypress.
316        // Any charCode < 32 is not going to be a valid keypress.
317        if (event.charCode < 32 && WebInspector.isWin())
318            return;
319        clearTimeout(this._keyDownTimer);
320        delete this._keyDownTimer;
321    },
322
323    _keyDown: function(event)
324    {
325        if (!WebInspector.KeyboardShortcut.eventHasCtrlOrMeta(event))
326            return;
327
328        var keyboardEvent = /** @type {!KeyboardEvent} */ (event);
329        // Ctrl/Cmd + 1-9 should show corresponding panel.
330        var panelShortcutEnabled = WebInspector.settings.shortcutPanelSwitch.get();
331        if (panelShortcutEnabled && !event.shiftKey && !event.altKey) {
332            var panelIndex = -1;
333            if (event.keyCode > 0x30 && event.keyCode < 0x3A)
334                panelIndex = event.keyCode - 0x31;
335            else if (event.keyCode > 0x60 && event.keyCode < 0x6A && keyboardEvent.location === KeyboardEvent.DOM_KEY_LOCATION_NUMPAD)
336                panelIndex = event.keyCode - 0x61;
337            if (panelIndex !== -1) {
338                var panelName = this._tabbedPane.allTabs()[panelIndex];
339                if (panelName) {
340                    if (!WebInspector.Dialog.currentInstance())
341                        this.showPanel(panelName);
342                    event.consume(true);
343                }
344                return;
345            }
346        }
347
348        // BUG85312: On French AZERTY keyboards, AltGr-]/[ combinations (synonymous to Ctrl-Alt-]/[ on Windows) are used to enter ]/[,
349        // so for a ]/[-related keydown we delay the panel switch using a timer, to see if there is a keypress event following this one.
350        // If there is, we cancel the timer and do not consider this a panel switch.
351        if (!WebInspector.isWin() || (!this._openBracketIdentifiers[event.keyIdentifier] && !this._closeBracketIdentifiers[event.keyIdentifier])) {
352            this._keyDownInternal(event);
353            return;
354        }
355
356        this._keyDownTimer = setTimeout(this._keyDownInternal.bind(this, event), 0);
357    },
358
359    _keyDownInternal: function(event)
360    {
361        var direction = 0;
362
363        if (this._openBracketIdentifiers[event.keyIdentifier])
364            direction = -1;
365
366        if (this._closeBracketIdentifiers[event.keyIdentifier])
367            direction = 1;
368
369        if (!direction)
370            return;
371
372        if (!event.shiftKey && !event.altKey) {
373            if (!WebInspector.Dialog.currentInstance())
374                this._changePanelInDirection(direction);
375            event.consume(true);
376            return;
377        }
378
379        if (event.altKey && this._moveInHistory(direction))
380            event.consume(true)
381    },
382
383    _changePanelInDirection: function(direction)
384    {
385        var panelOrder = this._tabbedPane.allTabs();
386        var index = panelOrder.indexOf(this.currentPanel().name);
387        index = (index + panelOrder.length + direction) % panelOrder.length;
388        this.showPanel(panelOrder[index]);
389    },
390
391    _moveInHistory: function(move)
392    {
393        var newIndex = this._historyIterator + move;
394        if (newIndex >= this._history.length || newIndex < 0)
395            return false;
396
397        this._inHistory = true;
398        this._historyIterator = newIndex;
399        if (!WebInspector.Dialog.currentInstance())
400            this.setCurrentPanel(this._panels[this._history[this._historyIterator]]);
401        delete this._inHistory;
402
403        return true;
404    },
405
406    _pushToHistory: function(panelName)
407    {
408        if (this._inHistory)
409            return;
410
411        this._history.splice(this._historyIterator + 1, this._history.length - this._historyIterator - 1);
412        if (!this._history.length || this._history[this._history.length - 1] !== panelName)
413            this._history.push(panelName);
414        this._historyIterator = this._history.length - 1;
415    },
416
417    onResize: function()
418    {
419        WebInspector.Dialog.modalHostRepositioned();
420    },
421
422    /**
423     * @return {!Element}
424     */
425    topResizerElement: function()
426    {
427        return this._tabbedPane.headerElement();
428    },
429
430    _createImagedCounterElementIfNeeded: function(parent, count, id, styleName)
431    {
432        if (!count)
433            return;
434
435        var imageElement = parent.createChild("div", styleName);
436        var counterElement = parent.createChild("span");
437        counterElement.id = id;
438        counterElement.textContent = count;
439    },
440
441    /**
442     * @param {number} errors
443     * @param {number} warnings
444     */
445    setErrorAndWarningCounts: function(errors, warnings)
446    {
447        if (this._errors === errors && this._warnings === warnings)
448            return;
449        this._errors = errors;
450        this._warnings = warnings;
451        this._errorWarningCountElement.classList.toggle("hidden", !errors && !warnings);
452        this._errorWarningCountElement.removeChildren();
453
454        this._createImagedCounterElementIfNeeded(this._errorWarningCountElement, errors, "error-count", "error-icon-small");
455        this._createImagedCounterElementIfNeeded(this._errorWarningCountElement, warnings, "warning-count", "warning-icon-small");
456
457        var errorString = errors ?  WebInspector.UIString("%d error%s", errors, errors > 1 ? "s" : "") : "";
458        var warningString = warnings ?  WebInspector.UIString("%d warning%s", warnings, warnings > 1 ? "s" : "") : "";
459        var commaString = errors && warnings ? ", " : "";
460        this._errorWarningCountElement.title = errorString + commaString + warningString;
461        this._tabbedPane.headerResized();
462    },
463
464    /**
465     * @param {!WebInspector.Event} event
466     */
467    _onDeviceCountChanged: function(event)
468    {
469        var count = /** @type {number} */ (event.data);
470        if (count === this.deviceCount_)
471            return;
472        this.deviceCount_ = count;
473        this._remoteDeviceCountElement.classList.toggle("hidden", !count);
474        this._remoteDeviceCountElement.removeChildren();
475        this._createImagedCounterElementIfNeeded(this._remoteDeviceCountElement, count, "device-count", "device-icon-small");
476        this._remoteDeviceCountElement.title = WebInspector.UIString(((count > 1) ? "%d devices found" : "%d device found"), count);
477        this._tabbedPane.headerResized();
478    },
479
480    __proto__: WebInspector.VBox.prototype
481};
482
483/**
484 * @type {!WebInspector.InspectorView}
485 */
486WebInspector.inspectorView;
487
488/**
489 * @constructor
490 * @implements {WebInspector.ActionDelegate}
491 */
492WebInspector.InspectorView.DrawerToggleActionDelegate = function()
493{
494}
495
496WebInspector.InspectorView.DrawerToggleActionDelegate.prototype = {
497    /**
498     * @return {boolean}
499     */
500    handleAction: function()
501    {
502        if (WebInspector.inspectorView.drawerVisible()) {
503            WebInspector.inspectorView.closeDrawer();
504            return true;
505        }
506        WebInspector.inspectorView.showDrawer();
507        return true;
508    }
509}
510
511/**
512 * @constructor
513 * @extends {WebInspector.VBox}
514 */
515WebInspector.RootView = function()
516{
517    WebInspector.VBox.call(this);
518    this.markAsRoot();
519    this.element.classList.add("root-view");
520    this.element.setAttribute("spellcheck", false);
521    window.addEventListener("resize", this.doResize.bind(this), true);
522    this._onScrollBound = this._onScroll.bind(this);
523};
524
525WebInspector.RootView.prototype = {
526    attachToBody: function()
527    {
528        this.doResize();
529        this.show(document.body);
530    },
531
532    _onScroll: function()
533    {
534        // If we didn't have enough space at the start, we may have wrong scroll offsets.
535        if (document.body.scrollTop !== 0)
536            document.body.scrollTop = 0;
537        if (document.body.scrollLeft !== 0)
538            document.body.scrollLeft = 0;
539    },
540
541    doResize: function()
542    {
543        var size = this.constraints().minimum;
544        var zoom = WebInspector.zoomManager.zoomFactor();
545        var right = Math.min(0, window.innerWidth - size.width / zoom);
546        this.element.style.right = right + "px";
547        var bottom = Math.min(0, window.innerHeight - size.height / zoom);
548        this.element.style.bottom = bottom + "px";
549
550        if (window.innerWidth < size.width || window.innerHeight < size.height)
551            window.addEventListener("scroll", this._onScrollBound, false);
552        else
553            window.removeEventListener("scroll", this._onScrollBound, false);
554
555        WebInspector.VBox.prototype.doResize.call(this);
556        this._onScroll();
557    },
558
559    __proto__: WebInspector.VBox.prototype
560};
561