Home | History | Annotate | Download | only in front_end
      1  /*
      2  * Copyright (C) 2006, 2007, 2008 Apple Inc.  All rights reserved.
      3  * Copyright (C) 2007 Matt Lilek (pewtermoose (a] gmail.com).
      4  * Copyright (C) 2009 Joseph Pecoraro
      5  * Copyright (C) 2011 Google Inc. All rights reserved.
      6  *
      7  * Redistribution and use in source and binary forms, with or without
      8  * modification, are permitted provided that the following conditions
      9  * are met:
     10  *
     11  * 1.  Redistributions of source code must retain the above copyright
     12  *     notice, this list of conditions and the following disclaimer.
     13  * 2.  Redistributions in binary form must reproduce the above copyright
     14  *     notice, this list of conditions and the following disclaimer in the
     15  *     documentation and/or other materials provided with the distribution.
     16  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
     17  *     its contributors may be used to endorse or promote products derived
     18  *     from this software without specific prior written permission.
     19  *
     20  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
     21  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     22  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     23  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
     24  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     25  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     26  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     27  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     28  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     29  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     30  */
     31 
     32 /**
     33  * @constructor
     34  */
     35 WebInspector.Toolbar = function()
     36 {
     37     this.element = document.getElementById("toolbar");
     38     WebInspector.installDragHandle(this.element, this._toolbarDragStart.bind(this), this._toolbarDrag.bind(this), this._toolbarDragEnd.bind(this), "default");
     39 
     40     this._dropdownButton = document.getElementById("toolbar-dropdown-arrow");
     41     this._dropdownButton.addEventListener("click", this._toggleDropdown.bind(this), false);
     42 
     43     this._panelsMenuButton = document.getElementById("toolbar-panels-menu");
     44     if (this._isToolbarCustomizable()) {
     45         this._panelsMenuButton.addEventListener("mousedown", this._togglePanelsMenu.bind(this), false);
     46         this._panelsMenuButton.removeStyleClass("hidden");
     47     }
     48 
     49     document.getElementById("close-button-left").addEventListener("click", this._onClose, true);
     50     document.getElementById("close-button-right").addEventListener("click", this._onClose, true);
     51 
     52     this._panelDescriptors = [];
     53 }
     54 
     55 WebInspector.Toolbar.prototype = {
     56     resize: function()
     57     {
     58         this._updateDropdownButtonAndHideDropdown();
     59     },
     60 
     61     /**
     62      * @param {!WebInspector.PanelDescriptor} panelDescriptor
     63      */
     64     addPanel: function(panelDescriptor)
     65     {
     66         this._panelDescriptors.push(panelDescriptor);
     67         panelDescriptor._toolbarElement = this._createPanelToolbarItem(panelDescriptor);
     68         if (!this._isToolbarCustomizable() || this._isPanelVisible(panelDescriptor.name()))
     69             this.element.insertBefore(panelDescriptor._toolbarElement, this._panelInsertLocation(panelDescriptor));
     70         this._updatePanelsMenuState();
     71         this.resize();
     72     },
     73 
     74     /**
     75      * @param {!WebInspector.PanelDescriptor} panelDescriptor
     76      * @return {Element}
     77      */
     78     _panelInsertLocation: function(panelDescriptor)
     79     {
     80         if (!this._isToolbarCustomizable())
     81             return null;
     82 
     83         if (this._isDefaultPanel(panelDescriptor.name()))
     84             return this._firstNonDefaultPanel || null;
     85 
     86         if (!this._firstNonDefaultPanel)
     87             this._firstNonDefaultPanel = panelDescriptor._toolbarElement;
     88         return null;
     89     },
     90 
     91     /**
     92      * @param {!string} name
     93      * @return {boolean}
     94      */
     95     _isDefaultPanel: function(name)
     96     {
     97         var defaultPanels = {
     98             "elements": true,
     99             "resources": true,
    100             "scripts": true,
    101             "console": true,
    102             "network": true,
    103             "timeline": true,
    104         };
    105         return !!defaultPanels[name];
    106     },
    107 
    108     /**
    109      * @param {!string} name
    110      * @return {boolean}
    111      */
    112     _isPanelVisibleByDefault: function(name)
    113     {
    114         var visible = {
    115             "elements": true,
    116             "console": true,
    117             "network": true,
    118             "scripts": true,
    119             "timeline": true,
    120             "profiles": true,
    121             "cpu-profiler": true,
    122             "heap-profiler": true,
    123             "audits": true,
    124             "resources": true,
    125         };
    126         return !!visible[name];
    127     },
    128 
    129     /**
    130      * @return {boolean}
    131      */
    132     _isToolbarCustomizable: function()
    133     {
    134         return WebInspector.experimentsSettings.customizableToolbar.isEnabled();
    135     },
    136 
    137     /**
    138      * @param {!string} name
    139      * @return {boolean}
    140      */
    141     _isPanelVisible: function(name)
    142     {
    143         if (!this._isToolbarCustomizable())
    144             return true;
    145         var visiblePanels = WebInspector.settings.visiblePanels.get();
    146         return visiblePanels.hasOwnProperty(name) ? visiblePanels[name] : this._isPanelVisibleByDefault(name);
    147     },
    148 
    149     /**
    150      * @param {!string} name
    151      * @param {boolean} visible
    152      */
    153     _setPanelVisible: function(name, visible)
    154     {
    155         var visiblePanels = WebInspector.settings.visiblePanels.get();
    156         visiblePanels[name] = visible;
    157         WebInspector.settings.visiblePanels.set(visiblePanels);
    158     },
    159 
    160     /**
    161      * @param {!WebInspector.PanelDescriptor} panelDescriptor
    162      */
    163     _hidePanel: function(panelDescriptor)
    164     {
    165         if (!this._isPanelVisible(panelDescriptor.name()))
    166             return;
    167         var switchToSibling = panelDescriptor._toolbarElement.nextSibling;
    168         if (!switchToSibling || !switchToSibling.classList.contains("toggleable"))
    169             switchToSibling = panelDescriptor._toolbarElement.previousSibling;
    170         if (!switchToSibling || !switchToSibling.classList || !switchToSibling.classList.contains("toggleable"))
    171             return;
    172         this._setPanelVisible(panelDescriptor.name(), false);
    173         this.element.removeChild(panelDescriptor._toolbarElement);
    174         if (WebInspector.inspectorView.currentPanel().name === panelDescriptor.name()) {
    175             for (var i = 0; i < this._panelDescriptors.length; ++i) {
    176                 var descr = this._panelDescriptors[i];
    177                 if (descr._toolbarElement === switchToSibling) {
    178                     WebInspector.showPanel(descr.name());
    179                     break;
    180                 }
    181             }
    182         }
    183         this._updatePanelsMenuState();
    184         this.resize();
    185     },
    186 
    187     _updatePanelsMenuState: function()
    188     {
    189         if (this._panelDescriptors.every(function (descr) { return this._isPanelVisible(descr.name()); }, this) && this._allItemsFitOntoToolbar())
    190             document.getElementById("toolbar-panels-menu").addStyleClass("disabled");
    191         else
    192             document.getElementById("toolbar-panels-menu").removeStyleClass("disabled");
    193     },
    194 
    195     /**
    196      * @return {boolean}
    197      */
    198     _allItemsFitOntoToolbar: function()
    199     {
    200         var toolbarItems = this.element.querySelectorAll(".toolbar-item.toggleable");
    201         return toolbarItems.length === 0 || this.element.scrollHeight < toolbarItems[0].offsetHeight * 2;
    202     },
    203 
    204     /**
    205      * @param {!WebInspector.PanelDescriptor} panelDescriptor
    206      */
    207     _showPanel: function(panelDescriptor)
    208     {
    209         if (this._isPanelVisible(panelDescriptor.name()))
    210             return;
    211         this.element.appendChild(panelDescriptor._toolbarElement);
    212         panelDescriptor._toolbarElement.removeStyleClass("hidden");
    213         this._setPanelVisible(panelDescriptor.name(), true);
    214         this._updatePanelsMenuState();
    215         this.resize();
    216     },
    217 
    218     /**
    219      * @param {WebInspector.PanelDescriptor} panelDescriptor
    220      * @param {boolean=} noCloseButton
    221      * @return {Element}
    222      */
    223     _createPanelToolbarItem: function(panelDescriptor, noCloseButton)
    224     {
    225         var toolbarItem = document.createElement("button");
    226         toolbarItem.className = "toolbar-item toggleable";
    227         toolbarItem.panelDescriptor = panelDescriptor;
    228         toolbarItem.addStyleClass(panelDescriptor.name());
    229 
    230         /**
    231          * @param {Event} event
    232          */
    233         function onContextMenuEvent(event)
    234         {
    235             var contextMenu = new WebInspector.ContextMenu(event);
    236             contextMenu.appendItem(WebInspector.UIString("Close"), this._hidePanel.bind(this, panelDescriptor));
    237             contextMenu.show();
    238         }
    239         if (!this._isDefaultPanel(panelDescriptor.name()))
    240             toolbarItem.addEventListener("contextmenu", onContextMenuEvent.bind(this), true);
    241 
    242         function onToolbarItemClicked()
    243         {
    244             this._showPanel(panelDescriptor);
    245             this._updateDropdownButtonAndHideDropdown();
    246             WebInspector.inspectorView.setCurrentPanel(panelDescriptor.panel());
    247         }
    248         toolbarItem.addEventListener("click", onToolbarItemClicked.bind(this), false);
    249 
    250         function onToolbarItemCloseButtonClicked(event)
    251         {
    252             event.stopPropagation();
    253             this._hidePanel(panelDescriptor);
    254         }
    255 
    256         function panelSelected()
    257         {
    258             if (WebInspector.inspectorView.currentPanel() && panelDescriptor.name() === WebInspector.inspectorView.currentPanel().name)
    259                 toolbarItem.addStyleClass("toggled-on");
    260             else
    261                 toolbarItem.removeStyleClass("toggled-on");
    262         }
    263         WebInspector.inspectorView.addEventListener(WebInspector.InspectorView.Events.PanelSelected, panelSelected);
    264 
    265         toolbarItem.createChild("div", "toolbar-label").textContent = panelDescriptor.title();
    266         if (this._isToolbarCustomizable() && !this._isDefaultPanel(panelDescriptor.name()) && !noCloseButton) {
    267             var closeButton = toolbarItem.createChild("div", "close-button");
    268             closeButton.addEventListener("click", onToolbarItemCloseButtonClicked.bind(this), false);
    269         }
    270         panelSelected();
    271         return toolbarItem;
    272     },
    273 
    274     /**
    275      * @return {boolean}
    276      */
    277     _isDockedToBottom: function()
    278     {
    279         return !!WebInspector.dockController && WebInspector.dockController.dockSide() == WebInspector.DockController.State.DockedToBottom;
    280     },
    281 
    282     /**
    283      * @return {boolean}
    284      */
    285     _isUndocked: function()
    286     {
    287         return !!WebInspector.dockController && WebInspector.dockController.dockSide() == WebInspector.DockController.State.Undocked;
    288     },
    289 
    290     /**
    291      * @return {boolean}
    292      */
    293     _toolbarDragStart: function(event)
    294     {
    295         if (this._isUndocked())
    296             return false;
    297 
    298         var target = event.target;
    299         if (target.hasStyleClass("toolbar-item") && target.hasStyleClass("toggleable"))
    300             return false;
    301 
    302         if (target !== this.element && !target.hasStyleClass("toolbar-item"))
    303             return false;
    304 
    305         this._lastScreenX = event.screenX;
    306         this._lastScreenY = event.screenY;
    307         this._lastHeightDuringDrag = window.innerHeight;
    308         this._startDistanceToRight = window.innerWidth - event.clientX;
    309         this._startDinstanceToBottom = window.innerHeight - event.clientY;
    310         return true;
    311     },
    312 
    313     _toolbarDragEnd: function(event)
    314     {
    315         // We may not get the drag event at the end.
    316         // Apply last changes manually.
    317         this._toolbarDrag(event);
    318         delete this._lastScreenX;
    319         delete this._lastScreenY;
    320         delete this._lastHeightDuringDrag;
    321         delete this._startDistanceToRight;
    322         delete this._startDinstanceToBottom;
    323     },
    324 
    325     _toolbarDrag: function(event)
    326     {
    327         event.preventDefault();
    328 
    329         if (this._isUndocked())
    330             return this._toolbarDragMoveWindow(event);
    331 
    332         return this._toolbarDragChangeDocking(event);
    333     },
    334 
    335     _toolbarDragMoveWindow: function(event)
    336     {
    337         var x = event.screenX - this._lastScreenX;
    338         var y = event.screenY - this._lastScreenY;
    339         this._lastScreenX = event.screenX;
    340         this._lastScreenY = event.screenY;
    341         InspectorFrontendHost.moveWindowBy(x, y);
    342     },
    343 
    344     _toolbarDragChangeDocking: function(event)
    345     {
    346         if (this._isDockedToBottom()) {
    347             var distanceToRight = window.innerWidth - event.clientX;
    348             if (distanceToRight < this._startDistanceToRight * 2 / 3) {
    349                 InspectorFrontendHost.requestSetDockSide(WebInspector.DockController.State.DockedToRight);
    350                 return true;
    351             }
    352         } else {
    353             var distanceToBottom = window.innerHeight - event.clientY;
    354             if (distanceToBottom < this._startDinstanceToBottom * 2 / 3) {
    355                 InspectorFrontendHost.requestSetDockSide(WebInspector.DockController.State.DockedToBottom);
    356                 return true;
    357             }
    358         }
    359     },
    360 
    361     _onClose: function()
    362     {
    363         WebInspector.close();
    364     },
    365 
    366     _setDropdownVisible: function(visible)
    367     {
    368         if (!this._dropdown) {
    369             if (!visible)
    370                 return;
    371             this._dropdown = new WebInspector.ToolbarDropdown(this);
    372         }
    373         if (visible)
    374             this._dropdown.show();
    375         else
    376             this._dropdown.hide();
    377     },
    378 
    379     _toggleDropdown: function()
    380     {
    381         this._setDropdownVisible(!this._dropdown || !this._dropdown.visible);
    382     },
    383 
    384     _togglePanelsMenu: function(event)
    385     {
    386         function activatePanel(panelDescriptor)
    387         {
    388             this._showPanel(panelDescriptor);
    389             WebInspector.showPanel(panelDescriptor.name());
    390         }
    391 
    392         var contextMenu = new WebInspector.ContextMenu(event);
    393         var currentPanelName = WebInspector.inspectorView.currentPanel().name;
    394         var toolbarItems = this.element.querySelectorAll(".toolbar-item.toggleable");
    395         for (var i = 0; i < toolbarItems.length; ++i) {
    396             if (toolbarItems[i].offsetTop >= toolbarItems[0].offsetHeight) {
    397                 var descr = toolbarItems[i].panelDescriptor;
    398                 if (descr.name() === currentPanelName)
    399                     contextMenu.appendCheckboxItem(descr.title(), activatePanel.bind(this, descr), true);
    400                 else
    401                     contextMenu.appendItem(descr.title(), activatePanel.bind(this, descr));
    402             }
    403         }
    404         contextMenu.appendSeparator();
    405         for (var i = 0; i < this._panelDescriptors.length; ++i) {
    406             var descr = this._panelDescriptors[i];
    407             if (this._isPanelVisible(descr.name()))
    408                 continue;
    409             contextMenu.appendItem(descr.title(), activatePanel.bind(this, descr));
    410         }
    411 
    412         contextMenu.showSoftMenu();
    413     },
    414 
    415     _updateDropdownButtonAndHideDropdown: function()
    416     {
    417         WebInspector.invokeOnceAfterBatchUpdate(this, this._innerUpdateDropdownButtonAndHideDropdown);
    418     },
    419 
    420     _innerUpdateDropdownButtonAndHideDropdown: function()
    421     {
    422         if (this._isToolbarCustomizable()) {
    423             this._updatePanelsMenuState();
    424             return;
    425         }
    426         this._setDropdownVisible(false);
    427 
    428         if (this.element.scrollHeight > this.element.offsetHeight)
    429             this._dropdownButton.removeStyleClass("hidden");
    430         else
    431             this._dropdownButton.addStyleClass("hidden");
    432     }
    433 }
    434 
    435 /**
    436  * @constructor
    437  * @param {WebInspector.Toolbar} toolbar
    438  */
    439 WebInspector.ToolbarDropdown = function(toolbar)
    440 {
    441     this._toolbar = toolbar;
    442     this._arrow = document.getElementById("toolbar-dropdown-arrow");
    443     this.element = document.createElement("div");
    444     this.element.id = "toolbar-dropdown";
    445     this.element.className = "toolbar-small";
    446     this._contentElement = this.element.createChild("div", "scrollable-content");
    447     this._contentElement.tabIndex = 0;
    448     this._contentElement.addEventListener("keydown", this._onKeyDown.bind(this), true);
    449 }
    450 
    451 WebInspector.ToolbarDropdown.prototype = {
    452     show: function()
    453     {
    454         if (this.visible)
    455             return;
    456         var style = this.element.style;
    457         this._populate();
    458         var top = this._arrow.totalOffsetTop() + this._arrow.clientHeight;
    459         this._arrow.addStyleClass("dropdown-visible");
    460         this.element.style.top = top + "px";
    461         this.element.style.right = window.innerWidth - this._arrow.totalOffsetLeft() - this._arrow.clientWidth + "px";
    462         this._contentElement.style.maxHeight = window.innerHeight - top - 20 + "px";
    463         this._toolbar.element.appendChild(this.element);
    464     },
    465 
    466     hide: function()
    467     {
    468         if (!this.visible)
    469             return;
    470         this._arrow.removeStyleClass("dropdown-visible");
    471         this.element.remove();
    472         this._contentElement.removeChildren();
    473     },
    474 
    475     get visible()
    476     {
    477         return !!this.element.parentNode;
    478     },
    479 
    480     _populate: function()
    481     {
    482         var toolbarItems = this._toolbar.element.querySelectorAll(".toolbar-item.toggleable");
    483 
    484         var needsSeparator = false;
    485         for (var i = 0; i < toolbarItems.length; ++i) {
    486             if (toolbarItems[i].offsetTop >= toolbarItems[0].offsetHeight) {
    487                 this._contentElement.appendChild(this._toolbar._createPanelToolbarItem(toolbarItems[i].panelDescriptor, true));
    488                 needsSeparator = true;
    489             }
    490         }
    491 
    492         var panelDescriptors = this._toolbar._panelDescriptors;
    493         for (var i = 0; i < panelDescriptors.length; ++i) {
    494             var descr = panelDescriptors[i];
    495             if (this._toolbar._isPanelVisible(descr.name()))
    496                 continue;
    497             if (needsSeparator) {
    498                 this._contentElement.createChild("div", "toolbar-items-separator");
    499                 needsSeparator = false;
    500             }
    501             this._contentElement.appendChild(this._toolbar._createPanelToolbarItem(descr, true));
    502         }
    503     },
    504 
    505     _onKeyDown: function(event)
    506     {
    507         if (event.keyCode !== WebInspector.KeyboardShortcut.Keys.Esc.code)
    508             return;
    509         event.consume();
    510         this.hide();
    511     }
    512 }
    513 
    514 /**
    515  * @type {?WebInspector.Toolbar}
    516  */
    517 WebInspector.toolbar = null;
    518