Home | History | Annotate | Download | only in front_end
      1 /*
      2  * Copyright (C) 2010 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  * @extends {WebInspector.View}
     33  * @constructor
     34  */
     35 WebInspector.TabbedPane = function()
     36 {
     37     WebInspector.View.call(this);
     38     this.element.classList.add("tabbed-pane", "vbox");
     39     this._headerElement = this.element.createChild("div", "tabbed-pane-header");
     40     this._headerContentsElement = this._headerElement.createChild("div", "tabbed-pane-header-contents");
     41     this._tabsElement = this._headerContentsElement.createChild("div", "tabbed-pane-header-tabs");
     42     this._contentElement = this.element.createChild("div", "tabbed-pane-content scroll-target");
     43     this._tabs = [];
     44     this._tabsHistory = [];
     45     this._tabsById = {};
     46 
     47     this._dropDownButton = this._createDropDownButton();
     48 }
     49 
     50 WebInspector.TabbedPane.EventTypes = {
     51     TabSelected: "TabSelected",
     52     TabClosed: "TabClosed"
     53 }
     54 
     55 WebInspector.TabbedPane.prototype = {
     56     /**
     57      * @return {!WebInspector.View}
     58      */
     59     get visibleView()
     60     {
     61         return this._currentTab ? this._currentTab.view : null;
     62     },
     63 
     64     /**
     65      * @return {string}
     66      */
     67     get selectedTabId()
     68     {
     69         return this._currentTab ? this._currentTab.id : null;
     70     },
     71 
     72     /**
     73      * @type {boolean} shrinkableTabs
     74      */
     75     set shrinkableTabs(shrinkableTabs)
     76     {
     77         this._shrinkableTabs = shrinkableTabs;
     78     },
     79 
     80     /**
     81      * @type {boolean} verticalTabLayout
     82      */
     83     set verticalTabLayout(verticalTabLayout)
     84     {
     85         this._verticalTabLayout = verticalTabLayout;
     86     },
     87 
     88     /**
     89      * @type {boolean} closeableTabs
     90      */
     91     set closeableTabs(closeableTabs)
     92     {
     93         this._closeableTabs = closeableTabs;
     94     },
     95 
     96     /**
     97      * @param {boolean} retainTabsOrder
     98      */
     99     setRetainTabsOrder: function(retainTabsOrder)
    100     {
    101         this._retainTabsOrder = retainTabsOrder;
    102     },
    103 
    104     /**
    105      * @return {?Element}
    106      */
    107     defaultFocusedElement: function()
    108     {
    109         return this.visibleView ? this.visibleView.defaultFocusedElement() : null;
    110     },
    111 
    112     /**
    113      * @return {!Element}
    114      */
    115     headerElement: function()
    116     {
    117         return this._headerElement;
    118     },
    119 
    120     /**
    121      * @param {string} id
    122      * @return {boolean}
    123      */
    124     isTabCloseable: function(id)
    125     {
    126         var tab = this._tabsById[id];
    127         return tab ? tab.isCloseable() : false;
    128     },
    129 
    130     /**
    131      * @param {!WebInspector.TabbedPaneTabDelegate} delegate
    132      */
    133     setTabDelegate: function(delegate)
    134     {
    135         var tabs = this._tabs.slice();
    136         for (var i = 0; i < tabs.length; ++i)
    137             tabs[i].setDelegate(delegate);
    138         this._delegate = delegate;
    139     },
    140 
    141     /**
    142      * @param {string} id
    143      * @param {string} tabTitle
    144      * @param {!WebInspector.View} view
    145      * @param {string=} tabTooltip
    146      * @param {boolean=} userGesture
    147      * @param {boolean=} isCloseable
    148      */
    149     appendTab: function(id, tabTitle, view, tabTooltip, userGesture, isCloseable)
    150     {
    151         isCloseable = typeof isCloseable === "boolean" ? isCloseable : this._closeableTabs;
    152         var tab = new WebInspector.TabbedPaneTab(this, id, tabTitle, isCloseable, view, tabTooltip);
    153         tab.setDelegate(this._delegate);
    154         this._tabsById[id] = tab;
    155 
    156         this._tabs.push(tab);
    157         this._tabsHistory.push(tab);
    158 
    159         if (this._tabsHistory[0] === tab)
    160             this.selectTab(tab.id, userGesture);
    161 
    162         this._updateTabElements();
    163     },
    164 
    165     /**
    166      * @param {string} id
    167      * @param {boolean=} userGesture
    168      */
    169     closeTab: function(id, userGesture)
    170     {
    171         this.closeTabs([id], userGesture);
    172     },
    173 
    174      /**
    175       * @param {!Array.<string>} ids
    176       * @param {boolean=} userGesture
    177       */
    178      closeTabs: function(ids, userGesture)
    179      {
    180          for (var i = 0; i < ids.length; ++i)
    181              this._innerCloseTab(ids[i], userGesture);
    182          this._updateTabElements();
    183          if (this._tabsHistory.length)
    184              this.selectTab(this._tabsHistory[0].id, false);
    185      },
    186 
    187     /**
    188      * @param {string} id
    189      * @param {boolean=} userGesture
    190      */
    191     _innerCloseTab: function(id, userGesture)
    192     {
    193         if (!this._tabsById[id])
    194             return;
    195         if (userGesture && !this._tabsById[id]._closeable)
    196             return;
    197         if (this._currentTab && this._currentTab.id === id)
    198             this._hideCurrentTab();
    199 
    200         var tab = this._tabsById[id];
    201         delete this._tabsById[id];
    202 
    203         this._tabsHistory.splice(this._tabsHistory.indexOf(tab), 1);
    204         this._tabs.splice(this._tabs.indexOf(tab), 1);
    205         if (tab._shown)
    206             this._hideTabElement(tab);
    207 
    208         var eventData = { tabId: id, view: tab.view, isUserGesture: userGesture };
    209         this.dispatchEventToListeners(WebInspector.TabbedPane.EventTypes.TabClosed, eventData);
    210         return true;
    211     },
    212 
    213     /**
    214      * @param {string} tabId
    215      * @return {boolean}
    216      */
    217     hasTab: function(tabId)
    218     {
    219         return !!this._tabsById[tabId];
    220     },
    221 
    222     /**
    223      * @return {!Array.<string>}
    224      */
    225     allTabs: function()
    226     {
    227         var result = [];
    228         var tabs = this._tabs.slice();
    229         for (var i = 0; i < tabs.length; ++i)
    230             result.push(tabs[i].id);
    231         return result;
    232     },
    233 
    234     /**
    235      * @param {string} id
    236      * @return {!Array.<string>}
    237      */
    238     otherTabs: function(id)
    239     {
    240         var result = [];
    241         var tabs = this._tabs.slice();
    242         for (var i = 0; i < tabs.length; ++i) {
    243             if (tabs[i].id !== id)
    244                 result.push(tabs[i].id);
    245         }
    246         return result;
    247     },
    248 
    249     /**
    250      * @param {string} id
    251      * @param {boolean=} userGesture
    252      */
    253     selectTab: function(id, userGesture)
    254     {
    255         var tab = this._tabsById[id];
    256         if (!tab)
    257             return;
    258         if (this._currentTab && this._currentTab.id === id)
    259             return;
    260 
    261         this._hideCurrentTab();
    262         this._showTab(tab);
    263         this._currentTab = tab;
    264 
    265         this._tabsHistory.splice(this._tabsHistory.indexOf(tab), 1);
    266         this._tabsHistory.splice(0, 0, tab);
    267 
    268         this._updateTabElements();
    269 
    270         var eventData = { tabId: id, view: tab.view, isUserGesture: userGesture };
    271         this.dispatchEventToListeners(WebInspector.TabbedPane.EventTypes.TabSelected, eventData);
    272         return true;
    273     },
    274 
    275     /**
    276      * @param {number} tabsCount
    277      * @return {!Array.<string>}
    278      */
    279     lastOpenedTabIds: function(tabsCount)
    280     {
    281         function tabToTabId(tab) {
    282             return tab.id;
    283         }
    284 
    285         return this._tabsHistory.slice(0, tabsCount).map(tabToTabId);
    286     },
    287 
    288     /**
    289      * @param {string} id
    290      * @param {string} iconClass
    291      * @param {string=} iconTooltip
    292      */
    293     setTabIcon: function(id, iconClass, iconTooltip)
    294     {
    295         var tab = this._tabsById[id];
    296         if (tab._setIconClass(iconClass, iconTooltip))
    297             this._updateTabElements();
    298     },
    299 
    300     /**
    301      * @param {string} id
    302      * @param {string} tabTitle
    303      */
    304     changeTabTitle: function(id, tabTitle)
    305     {
    306         var tab = this._tabsById[id];
    307         if (tab.title === tabTitle)
    308             return;
    309         tab.title = tabTitle;
    310         this._updateTabElements();
    311     },
    312 
    313     /**
    314      * @param {string} id
    315      * @param {!WebInspector.View} view
    316      */
    317     changeTabView: function(id, view)
    318     {
    319         var tab = this._tabsById[id];
    320         if (this._currentTab && this._currentTab.id === tab.id) {
    321             if (tab.view !== view)
    322                 this._hideTab(tab);
    323             tab.view = view;
    324             this._showTab(tab);
    325         } else
    326             tab.view = view;
    327     },
    328 
    329     /**
    330      * @param {string} id
    331      * @param {string=} tabTooltip
    332      */
    333     changeTabTooltip: function(id, tabTooltip)
    334     {
    335         var tab = this._tabsById[id];
    336         tab.tooltip = tabTooltip;
    337     },
    338 
    339     onResize: function()
    340     {
    341         this._updateTabElements();
    342     },
    343 
    344     headerResized: function()
    345     {
    346         this._updateTabElements();
    347     },
    348 
    349     _updateTabElements: function()
    350     {
    351         WebInspector.invokeOnceAfterBatchUpdate(this, this._innerUpdateTabElements);
    352     },
    353 
    354     /**
    355      * @param {string} text
    356      */
    357     setPlaceholderText: function(text)
    358     {
    359         this._noTabsMessage = text;
    360     },
    361 
    362     _innerUpdateTabElements: function()
    363     {
    364         if (!this.isShowing())
    365             return;
    366 
    367         if (!this._tabs.length) {
    368             this._contentElement.classList.add("has-no-tabs");
    369             if (this._noTabsMessage && !this._noTabsMessageElement) {
    370                 this._noTabsMessageElement = this._contentElement.createChild("div", "tabbed-pane-placeholder fill");
    371                 this._noTabsMessageElement.textContent = this._noTabsMessage;
    372             }
    373         } else {
    374             this._contentElement.classList.remove("has-no-tabs");
    375             if (this._noTabsMessageElement) {
    376                 this._noTabsMessageElement.remove();
    377                 delete this._noTabsMessageElement;
    378             }
    379         }
    380 
    381         if (!this._measuredDropDownButtonWidth)
    382             this._measureDropDownButton();
    383 
    384         this._updateWidths();
    385         this._updateTabsDropDown();
    386     },
    387 
    388     /**
    389      * @param {number} index
    390      * @param {!WebInspector.TabbedPaneTab} tab
    391      */
    392     _showTabElement: function(index, tab)
    393     {
    394         if (index >= this._tabsElement.children.length)
    395             this._tabsElement.appendChild(tab.tabElement);
    396         else
    397             this._tabsElement.insertBefore(tab.tabElement, this._tabsElement.children[index]);
    398         tab._shown = true;
    399     },
    400 
    401     /**
    402      * @param {!WebInspector.TabbedPaneTab} tab
    403      */
    404     _hideTabElement: function(tab)
    405     {
    406         this._tabsElement.removeChild(tab.tabElement);
    407         tab._shown = false;
    408     },
    409 
    410     _createDropDownButton: function()
    411     {
    412         var dropDownContainer = document.createElement("div");
    413         dropDownContainer.classList.add("tabbed-pane-header-tabs-drop-down-container");
    414         var dropDownButton = dropDownContainer.createChild("div", "tabbed-pane-header-tabs-drop-down");
    415         dropDownButton.appendChild(document.createTextNode("\u00bb"));
    416         this._tabsSelect = dropDownButton.createChild("select", "tabbed-pane-header-tabs-drop-down-select");
    417         this._tabsSelect.addEventListener("change", this._tabsSelectChanged.bind(this), false);
    418         return dropDownContainer;
    419     },
    420 
    421     _totalWidth: function()
    422     {
    423         return this._headerContentsElement.getBoundingClientRect().width;
    424     },
    425 
    426     _updateTabsDropDown: function()
    427     {
    428         var tabsToShowIndexes = this._tabsToShowIndexes(this._tabs, this._tabsHistory, this._totalWidth(), this._measuredDropDownButtonWidth);
    429 
    430         for (var i = 0; i < this._tabs.length; ++i) {
    431             if (this._tabs[i]._shown && tabsToShowIndexes.indexOf(i) === -1)
    432                 this._hideTabElement(this._tabs[i]);
    433         }
    434         for (var i = 0; i < tabsToShowIndexes.length; ++i) {
    435             var tab = this._tabs[tabsToShowIndexes[i]];
    436             if (!tab._shown)
    437                 this._showTabElement(i, tab);
    438         }
    439 
    440         this._populateDropDownFromIndex();
    441     },
    442 
    443     _populateDropDownFromIndex: function()
    444     {
    445         if (this._dropDownButton.parentElement)
    446             this._headerContentsElement.removeChild(this._dropDownButton);
    447 
    448         this._tabsSelect.removeChildren();
    449         var tabsToShow = [];
    450         for (var i = 0; i < this._tabs.length; ++i) {
    451             if (!this._tabs[i]._shown)
    452                 tabsToShow.push(this._tabs[i]);
    453                 continue;
    454         }
    455 
    456         function compareFunction(tab1, tab2)
    457         {
    458             return tab1.title.localeCompare(tab2.title);
    459         }
    460         if (!this._retainTabsOrder)
    461             tabsToShow.sort(compareFunction);
    462 
    463         var selectedIndex = -1;
    464         for (var i = 0; i < tabsToShow.length; ++i) {
    465             var option = new Option(tabsToShow[i].title);
    466             option.tab = tabsToShow[i];
    467             this._tabsSelect.appendChild(option);
    468             if (this._tabsHistory[0] === tabsToShow[i])
    469                 selectedIndex = i;
    470         }
    471         if (this._tabsSelect.options.length) {
    472             this._headerContentsElement.appendChild(this._dropDownButton);
    473             this._tabsSelect.selectedIndex = selectedIndex;
    474         }
    475     },
    476 
    477     _tabsSelectChanged: function()
    478     {
    479         var options = this._tabsSelect.options;
    480         var selectedOption = options[this._tabsSelect.selectedIndex];
    481         this.selectTab(selectedOption.tab.id, true);
    482     },
    483 
    484     _measureDropDownButton: function()
    485     {
    486         this._dropDownButton.classList.add("measuring");
    487         this._headerContentsElement.appendChild(this._dropDownButton);
    488         this._measuredDropDownButtonWidth = this._dropDownButton.getBoundingClientRect().width;
    489         this._headerContentsElement.removeChild(this._dropDownButton);
    490         this._dropDownButton.classList.remove("measuring");
    491     },
    492 
    493     _updateWidths: function()
    494     {
    495         var measuredWidths = this._measureWidths();
    496         var maxWidth = this._shrinkableTabs ? this._calculateMaxWidth(measuredWidths.slice(), this._totalWidth()) : Number.MAX_VALUE;
    497 
    498         var i = 0;
    499         for (var tabId in this._tabs) {
    500             var tab = this._tabs[tabId];
    501             tab.setWidth(this._verticalTabLayout ? -1 : Math.min(maxWidth, measuredWidths[i++]));
    502         }
    503     },
    504 
    505     _measureWidths: function()
    506     {
    507         // Add all elements to measure into this._tabsElement
    508         this._tabsElement.style.setProperty("width", "2000px");
    509         var measuringTabElements = [];
    510         for (var tabId in this._tabs) {
    511             var tab = this._tabs[tabId];
    512             if (typeof tab._measuredWidth === "number")
    513                 continue;
    514             var measuringTabElement = tab._createTabElement(true);
    515             measuringTabElement.__tab = tab;
    516             measuringTabElements.push(measuringTabElement);
    517             this._tabsElement.appendChild(measuringTabElement);
    518         }
    519 
    520         // Perform measurement
    521         for (var i = 0; i < measuringTabElements.length; ++i)
    522             measuringTabElements[i].__tab._measuredWidth = measuringTabElements[i].getBoundingClientRect().width;
    523 
    524         // Nuke elements from the UI
    525         for (var i = 0; i < measuringTabElements.length; ++i)
    526             measuringTabElements[i].remove();
    527 
    528         // Combine the results.
    529         var measuredWidths = [];
    530         for (var tabId in this._tabs)
    531             measuredWidths.push(this._tabs[tabId]._measuredWidth);
    532         this._tabsElement.style.removeProperty("width");
    533 
    534         return measuredWidths;
    535     },
    536 
    537     /**
    538      * @param {!Array.<number>} measuredWidths
    539      * @param {number} totalWidth
    540      */
    541     _calculateMaxWidth: function(measuredWidths, totalWidth)
    542     {
    543         if (!measuredWidths.length)
    544             return 0;
    545 
    546         measuredWidths.sort(function(x, y) { return x - y });
    547 
    548         var totalMeasuredWidth = 0;
    549         for (var i = 0; i < measuredWidths.length; ++i)
    550             totalMeasuredWidth += measuredWidths[i];
    551 
    552         if (totalWidth >= totalMeasuredWidth)
    553             return measuredWidths[measuredWidths.length - 1];
    554 
    555         var totalExtraWidth = 0;
    556         for (var i = measuredWidths.length - 1; i > 0; --i) {
    557             var extraWidth = measuredWidths[i] - measuredWidths[i - 1];
    558             totalExtraWidth += (measuredWidths.length - i) * extraWidth;
    559 
    560             if (totalWidth + totalExtraWidth >= totalMeasuredWidth)
    561                 return measuredWidths[i - 1] + (totalWidth + totalExtraWidth - totalMeasuredWidth) / (measuredWidths.length - i);
    562         }
    563 
    564         return totalWidth / measuredWidths.length;
    565     },
    566 
    567     /**
    568      * @param {!Array.<!WebInspector.TabbedPaneTab>} tabsOrdered
    569      * @param {!Array.<!WebInspector.TabbedPaneTab>} tabsHistory
    570      * @param {number} totalWidth
    571      * @param {number} measuredDropDownButtonWidth
    572      * @return {!Array.<number>}
    573      */
    574     _tabsToShowIndexes: function(tabsOrdered, tabsHistory, totalWidth, measuredDropDownButtonWidth)
    575     {
    576         var tabsToShowIndexes = [];
    577 
    578         var totalTabsWidth = 0;
    579         var tabCount = tabsOrdered.length;
    580         for (var i = 0; i < tabCount; ++i) {
    581             var tab = this._retainTabsOrder ? tabsOrdered[i] : tabsHistory[i];
    582             totalTabsWidth += tab.width();
    583             var minimalRequiredWidth = totalTabsWidth;
    584             if (i !== tabCount - 1)
    585                 minimalRequiredWidth += measuredDropDownButtonWidth;
    586             if (!this._verticalTabLayout && minimalRequiredWidth > totalWidth)
    587                 break;
    588             tabsToShowIndexes.push(tabsOrdered.indexOf(tab));
    589         }
    590 
    591         tabsToShowIndexes.sort(function(x, y) { return x - y });
    592 
    593         return tabsToShowIndexes;
    594     },
    595 
    596     _hideCurrentTab: function()
    597     {
    598         if (!this._currentTab)
    599             return;
    600 
    601         this._hideTab(this._currentTab);
    602         delete this._currentTab;
    603     },
    604 
    605     /**
    606      * @param {!WebInspector.TabbedPaneTab} tab
    607      */
    608     _showTab: function(tab)
    609     {
    610         tab.tabElement.classList.add("selected");
    611         tab.view.show(this._contentElement);
    612     },
    613 
    614     /**
    615      * @param {!WebInspector.TabbedPaneTab} tab
    616      */
    617     _hideTab: function(tab)
    618     {
    619         tab.tabElement.classList.remove("selected");
    620         tab.view.detach();
    621     },
    622 
    623     /**
    624      * @override
    625      */
    626     canHighlightPosition: function()
    627     {
    628         return this._currentTab && this._currentTab.view && this._currentTab.view.canHighlightPosition();
    629     },
    630 
    631     /**
    632      * @override
    633      */
    634     highlightPosition: function(line, column)
    635     {
    636         if (this.canHighlightPosition())
    637             this._currentTab.view.highlightPosition(line, column);
    638     },
    639 
    640     /**
    641      * @return {!Array.<!Element>}
    642      */
    643     elementsToRestoreScrollPositionsFor: function()
    644     {
    645         return [ this._contentElement ];
    646     },
    647 
    648     /**
    649      * @param {!WebInspector.TabbedPaneTab} tab
    650      * @param {number} index
    651      */
    652     _insertBefore: function(tab, index)
    653     {
    654         this._tabsElement.insertBefore(tab._tabElement, this._tabsElement.childNodes[index]);
    655         var oldIndex = this._tabs.indexOf(tab);
    656         this._tabs.splice(oldIndex, 1);
    657         if (oldIndex < index)
    658             --index;
    659         this._tabs.splice(index, 0, tab);
    660     },
    661 
    662     __proto__: WebInspector.View.prototype
    663 }
    664 
    665 
    666 /**
    667  * @constructor
    668  * @param {!WebInspector.TabbedPane} tabbedPane
    669  * @param {string} id
    670  * @param {string} title
    671  * @param {boolean} closeable
    672  * @param {!WebInspector.View} view
    673  * @param {string=} tooltip
    674  */
    675 WebInspector.TabbedPaneTab = function(tabbedPane, id, title, closeable, view, tooltip)
    676 {
    677     this._closeable = closeable;
    678     this._tabbedPane = tabbedPane;
    679     this._id = id;
    680     this._title = title;
    681     this._tooltip = tooltip;
    682     this._view = view;
    683     this._shown = false;
    684     /** @type {number} */ this._measuredWidth;
    685     /** @type {!Element|undefined} */ this._tabElement;
    686 }
    687 
    688 WebInspector.TabbedPaneTab.prototype = {
    689     /**
    690      * @return {string}
    691      */
    692     get id()
    693     {
    694         return this._id;
    695     },
    696 
    697     /**
    698      * @return {string}
    699      */
    700     get title()
    701     {
    702         return this._title;
    703     },
    704 
    705     set title(title)
    706     {
    707         if (title === this._title)
    708             return;
    709         this._title = title;
    710         if (this._titleElement)
    711             this._titleElement.textContent = title;
    712         delete this._measuredWidth;
    713     },
    714 
    715     /**
    716      * @return {string}
    717      */
    718     iconClass: function()
    719     {
    720         return this._iconClass;
    721     },
    722 
    723     /**
    724      * @return {boolean}
    725      */
    726     isCloseable: function()
    727     {
    728         return this._closeable;
    729     },
    730 
    731     /**
    732      * @param {string} iconClass
    733      * @param {string} iconTooltip
    734      * @return {boolean}
    735      */
    736     _setIconClass: function(iconClass, iconTooltip)
    737     {
    738         if (iconClass === this._iconClass && iconTooltip === this._iconTooltip)
    739             return false;
    740         this._iconClass = iconClass;
    741         this._iconTooltip = iconTooltip;
    742         if (this._iconElement)
    743             this._iconElement.remove();
    744         if (this._iconClass && this._tabElement)
    745             this._iconElement = this._createIconElement(this._tabElement, this._titleElement);
    746         delete this._measuredWidth;
    747         return true;
    748     },
    749 
    750     /**
    751      * @return {!WebInspector.View}
    752      */
    753     get view()
    754     {
    755         return this._view;
    756     },
    757 
    758     set view(view)
    759     {
    760         this._view = view;
    761     },
    762 
    763     /**
    764      * @return {string|undefined}
    765      */
    766     get tooltip()
    767     {
    768         return this._tooltip;
    769     },
    770 
    771     set tooltip(tooltip)
    772     {
    773         this._tooltip = tooltip;
    774         if (this._titleElement)
    775             this._titleElement.title = tooltip || "";
    776     },
    777 
    778     /**
    779      * @return {!Element}
    780      */
    781     get tabElement()
    782     {
    783         if (!this._tabElement)
    784             this._tabElement = this._createTabElement(false);
    785 
    786         return this._tabElement;
    787     },
    788 
    789     /**
    790      * @return {number}
    791      */
    792     width: function()
    793     {
    794         return this._width;
    795     },
    796 
    797     /**
    798      * @param {number} width
    799      */
    800     setWidth: function(width)
    801     {
    802         this.tabElement.style.width = width === -1 ? "" : (width + "px");
    803         this._width = width;
    804     },
    805 
    806     /**
    807      * @param {!WebInspector.TabbedPaneTabDelegate} delegate
    808      */
    809     setDelegate: function(delegate)
    810     {
    811         this._delegate = delegate;
    812     },
    813 
    814     _createIconElement: function(tabElement, titleElement)
    815     {
    816         var iconElement = document.createElement("span");
    817         iconElement.className = "tabbed-pane-header-tab-icon " + this._iconClass;
    818         if (this._iconTooltip)
    819             iconElement.title = this._iconTooltip;
    820         tabElement.insertBefore(iconElement, titleElement);
    821         return iconElement;
    822     },
    823 
    824     /**
    825      * @param {boolean} measuring
    826      * @return {!Element}
    827      */
    828     _createTabElement: function(measuring)
    829     {
    830         var tabElement = document.createElement("div");
    831         tabElement.classList.add("tabbed-pane-header-tab");
    832         tabElement.id = "tab-" + this._id;
    833         tabElement.tabIndex = -1;
    834         tabElement.selectTabForTest = this._tabbedPane.selectTab.bind(this._tabbedPane, this.id, true);
    835 
    836         var titleElement = tabElement.createChild("span", "tabbed-pane-header-tab-title");
    837         titleElement.textContent = this.title;
    838         titleElement.title = this.tooltip || "";
    839         if (this._iconClass)
    840             this._createIconElement(tabElement, titleElement);
    841         if (!measuring)
    842             this._titleElement = titleElement;
    843 
    844         if (this._closeable)
    845             tabElement.createChild("div", "close-button-gray");
    846 
    847         if (measuring) {
    848             tabElement.classList.add("measuring");
    849         } else {
    850             tabElement.addEventListener("click", this._tabClicked.bind(this), false);
    851             tabElement.addEventListener("mousedown", this._tabMouseDown.bind(this), false);
    852             tabElement.addEventListener("mouseup", this._tabMouseUp.bind(this), false);
    853 
    854             if (this._closeable) {
    855                 tabElement.addEventListener("contextmenu", this._tabContextMenu.bind(this), false);
    856                 WebInspector.installDragHandle(tabElement, this._startTabDragging.bind(this), this._tabDragging.bind(this), this._endTabDragging.bind(this), "pointer");
    857             }
    858         }
    859 
    860         return tabElement;
    861     },
    862 
    863     /**
    864      * @param {?Event} event
    865      */
    866     _tabClicked: function(event)
    867     {
    868         var middleButton = event.button === 1;
    869         var shouldClose = this._closeable && (middleButton || event.target.classList.contains("close-button-gray"));
    870         if (!shouldClose) {
    871             this._tabbedPane.focus();
    872             return;
    873         }
    874         this._closeTabs([this.id]);
    875         event.consume(true);
    876     },
    877 
    878     /**
    879      * @param {?Event} event
    880      */
    881     _tabMouseDown: function(event)
    882     {
    883         if (event.target.classList.contains("close-button-gray") || event.button === 1)
    884             return;
    885         this._tabbedPane.selectTab(this.id, true);
    886     },
    887 
    888     /**
    889      * @param {?Event} event
    890      */
    891     _tabMouseUp: function(event)
    892     {
    893         // This is needed to prevent middle-click pasting on linux when tabs are clicked.
    894         if (event.button === 1)
    895             event.consume(true);
    896     },
    897 
    898     /**
    899      * @param {!Array.<string>} ids
    900      */
    901     _closeTabs: function(ids)
    902     {
    903         if (this._delegate) {
    904             this._delegate.closeTabs(this._tabbedPane, ids);
    905             return;
    906         }
    907         this._tabbedPane.closeTabs(ids, true);
    908     },
    909 
    910     _tabContextMenu: function(event)
    911     {
    912         /**
    913          * @this {WebInspector.TabbedPaneTab}
    914          */
    915         function close()
    916         {
    917             this._closeTabs([this.id]);
    918         }
    919 
    920         /**
    921          * @this {WebInspector.TabbedPaneTab}
    922          */
    923         function closeOthers()
    924         {
    925             this._closeTabs(this._tabbedPane.otherTabs(this.id));
    926         }
    927 
    928         /**
    929          * @this {WebInspector.TabbedPaneTab}
    930          */
    931         function closeAll()
    932         {
    933             this._closeTabs(this._tabbedPane.allTabs());
    934         }
    935 
    936         var contextMenu = new WebInspector.ContextMenu(event);
    937         contextMenu.appendItem(WebInspector.UIString("Close"), close.bind(this));
    938         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Close others" : "Close Others"), closeOthers.bind(this));
    939         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Close all" : "Close All"), closeAll.bind(this));
    940         contextMenu.show();
    941     },
    942 
    943     /**
    944      * @param {!Event} event
    945      * @return {boolean}
    946      */
    947     _startTabDragging: function(event)
    948     {
    949         if (event.target.classList.contains("close-button-gray"))
    950             return false;
    951         this._dragStartX = event.pageX;
    952         return true;
    953     },
    954 
    955     /**
    956      * @param {!Event} event
    957      */
    958     _tabDragging: function(event)
    959     {
    960         var tabElements = this._tabbedPane._tabsElement.childNodes;
    961         for (var i = 0; i < tabElements.length; ++i) {
    962             var tabElement = tabElements[i];
    963             if (tabElement === this._tabElement)
    964                 continue;
    965 
    966             var intersects = tabElement.offsetLeft + tabElement.clientWidth > this._tabElement.offsetLeft &&
    967                 this._tabElement.offsetLeft + this._tabElement.clientWidth > tabElement.offsetLeft;
    968             if (!intersects)
    969                 continue;
    970 
    971             if (Math.abs(event.pageX - this._dragStartX) < tabElement.clientWidth / 2 + 5)
    972                 break;
    973 
    974             if (event.pageX - this._dragStartX > 0) {
    975                 tabElement = tabElement.nextSibling;
    976                 ++i;
    977             }
    978 
    979             var oldOffsetLeft = this._tabElement.offsetLeft;
    980             this._tabbedPane._insertBefore(this, i);
    981             this._dragStartX += this._tabElement.offsetLeft - oldOffsetLeft;
    982             break;
    983         }
    984 
    985         if (!this._tabElement.previousSibling && event.pageX - this._dragStartX < 0) {
    986             this._tabElement.style.setProperty("left", "0px");
    987             return;
    988         }
    989         if (!this._tabElement.nextSibling && event.pageX - this._dragStartX > 0) {
    990             this._tabElement.style.setProperty("left", "0px");
    991             return;
    992         }
    993 
    994         this._tabElement.style.setProperty("position", "relative");
    995         this._tabElement.style.setProperty("left", (event.pageX - this._dragStartX) + "px");
    996     },
    997 
    998     /**
    999      * @param {!Event} event
   1000      */
   1001     _endTabDragging: function(event)
   1002     {
   1003         this._tabElement.style.removeProperty("position");
   1004         this._tabElement.style.removeProperty("left");
   1005         delete this._dragStartX;
   1006     }
   1007 }
   1008 
   1009 /**
   1010  * @interface
   1011  */
   1012 WebInspector.TabbedPaneTabDelegate = function()
   1013 {
   1014 }
   1015 
   1016 WebInspector.TabbedPaneTabDelegate.prototype = {
   1017     /**
   1018      * @param {!WebInspector.TabbedPane} tabbedPane
   1019      * @param {!Array.<string>} ids
   1020      */
   1021     closeTabs: function(tabbedPane, ids) { }
   1022 }
   1023