Home | History | Annotate | Download | only in front_end
      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  * 1. Redistributions of source code must retain the above copyright
      9  * notice, this list of conditions and the following disclaimer.
     10  *
     11  * 2. Redistributions in binary form must reproduce the above
     12  * copyright notice, this list of conditions and the following disclaimer
     13  * in the documentation and/or other materials provided with the
     14  * distribution.
     15  *
     16  * THIS SOFTWARE IS PROVIDED BY GOOGLE INC. AND ITS CONTRIBUTORS
     17  * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
     18  * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
     19  * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GOOGLE INC.
     20  * OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
     21  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
     22  * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
     23  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
     24  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     25  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
     26  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     27  */
     28 
     29 /**
     30  * @interface
     31  */
     32 WebInspector.TabbedEditorContainerDelegate = function() { }
     33 
     34 WebInspector.TabbedEditorContainerDelegate.prototype = {
     35     /**
     36      * @param {WebInspector.UISourceCode} uiSourceCode
     37      * @return {WebInspector.SourceFrame}
     38      */
     39     viewForFile: function(uiSourceCode) { }
     40 }
     41 
     42 /**
     43  * @constructor
     44  * @extends {WebInspector.Object}
     45  * @param {WebInspector.TabbedEditorContainerDelegate} delegate
     46  * @param {string} settingName
     47  * @param {string} placeholderText
     48  */
     49 WebInspector.TabbedEditorContainer = function(delegate, settingName, placeholderText)
     50 {
     51     WebInspector.Object.call(this);
     52     this._delegate = delegate;
     53 
     54     this._tabbedPane = new WebInspector.TabbedPane();
     55     this._tabbedPane.setPlaceholderText(placeholderText);
     56     this._tabbedPane.setTabDelegate(new WebInspector.EditorContainerTabDelegate(this));
     57 
     58     this._tabbedPane.closeableTabs = true;
     59     this._tabbedPane.element.id = "scripts-editor-container-tabbed-pane";
     60 
     61     this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabClosed, this._tabClosed, this);
     62     this._tabbedPane.addEventListener(WebInspector.TabbedPane.EventTypes.TabSelected, this._tabSelected, this);
     63 
     64     this._tabIds = new Map();
     65     this._files = {};
     66 
     67     this._previouslyViewedFilesSetting = WebInspector.settings.createSetting(settingName, []);
     68     this._history = WebInspector.TabbedEditorContainer.History.fromObject(this._previouslyViewedFilesSetting.get());
     69 }
     70 
     71 
     72 WebInspector.TabbedEditorContainer.Events = {
     73     EditorSelected: "EditorSelected",
     74     EditorClosed: "EditorClosed"
     75 }
     76 
     77 WebInspector.TabbedEditorContainer._tabId = 0;
     78 
     79 WebInspector.TabbedEditorContainer.maximalPreviouslyViewedFilesCount = 30;
     80 
     81 WebInspector.TabbedEditorContainer.prototype = {
     82     /**
     83      * @return {WebInspector.View}
     84      */
     85     get view()
     86     {
     87         return this._tabbedPane;
     88     },
     89 
     90     /**
     91      * @type {WebInspector.SourceFrame}
     92      */
     93     get visibleView()
     94     {
     95         return this._tabbedPane.visibleView;
     96     },
     97 
     98     /**
     99      * @param {Element} parentElement
    100      */
    101     show: function(parentElement)
    102     {
    103         this._tabbedPane.show(parentElement);
    104     },
    105 
    106     /**
    107      * @param {WebInspector.UISourceCode} uiSourceCode
    108      */
    109     showFile: function(uiSourceCode)
    110     {
    111         this._innerShowFile(uiSourceCode, true);
    112     },
    113 
    114     /**
    115      * @return {Array.<WebInspector.UISourceCode>}
    116      */
    117     historyUISourceCodes: function()
    118     {
    119         // FIXME: there should be a way to fetch UISourceCode for its uri.
    120         var uriToUISourceCode = {};
    121         for (var id in this._files) {
    122             var uiSourceCode = this._files[id];
    123             uriToUISourceCode[uiSourceCode.uri()] = uiSourceCode;
    124         }
    125 
    126         var result = [];
    127         var uris = this._history._urls();
    128         for (var i = 0; i < uris.length; ++i) {
    129             var uiSourceCode = uriToUISourceCode[uris[i]];
    130             if (uiSourceCode)
    131                 result.push(uiSourceCode);
    132         }
    133         return result;
    134     },
    135 
    136     _addScrollAndSelectionListeners: function()
    137     {
    138         if (!this._currentView)
    139             return;
    140         this._currentView.addEventListener(WebInspector.SourceFrame.Events.ScrollChanged, this._scrollChanged, this);
    141         this._currentView.addEventListener(WebInspector.SourceFrame.Events.SelectionChanged, this._selectionChanged, this);
    142     },
    143 
    144     _removeScrollAndSelectionListeners: function()
    145     {
    146         if (!this._currentView)
    147             return;
    148         this._currentView.removeEventListener(WebInspector.SourceFrame.Events.ScrollChanged, this._scrollChanged, this);
    149         this._currentView.removeEventListener(WebInspector.SourceFrame.Events.SelectionChanged, this._selectionChanged, this);
    150     },
    151 
    152     _scrollChanged: function(event)
    153     {
    154         var lineNumber = /** @type {number} */ (event.data);
    155         this._history.updateScrollLineNumber(this._currentFile.uri(), lineNumber);
    156         this._history.save(this._previouslyViewedFilesSetting);
    157     },
    158 
    159     _selectionChanged: function(event)
    160     {
    161         var range = /** @type {WebInspector.TextRange} */ (event.data);
    162         this._history.updateSelectionRange(this._currentFile.uri(), range);
    163         this._history.save(this._previouslyViewedFilesSetting);
    164     },
    165 
    166     /**
    167      * @param {WebInspector.UISourceCode} uiSourceCode
    168      * @param {boolean=} userGesture
    169      */
    170     _innerShowFile: function(uiSourceCode, userGesture)
    171     {
    172         if (this._currentFile === uiSourceCode)
    173             return;
    174         this._removeScrollAndSelectionListeners();
    175         this._currentFile = uiSourceCode;
    176 
    177         var tabId = this._tabIds.get(uiSourceCode) || this._appendFileTab(uiSourceCode, userGesture);
    178 
    179         this._tabbedPane.selectTab(tabId, userGesture);
    180         if (userGesture)
    181             this._editorSelectedByUserAction();
    182 
    183         this._currentView = this.visibleView;
    184         this._addScrollAndSelectionListeners();
    185 
    186         this.dispatchEventToListeners(WebInspector.TabbedEditorContainer.Events.EditorSelected, this._currentFile);
    187     },
    188 
    189     /**
    190      * @param {WebInspector.UISourceCode} uiSourceCode
    191      * @return {string}
    192      */
    193     _titleForFile: function(uiSourceCode)
    194     {
    195         var maxDisplayNameLength = 30;
    196         var title = uiSourceCode.displayName(true).trimMiddle(maxDisplayNameLength);
    197         if (uiSourceCode.isDirty())
    198             title += "*";
    199         return title;
    200     },
    201 
    202     /**
    203      * @param {string} id
    204      * @param {string} nextTabId
    205      */
    206     _maybeCloseTab: function(id, nextTabId)
    207     {
    208         var uiSourceCode = this._files[id];
    209         var shouldPrompt = uiSourceCode.isDirty() && uiSourceCode.project().canSetFileContent();
    210         // FIXME: this should be replaced with common Save/Discard/Cancel dialog.
    211         if (!shouldPrompt || confirm(WebInspector.UIString("Are you sure you want to close unsaved file: %s?", uiSourceCode.name()))) {
    212             uiSourceCode.resetWorkingCopy();
    213             if (nextTabId)
    214                 this._tabbedPane.selectTab(nextTabId, true);
    215             this._tabbedPane.closeTab(id, true);
    216             return true;
    217         }
    218         return false;
    219     },
    220 
    221     /**
    222      * @param {Array.<string>} ids
    223      */
    224     _closeTabs: function(ids)
    225     {
    226         var dirtyTabs = [];
    227         var cleanTabs = [];
    228         for (var i = 0; i < ids.length; ++i) {
    229             var id = ids[i];
    230             var uiSourceCode = this._files[id];
    231             if (uiSourceCode.isDirty())
    232                 dirtyTabs.push(id);
    233             else
    234                 cleanTabs.push(id);
    235         }
    236         if (dirtyTabs.length)
    237             this._tabbedPane.selectTab(dirtyTabs[0], true);
    238         this._tabbedPane.closeTabs(cleanTabs, true);
    239         for (var i = 0; i < dirtyTabs.length; ++i) {
    240             var nextTabId = i + 1 < dirtyTabs.length ? dirtyTabs[i + 1] : null;
    241             if (!this._maybeCloseTab(dirtyTabs[i], nextTabId))
    242                 break;
    243         }
    244     },
    245 
    246     /**
    247      * @param {WebInspector.UISourceCode} uiSourceCode
    248      */
    249     addUISourceCode: function(uiSourceCode)
    250     {
    251         var uri = uiSourceCode.uri();
    252         if (this._userSelectedFiles)
    253             return;
    254 
    255         var index = this._history.index(uri)
    256         if (index === -1)
    257             return;
    258 
    259         var tabId = this._tabIds.get(uiSourceCode) || this._appendFileTab(uiSourceCode, false);
    260 
    261         if (!this._currentFile)
    262             return;
    263 
    264         // Select tab if this file was the last to be shown.
    265         if (!index) {
    266             this._innerShowFile(uiSourceCode, false);
    267             return;
    268         }
    269 
    270         var currentProjectType = this._currentFile.project().type();
    271         var addedProjectType = uiSourceCode.project().type();
    272         var snippetsProjectType = WebInspector.projectTypes.Snippets;
    273         if (this._history.index(this._currentFile.uri()) && currentProjectType === snippetsProjectType && addedProjectType !== snippetsProjectType)
    274             this._innerShowFile(uiSourceCode, false);
    275     },
    276 
    277     /**
    278      * @param {WebInspector.UISourceCode} uiSourceCode
    279      */
    280     removeUISourceCode: function(uiSourceCode)
    281     {
    282         this.removeUISourceCodes([uiSourceCode]);
    283     },
    284 
    285     /**
    286      * @param {Array.<WebInspector.UISourceCode>} uiSourceCodes
    287      */
    288     removeUISourceCodes: function(uiSourceCodes)
    289     {
    290         var tabIds = [];
    291         for (var i = 0; i < uiSourceCodes.length; ++i) {
    292             var uiSourceCode = uiSourceCodes[i];
    293             var tabId = this._tabIds.get(uiSourceCode);
    294             if (tabId)
    295                 tabIds.push(tabId);
    296         }
    297         this._tabbedPane.closeTabs(tabIds);
    298     },
    299 
    300     /**
    301      * @param {WebInspector.UISourceCode} uiSourceCode
    302      */
    303     _editorClosedByUserAction: function(uiSourceCode)
    304     {
    305         this._userSelectedFiles = true;
    306         this._history.remove(uiSourceCode.uri());
    307         this._updateHistory();
    308     },
    309 
    310     _editorSelectedByUserAction: function()
    311     {
    312         this._userSelectedFiles = true;
    313         this._updateHistory();
    314     },
    315 
    316     _updateHistory: function()
    317     {
    318         var tabIds = this._tabbedPane.lastOpenedTabIds(WebInspector.TabbedEditorContainer.maximalPreviouslyViewedFilesCount);
    319 
    320         function tabIdToURI(tabId)
    321         {
    322             return this._files[tabId].uri();
    323         }
    324 
    325         this._history.update(tabIds.map(tabIdToURI.bind(this)));
    326         this._history.save(this._previouslyViewedFilesSetting);
    327     },
    328 
    329     /**
    330      * @param {WebInspector.UISourceCode} uiSourceCode
    331      * @return {string}
    332      */
    333     _tooltipForFile: function(uiSourceCode)
    334     {
    335         return uiSourceCode.originURL();
    336     },
    337 
    338     /**
    339      * @param {WebInspector.UISourceCode} uiSourceCode
    340      * @param {boolean=} userGesture
    341      * @return {string}
    342      */
    343     _appendFileTab: function(uiSourceCode, userGesture)
    344     {
    345         var view = this._delegate.viewForFile(uiSourceCode);
    346         var title = this._titleForFile(uiSourceCode);
    347         var tooltip = this._tooltipForFile(uiSourceCode);
    348 
    349         var tabId = this._generateTabId();
    350         this._tabIds.put(uiSourceCode, tabId);
    351         this._files[tabId] = uiSourceCode;
    352 
    353         var savedSelectionRange = this._history.selectionRange(uiSourceCode.uri());
    354         if (savedSelectionRange)
    355             view.setSelection(savedSelectionRange);
    356         var savedScrollLineNumber = this._history.scrollLineNumber(uiSourceCode.uri());
    357         if (savedScrollLineNumber)
    358             view.scrollToLine(savedScrollLineNumber);
    359 
    360         this._tabbedPane.appendTab(tabId, title, view, tooltip, userGesture);
    361 
    362         this._addUISourceCodeListeners(uiSourceCode);
    363         return tabId;
    364     },
    365 
    366     /**
    367      * @param {WebInspector.Event} event
    368      */
    369     _tabClosed: function(event)
    370     {
    371         var tabId = /** @type {string} */ (event.data.tabId);
    372         var userGesture = /** @type {boolean} */ (event.data.isUserGesture);
    373 
    374         var uiSourceCode = this._files[tabId];
    375         if (this._currentFile === uiSourceCode) {
    376             this._removeScrollAndSelectionListeners();
    377             delete this._currentView;
    378             delete this._currentFile;
    379         }
    380         this._tabIds.remove(uiSourceCode);
    381         delete this._files[tabId];
    382 
    383         this._removeUISourceCodeListeners(uiSourceCode);
    384 
    385         this.dispatchEventToListeners(WebInspector.TabbedEditorContainer.Events.EditorClosed, uiSourceCode);
    386 
    387         if (userGesture)
    388             this._editorClosedByUserAction(uiSourceCode);
    389     },
    390 
    391     /**
    392      * @param {WebInspector.Event} event
    393      */
    394     _tabSelected: function(event)
    395     {
    396         var tabId = /** @type {string} */ (event.data.tabId);
    397         var userGesture = /** @type {boolean} */ (event.data.isUserGesture);
    398 
    399         var uiSourceCode = this._files[tabId];
    400         this._innerShowFile(uiSourceCode, userGesture);
    401     },
    402 
    403     /**
    404      * @param {WebInspector.UISourceCode} uiSourceCode
    405      */
    406     _addUISourceCodeListeners: function(uiSourceCode)
    407     {
    408         uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.TitleChanged, this._uiSourceCodeTitleChanged, this);
    409         uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._uiSourceCodeWorkingCopyChanged, this);
    410         uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._uiSourceCodeWorkingCopyCommitted, this);
    411         uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.SavedStateUpdated, this._uiSourceCodeSavedStateUpdated, this);
    412         uiSourceCode.addEventListener(WebInspector.UISourceCode.Events.FormattedChanged, this._uiSourceCodeFormattedChanged, this);
    413     },
    414 
    415     /**
    416      * @param {WebInspector.UISourceCode} uiSourceCode
    417      */
    418     _removeUISourceCodeListeners: function(uiSourceCode)
    419     {
    420         uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.TitleChanged, this._uiSourceCodeTitleChanged, this);
    421         uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyChanged, this._uiSourceCodeWorkingCopyChanged, this);
    422         uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.WorkingCopyCommitted, this._uiSourceCodeWorkingCopyCommitted, this);
    423         uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.SavedStateUpdated, this._uiSourceCodeSavedStateUpdated, this);
    424         uiSourceCode.removeEventListener(WebInspector.UISourceCode.Events.FormattedChanged, this._uiSourceCodeFormattedChanged, this);
    425     },
    426 
    427     /**
    428      * @param {WebInspector.UISourceCode} uiSourceCode
    429      */
    430     _updateFileTitle: function(uiSourceCode)
    431     {
    432         var tabId = this._tabIds.get(uiSourceCode);
    433         if (tabId) {
    434             var title = this._titleForFile(uiSourceCode);
    435             this._tabbedPane.changeTabTitle(tabId, title);
    436             if (uiSourceCode.hasUnsavedCommittedChanges())
    437                 this._tabbedPane.setTabIcon(tabId, "editor-container-unsaved-committed-changes-icon", WebInspector.UIString("Changes to this file were not saved to file system."));
    438             else
    439                 this._tabbedPane.setTabIcon(tabId, "");
    440         }
    441     },
    442 
    443     _uiSourceCodeTitleChanged: function(event)
    444     {
    445         var uiSourceCode = /** @type {WebInspector.UISourceCode} */ (event.target);
    446         this._updateFileTitle(uiSourceCode);
    447         this._updateHistory();
    448     },
    449 
    450     _uiSourceCodeWorkingCopyChanged: function(event)
    451     {
    452         var uiSourceCode = /** @type {WebInspector.UISourceCode} */ (event.target);
    453         this._updateFileTitle(uiSourceCode);
    454     },
    455 
    456     _uiSourceCodeWorkingCopyCommitted: function(event)
    457     {
    458         var uiSourceCode = /** @type {WebInspector.UISourceCode} */ (event.target);
    459         this._updateFileTitle(uiSourceCode);
    460     },
    461 
    462     _uiSourceCodeSavedStateUpdated: function(event)
    463     {
    464         var uiSourceCode = /** @type {WebInspector.UISourceCode} */ (event.target);
    465         this._updateFileTitle(uiSourceCode);
    466     },
    467 
    468     _uiSourceCodeFormattedChanged: function(event)
    469     {
    470         var uiSourceCode = /** @type {WebInspector.UISourceCode} */ (event.target);
    471         this._updateFileTitle(uiSourceCode);
    472     },
    473 
    474     reset: function()
    475     {
    476         delete this._userSelectedFiles;
    477     },
    478 
    479     /**
    480      * @return {string}
    481      */
    482     _generateTabId: function()
    483     {
    484         return "tab_" + (WebInspector.TabbedEditorContainer._tabId++);
    485     },
    486 
    487     /**
    488      * @return {WebInspector.UISourceCode} uiSourceCode
    489      */
    490     currentFile: function()
    491     {
    492         return this._currentFile;
    493     },
    494 
    495     __proto__: WebInspector.Object.prototype
    496 }
    497 
    498 /**
    499  * @constructor
    500  * @param {string} url
    501  * @param {WebInspector.TextRange=} selectionRange
    502  * @param {number=} scrollLineNumber
    503  */
    504 WebInspector.TabbedEditorContainer.HistoryItem = function(url, selectionRange, scrollLineNumber)
    505 {
    506     /** @const */ this.url = url;
    507     /** @const */ this._isSerializable = url.length < WebInspector.TabbedEditorContainer.HistoryItem.serializableUrlLengthLimit;
    508     this.selectionRange = selectionRange;
    509     this.scrollLineNumber = scrollLineNumber;
    510 }
    511 
    512 WebInspector.TabbedEditorContainer.HistoryItem.serializableUrlLengthLimit = 4096;
    513 
    514 /**
    515  * @param {Object} serializedHistoryItem
    516  * @return {WebInspector.TabbedEditorContainer.HistoryItem}
    517  */
    518 WebInspector.TabbedEditorContainer.HistoryItem.fromObject = function (serializedHistoryItem)
    519 {
    520     var selectionRange = serializedHistoryItem.selectionRange ? WebInspector.TextRange.fromObject(serializedHistoryItem.selectionRange) : null;
    521     return new WebInspector.TabbedEditorContainer.HistoryItem(serializedHistoryItem.url, selectionRange, serializedHistoryItem.scrollLineNumber);
    522 }
    523 
    524 WebInspector.TabbedEditorContainer.HistoryItem.prototype = {
    525     /**
    526      * @return {?Object}
    527      */
    528     serializeToObject: function()
    529     {
    530         if (!this._isSerializable)
    531             return null;
    532         var serializedHistoryItem = {};
    533         serializedHistoryItem.url = this.url;
    534         serializedHistoryItem.selectionRange = this.selectionRange;
    535         serializedHistoryItem.scrollLineNumber = this.scrollLineNumber;
    536         return serializedHistoryItem;
    537     },
    538 
    539     __proto__: WebInspector.Object.prototype
    540 }
    541 
    542 /**
    543  * @constructor
    544  * @param {Array.<WebInspector.TabbedEditorContainer.HistoryItem>} items
    545  */
    546 WebInspector.TabbedEditorContainer.History = function(items)
    547 {
    548     this._items = items;
    549     this._rebuildItemIndex();
    550 }
    551 
    552 /**
    553  * @param {!Array.<!Object>} serializedHistory
    554  * @return {WebInspector.TabbedEditorContainer.History}
    555  */
    556 WebInspector.TabbedEditorContainer.History.fromObject = function(serializedHistory)
    557 {
    558     var items = [];
    559     for (var i = 0; i < serializedHistory.length; ++i)
    560         items.push(WebInspector.TabbedEditorContainer.HistoryItem.fromObject(serializedHistory[i]));
    561     return new WebInspector.TabbedEditorContainer.History(items);
    562 }
    563 
    564 WebInspector.TabbedEditorContainer.History.prototype = {
    565     /**
    566      * @param {string} url
    567      * @return {number}
    568      */
    569     index: function(url)
    570     {
    571         var index = this._itemsIndex[url];
    572         if (typeof index === "number")
    573             return index;
    574         return -1;
    575     },
    576 
    577     _rebuildItemIndex: function()
    578     {
    579         this._itemsIndex = {};
    580         for (var i = 0; i < this._items.length; ++i) {
    581             console.assert(!this._itemsIndex.hasOwnProperty(this._items[i].url));
    582             this._itemsIndex[this._items[i].url] = i;
    583         }
    584     },
    585 
    586     /**
    587      * @param {string} url
    588      * @return {WebInspector.TextRange|undefined}
    589      */
    590     selectionRange: function(url)
    591     {
    592         var index = this.index(url);
    593         return index !== -1 ? this._items[index].selectionRange : undefined;
    594     },
    595 
    596     /**
    597      * @param {string} url
    598      * @param {WebInspector.TextRange} selectionRange
    599      */
    600     updateSelectionRange: function(url, selectionRange)
    601     {
    602         if (!selectionRange)
    603             return;
    604         var index = this.index(url);
    605         if (index === -1)
    606             return;
    607         this._items[index].selectionRange = selectionRange;
    608     },
    609 
    610     /**
    611      * @param {string} url
    612      * @return {number|undefined}
    613      */
    614     scrollLineNumber: function(url)
    615     {
    616         var index = this.index(url);
    617         return index !== -1 ? this._items[index].scrollLineNumber : undefined;
    618     },
    619 
    620     /**
    621      * @param {string} url
    622      * @param {number} scrollLineNumber
    623      */
    624     updateScrollLineNumber: function(url, scrollLineNumber)
    625     {
    626         var index = this.index(url);
    627         if (index === -1)
    628             return;
    629         this._items[index].scrollLineNumber = scrollLineNumber;
    630     },
    631 
    632     /**
    633      * @param {Array.<string>} urls
    634      */
    635     update: function(urls)
    636     {
    637         for (var i = urls.length - 1; i >= 0; --i) {
    638             var index = this.index(urls[i]);
    639             var item;
    640             if (index !== -1) {
    641                 item = this._items[index];
    642                 this._items.splice(index, 1);
    643             } else
    644                 item = new WebInspector.TabbedEditorContainer.HistoryItem(urls[i]);
    645             this._items.unshift(item);
    646             this._rebuildItemIndex();
    647         }
    648     },
    649 
    650     /**
    651      * @param {string} url
    652      */
    653     remove: function(url)
    654     {
    655         var index = this.index(url);
    656         if (index !== -1) {
    657             this._items.splice(index, 1);
    658             this._rebuildItemIndex();
    659         }
    660     },
    661 
    662     /**
    663      * @param {WebInspector.Setting} setting
    664      */
    665     save: function(setting)
    666     {
    667         setting.set(this._serializeToObject());
    668     },
    669 
    670     /**
    671      * @return {!Array.<!Object>}
    672      */
    673     _serializeToObject: function()
    674     {
    675         var serializedHistory = [];
    676         for (var i = 0; i < this._items.length; ++i) {
    677             var serializedItem = this._items[i].serializeToObject();
    678             if (serializedItem)
    679                 serializedHistory.push(serializedItem);
    680             if (serializedHistory.length === WebInspector.TabbedEditorContainer.maximalPreviouslyViewedFilesCount)
    681                 break;
    682         }
    683         return serializedHistory;
    684     },
    685 
    686 
    687     /**
    688      * @return {Array.<string>}
    689      */
    690     _urls: function()
    691     {
    692         var result = [];
    693         for (var i = 0; i < this._items.length; ++i)
    694             result.push(this._items[i].url);
    695         return result;
    696     },
    697 
    698     __proto__: WebInspector.Object.prototype
    699 }
    700 
    701 /**
    702  * @constructor
    703  * @implements {WebInspector.TabbedPaneTabDelegate}
    704  * @param {WebInspector.TabbedEditorContainer} editorContainer
    705  */
    706 WebInspector.EditorContainerTabDelegate = function(editorContainer)
    707 {
    708     this._editorContainer = editorContainer;
    709 }
    710 
    711 WebInspector.EditorContainerTabDelegate.prototype = {
    712     /**
    713      * @param {WebInspector.TabbedPane} tabbedPane
    714      * @param {Array.<string>} ids
    715      */
    716     closeTabs: function(tabbedPane, ids)
    717     {
    718         this._editorContainer._closeTabs(ids);
    719     }
    720 }
    721