Home | History | Annotate | Download | only in components
      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  */
     35 WebInspector.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 
     98 WebInspector.InspectorView.Events = {
     99     DeviceCountChanged: "DeviceCountChanged"
    100 }
    101 
    102 WebInspector.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  */
    486 WebInspector.inspectorView;
    487 
    488 /**
    489  * @constructor
    490  * @implements {WebInspector.ActionDelegate}
    491  */
    492 WebInspector.InspectorView.DrawerToggleActionDelegate = function()
    493 {
    494 }
    495 
    496 WebInspector.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  */
    515 WebInspector.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 
    525 WebInspector.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