Home | History | Annotate | Download | only in front-end
      1 /*
      2  * Copyright (C) 2007, 2008, 2010 Apple Inc.  All rights reserved.
      3  * Copyright (C) 2009 Joseph Pecoraro
      4  *
      5  * Redistribution and use in source and binary forms, with or without
      6  * modification, are permitted provided that the following conditions
      7  * are met:
      8  *
      9  * 1.  Redistributions of source code must retain the above copyright
     10  *     notice, this list of conditions and the following disclaimer.
     11  * 2.  Redistributions in binary form must reproduce the above copyright
     12  *     notice, this list of conditions and the following disclaimer in the
     13  *     documentation and/or other materials provided with the distribution.
     14  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
     15  *     its contributors may be used to endorse or promote products derived
     16  *     from this software without specific prior written permission.
     17  *
     18  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
     19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
     20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
     21  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
     22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
     23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
     24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
     25  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
     26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
     27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
     28  */
     29 
     30 WebInspector.ResourcesPanel = function(database)
     31 {
     32     WebInspector.Panel.call(this, "resources");
     33 
     34     WebInspector.settings.installApplicationSetting("resourcesLastSelectedItem", {});
     35 
     36     this.createSidebar();
     37     this.sidebarElement.addStyleClass("outline-disclosure");
     38     this.sidebarElement.addStyleClass("filter-all");
     39     this.sidebarElement.addStyleClass("children");
     40     this.sidebarElement.addStyleClass("small");
     41     this.sidebarTreeElement.removeStyleClass("sidebar-tree");
     42 
     43     this.resourcesListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Frames"), "Frames", ["frame-storage-tree-item"]);
     44     this.sidebarTree.appendChild(this.resourcesListTreeElement);
     45 
     46     this.databasesListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Databases"), "Databases", ["database-storage-tree-item"]);
     47     this.sidebarTree.appendChild(this.databasesListTreeElement);
     48 
     49     this.localStorageListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Local Storage"), "LocalStorage", ["domstorage-storage-tree-item", "local-storage"]);
     50     this.sidebarTree.appendChild(this.localStorageListTreeElement);
     51 
     52     this.sessionStorageListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Session Storage"), "SessionStorage", ["domstorage-storage-tree-item", "session-storage"]);
     53     this.sidebarTree.appendChild(this.sessionStorageListTreeElement);
     54 
     55     this.cookieListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Cookies"), "Cookies", ["cookie-storage-tree-item"]);
     56     this.sidebarTree.appendChild(this.cookieListTreeElement);
     57 
     58     this.applicationCacheListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Application Cache"), "ApplicationCache", ["application-cache-storage-tree-item"]);
     59     this.sidebarTree.appendChild(this.applicationCacheListTreeElement);
     60 
     61     this.storageViews = document.createElement("div");
     62     this.storageViews.id = "storage-views";
     63     this.storageViews.className = "diff-container";
     64     this.element.appendChild(this.storageViews);
     65 
     66     this.storageViewStatusBarItemsContainer = document.createElement("div");
     67     this.storageViewStatusBarItemsContainer.className = "status-bar-items";
     68 
     69     this._databases = [];
     70     this._domStorage = [];
     71     this._cookieViews = {};
     72     this._origins = {};
     73     this._domains = {};
     74 
     75     this.sidebarElement.addEventListener("mousemove", this._onmousemove.bind(this), false);
     76     this.sidebarElement.addEventListener("mouseout", this._onmouseout.bind(this), false);
     77 
     78     WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceUpdated, this._refreshResource, this);
     79 }
     80 
     81 WebInspector.ResourcesPanel.prototype = {
     82     get toolbarItemLabel()
     83     {
     84         return WebInspector.UIString("Resources");
     85     },
     86 
     87     get statusBarItems()
     88     {
     89         return [this.storageViewStatusBarItemsContainer];
     90     },
     91 
     92     elementsToRestoreScrollPositionsFor: function()
     93     {
     94         return [this.sidebarElement];
     95     },
     96 
     97     show: function()
     98     {
     99         WebInspector.Panel.prototype.show.call(this);
    100 
    101         this._populateResourceTree();
    102 
    103         if (this.visibleView && this.visibleView.resource)
    104             this._showResourceView(this.visibleView.resource);
    105     },
    106 
    107     loadEventFired: function()
    108     {
    109         this._initDefaultSelection();
    110     },
    111 
    112     _initDefaultSelection: function()
    113     {
    114         var itemURL = WebInspector.settings.resourcesLastSelectedItem;
    115         if (itemURL) {
    116             for (var treeElement = this.sidebarTree.children[0]; treeElement; treeElement = treeElement.traverseNextTreeElement(false, this.sidebarTree, true)) {
    117                 if (treeElement.itemURL === itemURL) {
    118                     treeElement.select();
    119                     treeElement.reveal();
    120                     return;
    121                 }
    122             }
    123         }
    124 
    125         if (WebInspector.mainResource && this.resourcesListTreeElement && this.resourcesListTreeElement.expanded)
    126             this.showResource(WebInspector.mainResource);
    127     },
    128 
    129     reset: function()
    130     {
    131         delete this._initializedDefaultSelection;
    132         this._origins = {};
    133         this._domains = {};
    134         for (var i = 0; i < this._databases.length; ++i) {
    135             var database = this._databases[i];
    136             delete database._tableViews;
    137             delete database._queryView;
    138         }
    139         this._databases = [];
    140 
    141         var domStorageLength = this._domStorage.length;
    142         for (var i = 0; i < this._domStorage.length; ++i) {
    143             var domStorage = this._domStorage[i];
    144             delete domStorage._domStorageView;
    145         }
    146         this._domStorage = [];
    147 
    148         this._cookieViews = {};
    149 
    150         this._applicationCacheView = null;
    151         delete this._cachedApplicationCacheViewStatus;
    152 
    153         this.databasesListTreeElement.removeChildren();
    154         this.localStorageListTreeElement.removeChildren();
    155         this.sessionStorageListTreeElement.removeChildren();
    156         this.cookieListTreeElement.removeChildren();
    157         this.applicationCacheListTreeElement.removeChildren();
    158         this.storageViews.removeChildren();
    159 
    160         this.storageViewStatusBarItemsContainer.removeChildren();
    161 
    162         if (this.sidebarTree.selectedTreeElement)
    163             this.sidebarTree.selectedTreeElement.deselect();
    164     },
    165 
    166     _populateResourceTree: function()
    167     {
    168         if (this._treeElementForFrameId)
    169             return;
    170 
    171         this._treeElementForFrameId = {};
    172         WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameAdded, this._frameAdded, this);
    173         WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameNavigated, this._frameNavigated, this);
    174         WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameDetached, this._frameDetached, this);
    175         WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.ResourceAdded, this._resourceAdded, this);
    176         WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.CachedResourcesLoaded, this._cachedResourcesLoaded, this);
    177 
    178         function populateFrame(frameId)
    179         {
    180             var subframes = WebInspector.resourceTreeModel.subframes(frameId);
    181             for (var i = 0; i < subframes.length; ++i) {
    182                 this._frameAdded({data:subframes[i]});
    183                 populateFrame.call(this, subframes[i].id);
    184             }
    185 
    186             var resources = WebInspector.resourceTreeModel.resources(frameId);
    187             for (var i = 0; i < resources.length; ++i)
    188                 this._resourceAdded({data:resources[i]});
    189         }
    190         populateFrame.call(this, 0);
    191     },
    192 
    193     _frameAdded: function(event)
    194     {
    195         var frame = event.data;
    196         var frameId = frame.id;
    197         var parentFrameId = frame.parentId;
    198         var title = frame.name;
    199         var subtitle = new WebInspector.Resource(null, frame.url).displayName;
    200         this.addDocumentURL(frame.url);
    201 
    202         var frameTreeElement = this._treeElementForFrameId[frameId];
    203         if (frameTreeElement) {
    204             // Maintain sorted order.
    205             var parent = frameTreeElement.parent;
    206             parent.removeChild(frameTreeElement);
    207             frameTreeElement.setTitles(title, subtitle);
    208             parent.appendChild(frameTreeElement);
    209             return;
    210         }
    211 
    212         var parentTreeElement = parentFrameId ? this._treeElementForFrameId[parentFrameId] : this.resourcesListTreeElement;
    213         if (!parentTreeElement) {
    214             console.warn("No frame with id:" + parentFrameId + " to route " + title + "/" + subtitle + " to.")
    215             return;
    216         }
    217 
    218         var frameTreeElement = new WebInspector.FrameTreeElement(this, frameId, title, subtitle);
    219         this._treeElementForFrameId[frameId] = frameTreeElement;
    220         parentTreeElement.appendChild(frameTreeElement);
    221     },
    222 
    223     _frameDetached: function(event)
    224     {
    225         var frameId = event.data;
    226         var frameTreeElement = this._treeElementForFrameId[frameId];
    227         if (!frameTreeElement)
    228             return;
    229         delete this._treeElementForFrameId[frameId];
    230         if (frameTreeElement.parent)
    231             frameTreeElement.parent.removeChild(frameTreeElement);
    232     },
    233 
    234     _resourceAdded: function(event)
    235     {
    236         var resource = event.data;
    237         var frameId = resource.frameId;
    238 
    239         if (resource.statusCode >= 301 && resource.statusCode <= 303)
    240             return;
    241 
    242         var frameTreeElement = this._treeElementForFrameId[frameId];
    243         if (!frameTreeElement) {
    244             // This is a frame's main resource, it will be retained
    245             // and re-added by the resource manager;
    246             return;
    247         }
    248 
    249         frameTreeElement.appendResource(resource);
    250     },
    251 
    252     _frameNavigated: function(event)
    253     {
    254         var frameId = event.data;
    255         if (!frameId) {
    256             // Total update.
    257             this.resourcesListTreeElement.removeChildren();
    258             this._treeElementForFrameId = {};
    259             this.reset();
    260             return;
    261         }
    262 
    263         var frameTreeElement = this._treeElementForFrameId[frameId];
    264         if (frameTreeElement)
    265             frameTreeElement.removeChildren();
    266     },
    267 
    268     _cachedResourcesLoaded: function()
    269     {
    270         this._initDefaultSelection();
    271     },
    272 
    273     _refreshResource: function(event)
    274     {
    275         var resource = event.data;
    276         // FIXME: do not add XHR in the first place based on the native instrumentation.
    277         if (resource.type === WebInspector.Resource.Type.XHR) {
    278             var resourceTreeElement = this._findTreeElementForResource(resource);
    279             if (resourceTreeElement)
    280                 resourceTreeElement.parent.removeChild(resourceTreeElement);
    281         }
    282     },
    283 
    284     addDatabase: function(database)
    285     {
    286         this._databases.push(database);
    287 
    288         var databaseTreeElement = new WebInspector.DatabaseTreeElement(this, database);
    289         database._databasesTreeElement = databaseTreeElement;
    290         this.databasesListTreeElement.appendChild(databaseTreeElement);
    291     },
    292 
    293     addDocumentURL: function(url)
    294     {
    295         var parsedURL = url.asParsedURL();
    296         if (!parsedURL)
    297             return;
    298 
    299         var domain = parsedURL.host;
    300         if (!this._domains[domain]) {
    301             this._domains[domain] = true;
    302 
    303             var cookieDomainTreeElement = new WebInspector.CookieTreeElement(this, domain);
    304             this.cookieListTreeElement.appendChild(cookieDomainTreeElement);
    305 
    306             var applicationCacheTreeElement = new WebInspector.ApplicationCacheTreeElement(this, domain);
    307             this.applicationCacheListTreeElement.appendChild(applicationCacheTreeElement);
    308         }
    309     },
    310 
    311     addDOMStorage: function(domStorage)
    312     {
    313         this._domStorage.push(domStorage);
    314         var domStorageTreeElement = new WebInspector.DOMStorageTreeElement(this, domStorage, (domStorage.isLocalStorage ? "local-storage" : "session-storage"));
    315         domStorage._domStorageTreeElement = domStorageTreeElement;
    316         if (domStorage.isLocalStorage)
    317             this.localStorageListTreeElement.appendChild(domStorageTreeElement);
    318         else
    319             this.sessionStorageListTreeElement.appendChild(domStorageTreeElement);
    320     },
    321 
    322     selectDatabase: function(databaseId)
    323     {
    324         var database;
    325         for (var i = 0, len = this._databases.length; i < len; ++i) {
    326             database = this._databases[i];
    327             if (database.id === databaseId) {
    328                 this.showDatabase(database);
    329                 database._databasesTreeElement.select();
    330                 return;
    331             }
    332         }
    333     },
    334 
    335     selectDOMStorage: function(storageId)
    336     {
    337         var domStorage = this._domStorageForId(storageId);
    338         if (domStorage) {
    339             this.showDOMStorage(domStorage);
    340             domStorage._domStorageTreeElement.select();
    341         }
    342     },
    343 
    344     canShowAnchorLocation: function(anchor)
    345     {
    346         return !!WebInspector.resourceForURL(anchor.href);
    347     },
    348 
    349     showAnchorLocation: function(anchor)
    350     {
    351         var resource = WebInspector.resourceForURL(anchor.href);
    352         if (resource.type === WebInspector.Resource.Type.XHR) {
    353             // Show XHRs in the network panel only.
    354             if (WebInspector.panels.network && WebInspector.panels.network.canShowAnchorLocation(anchor)) {
    355                 WebInspector.currentPanel = WebInspector.panels.network;
    356                 WebInspector.panels.network.showAnchorLocation(anchor);
    357             }
    358             return;
    359         }
    360         this.showResource(resource, anchor.getAttribute("line_number") - 1);
    361     },
    362 
    363     showResource: function(resource, line)
    364     {
    365         var resourceTreeElement = this._findTreeElementForResource(resource);
    366         if (resourceTreeElement) {
    367             resourceTreeElement.reveal();
    368             resourceTreeElement.select();
    369         }
    370 
    371         if (line !== undefined) {
    372             var view = WebInspector.ResourceView.resourceViewForResource(resource);
    373             if (view.highlightLine)
    374                 view.highlightLine(line);
    375         }
    376         return true;
    377     },
    378 
    379     _showResourceView: function(resource)
    380     {
    381         var view = WebInspector.ResourceView.resourceViewForResource(resource);
    382         this._fetchAndApplyDiffMarkup(view, resource);
    383         this._innerShowView(view);
    384     },
    385 
    386     _showRevisionView: function(revision)
    387     {
    388         if (!revision._view)
    389             revision._view = new WebInspector.RevisionSourceFrame(revision);
    390         var view = revision._view;
    391         this._fetchAndApplyDiffMarkup(view, revision.resource, revision);
    392         this._innerShowView(view);
    393     },
    394 
    395     _fetchAndApplyDiffMarkup: function(view, resource, revision)
    396     {
    397         var baseRevision = resource.history[0];
    398         if (!baseRevision)
    399             return;
    400         if (!(view instanceof WebInspector.SourceFrame))
    401             return;
    402 
    403         baseRevision.requestContent(step1.bind(this));
    404 
    405         function step1(baseContent)
    406         {
    407             (revision ? revision : resource).requestContent(step2.bind(this, baseContent));
    408         }
    409 
    410         function step2(baseContent, revisionContent)
    411         {
    412             this._applyDiffMarkup(view, baseContent, revisionContent);
    413         }
    414     },
    415 
    416     _applyDiffMarkup: function(view, baseContent, newContent) {
    417         var oldLines = baseContent.split(/\r?\n/);
    418         var newLines = newContent.split(/\r?\n/);
    419 
    420         var diff = Array.diff(oldLines, newLines);
    421 
    422         var diffData = {};
    423         diffData.added = [];
    424         diffData.removed = [];
    425         diffData.changed = [];
    426 
    427         var offset = 0;
    428         var right = diff.right;
    429         for (var i = 0; i < right.length; ++i) {
    430             if (typeof right[i] === "string") {
    431                 if (right.length > i + 1 && right[i + 1].row === i + 1 - offset)
    432                     diffData.changed.push(i);
    433                 else {
    434                     diffData.added.push(i);
    435                     offset++;
    436                 }
    437             } else
    438                 offset = i - right[i].row;
    439         }
    440         view.markDiff(diffData);
    441     },
    442 
    443     showDatabase: function(database, tableName)
    444     {
    445         if (!database)
    446             return;
    447 
    448         var view;
    449         if (tableName) {
    450             if (!("_tableViews" in database))
    451                 database._tableViews = {};
    452             view = database._tableViews[tableName];
    453             if (!view) {
    454                 view = new WebInspector.DatabaseTableView(database, tableName);
    455                 database._tableViews[tableName] = view;
    456             }
    457         } else {
    458             view = database._queryView;
    459             if (!view) {
    460                 view = new WebInspector.DatabaseQueryView(database);
    461                 database._queryView = view;
    462             }
    463         }
    464 
    465         this._innerShowView(view);
    466     },
    467 
    468     showDOMStorage: function(domStorage)
    469     {
    470         if (!domStorage)
    471             return;
    472 
    473         var view;
    474         view = domStorage._domStorageView;
    475         if (!view) {
    476             view = new WebInspector.DOMStorageItemsView(domStorage);
    477             domStorage._domStorageView = view;
    478         }
    479 
    480         this._innerShowView(view);
    481     },
    482 
    483     showCookies: function(treeElement, cookieDomain)
    484     {
    485         var view = this._cookieViews[cookieDomain];
    486         if (!view) {
    487             view = new WebInspector.CookieItemsView(treeElement, cookieDomain);
    488             this._cookieViews[cookieDomain] = view;
    489         }
    490 
    491         this._innerShowView(view);
    492     },
    493 
    494     showApplicationCache: function(treeElement, appcacheDomain)
    495     {
    496         var view = this._applicationCacheView;
    497         if (!view) {
    498             view = new WebInspector.ApplicationCacheItemsView(treeElement, appcacheDomain);
    499             this._applicationCacheView = view;
    500         }
    501 
    502         this._innerShowView(view);
    503 
    504         if ("_cachedApplicationCacheViewStatus" in this)
    505             this._applicationCacheView.updateStatus(this._cachedApplicationCacheViewStatus);
    506     },
    507 
    508     showCategoryView: function(categoryName)
    509     {
    510         if (!this._categoryView)
    511             this._categoryView = new WebInspector.StorageCategoryView();
    512         this._categoryView.setText(categoryName);
    513         this._innerShowView(this._categoryView);
    514     },
    515 
    516     _innerShowView: function(view)
    517     {
    518         if (this.visibleView)
    519             this.visibleView.hide();
    520 
    521         view.show(this.storageViews);
    522         this.visibleView = view;
    523 
    524         this.storageViewStatusBarItemsContainer.removeChildren();
    525         var statusBarItems = view.statusBarItems || [];
    526         for (var i = 0; i < statusBarItems.length; ++i)
    527             this.storageViewStatusBarItemsContainer.appendChild(statusBarItems[i]);
    528     },
    529 
    530     closeVisibleView: function()
    531     {
    532         if (this.visibleView)
    533             this.visibleView.hide();
    534         delete this.visibleView;
    535     },
    536 
    537     updateDatabaseTables: function(database)
    538     {
    539         if (!database || !database._databasesTreeElement)
    540             return;
    541 
    542         database._databasesTreeElement.shouldRefreshChildren = true;
    543 
    544         if (!("_tableViews" in database))
    545             return;
    546 
    547         var tableNamesHash = {};
    548         var self = this;
    549         function tableNamesCallback(tableNames)
    550         {
    551             var tableNamesLength = tableNames.length;
    552             for (var i = 0; i < tableNamesLength; ++i)
    553                 tableNamesHash[tableNames[i]] = true;
    554 
    555             for (var tableName in database._tableViews) {
    556                 if (!(tableName in tableNamesHash)) {
    557                     if (self.visibleView === database._tableViews[tableName])
    558                         self.closeVisibleView();
    559                     delete database._tableViews[tableName];
    560                 }
    561             }
    562         }
    563         database.getTableNames(tableNamesCallback);
    564     },
    565 
    566     dataGridForResult: function(columnNames, values)
    567     {
    568         var numColumns = columnNames.length;
    569         if (!numColumns)
    570             return null;
    571 
    572         var columns = {};
    573 
    574         for (var i = 0; i < columnNames.length; ++i) {
    575             var column = {};
    576             column.width = columnNames[i].length;
    577             column.title = columnNames[i];
    578             column.sortable = true;
    579 
    580             columns[columnNames[i]] = column;
    581         }
    582 
    583         var nodes = [];
    584         for (var i = 0; i < values.length / numColumns; ++i) {
    585             var data = {};
    586             for (var j = 0; j < columnNames.length; ++j)
    587                 data[columnNames[j]] = values[numColumns * i + j];
    588 
    589             var node = new WebInspector.DataGridNode(data, false);
    590             node.selectable = false;
    591             nodes.push(node);
    592         }
    593 
    594         var dataGrid = new WebInspector.DataGrid(columns);
    595         var length = nodes.length;
    596         for (var i = 0; i < length; ++i)
    597             dataGrid.appendChild(nodes[i]);
    598 
    599         dataGrid.addEventListener("sorting changed", this._sortDataGrid.bind(this, dataGrid), this);
    600         return dataGrid;
    601     },
    602 
    603     _sortDataGrid: function(dataGrid)
    604     {
    605         var nodes = dataGrid.children.slice();
    606         var sortColumnIdentifier = dataGrid.sortColumnIdentifier;
    607         var sortDirection = dataGrid.sortOrder === "ascending" ? 1 : -1;
    608         var columnIsNumeric = true;
    609 
    610         for (var i = 0; i < nodes.length; i++) {
    611             if (isNaN(Number(nodes[i].data[sortColumnIdentifier])))
    612                 columnIsNumeric = false;
    613         }
    614 
    615         function comparator(dataGridNode1, dataGridNode2)
    616         {
    617             var item1 = dataGridNode1.data[sortColumnIdentifier];
    618             var item2 = dataGridNode2.data[sortColumnIdentifier];
    619 
    620             var comparison;
    621             if (columnIsNumeric) {
    622                 // Sort numbers based on comparing their values rather than a lexicographical comparison.
    623                 var number1 = parseFloat(item1);
    624                 var number2 = parseFloat(item2);
    625                 comparison = number1 < number2 ? -1 : (number1 > number2 ? 1 : 0);
    626             } else
    627                 comparison = item1 < item2 ? -1 : (item1 > item2 ? 1 : 0);
    628 
    629             return sortDirection * comparison;
    630         }
    631 
    632         nodes.sort(comparator);
    633         dataGrid.removeChildren();
    634         for (var i = 0; i < nodes.length; i++)
    635             dataGrid.appendChild(nodes[i]);
    636     },
    637 
    638     updateDOMStorage: function(storageId)
    639     {
    640         var domStorage = this._domStorageForId(storageId);
    641         if (!domStorage)
    642             return;
    643 
    644         var view = domStorage._domStorageView;
    645         if (this.visibleView && view === this.visibleView)
    646             domStorage._domStorageView.update();
    647     },
    648 
    649     updateApplicationCacheStatus: function(status)
    650     {
    651         this._cachedApplicationCacheViewStatus = status;
    652         if (this._applicationCacheView && this._applicationCacheView === this.visibleView)
    653             this._applicationCacheView.updateStatus(status);
    654     },
    655 
    656     updateNetworkState: function(isNowOnline)
    657     {
    658         if (this._applicationCacheView && this._applicationCacheView === this.visibleView)
    659             this._applicationCacheView.updateNetworkState(isNowOnline);
    660     },
    661 
    662     updateManifest: function(manifest)
    663     {
    664         if (this._applicationCacheView && this._applicationCacheView === this.visibleView)
    665             this._applicationCacheView.updateManifest(manifest);
    666     },
    667 
    668     _domStorageForId: function(storageId)
    669     {
    670         if (!this._domStorage)
    671             return null;
    672         var domStorageLength = this._domStorage.length;
    673         for (var i = 0; i < domStorageLength; ++i) {
    674             var domStorage = this._domStorage[i];
    675             if (domStorage.id == storageId)
    676                 return domStorage;
    677         }
    678         return null;
    679     },
    680 
    681     updateMainViewWidth: function(width)
    682     {
    683         this.storageViews.style.left = width + "px";
    684         this.storageViewStatusBarItemsContainer.style.left = width + "px";
    685         this.resize();
    686     },
    687 
    688     get searchableViews()
    689     {
    690         var views = [];
    691 
    692         const visibleView = this.visibleView;
    693         if (visibleView && visibleView.performSearch)
    694             views.push(visibleView);
    695 
    696         function callback(resourceTreeElement)
    697         {
    698             var resource = resourceTreeElement._resource;
    699             var resourceView = WebInspector.ResourceView.resourceViewForResource(resource);
    700             if (resourceView.performSearch && resourceView !== visibleView)
    701                 views.push(resourceView);
    702         }
    703         this._forAllResourceTreeElements(callback);
    704         return views;
    705     },
    706 
    707     _forAllResourceTreeElements: function(callback)
    708     {
    709         var stop = false;
    710         for (var treeElement = this.resourcesListTreeElement; !stop && treeElement; treeElement = treeElement.traverseNextTreeElement(false, this.resourcesListTreeElement, true)) {
    711             if (treeElement instanceof WebInspector.FrameResourceTreeElement)
    712                 stop = callback(treeElement);
    713         }
    714     },
    715 
    716     searchMatchFound: function(view, matches)
    717     {
    718         if (!view.resource)
    719             return;
    720         var treeElement = this._findTreeElementForResource(view.resource);
    721         if (treeElement)
    722             treeElement.searchMatchFound(matches);
    723     },
    724 
    725     _findTreeElementForResource: function(resource)
    726     {
    727         function isAncestor(ancestor, object)
    728         {
    729             // Redirects, XHRs do not belong to the tree, it is fine to silently return false here.
    730             return false;
    731         }
    732 
    733         function getParent(object)
    734         {
    735             // Redirects, XHRs do not belong to the tree, it is fine to silently return false here.
    736             return null;
    737         }
    738 
    739         return this.sidebarTree.findTreeElement(resource, isAncestor, getParent);
    740     },
    741 
    742     searchCanceled: function(startingNewSearch)
    743     {
    744         WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch);
    745 
    746         if (startingNewSearch)
    747             return;
    748 
    749         function callback(resourceTreeElement)
    750         {
    751             resourceTreeElement._errorsWarningsUpdated();
    752         }
    753         this._forAllResourceTreeElements(callback);
    754     },
    755 
    756     performSearch: function(query)
    757     {
    758         function callback(resourceTreeElement)
    759         {
    760             resourceTreeElement._resetBubble();
    761         }
    762         this._forAllResourceTreeElements(callback);
    763         WebInspector.Panel.prototype.performSearch.call(this, query);
    764     },
    765 
    766     showView: function(view)
    767     {
    768         if (view)
    769             this.showResource(view.resource);
    770     },
    771 
    772     _onmousemove: function(event)
    773     {
    774         var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
    775         if (!nodeUnderMouse)
    776             return;
    777 
    778         var listNode = nodeUnderMouse.enclosingNodeOrSelfWithNodeName("li");
    779         if (!listNode)
    780             return;
    781 
    782         var element = listNode.treeElement;
    783         if (this._previousHoveredElement === element)
    784             return;
    785 
    786         if (this._previousHoveredElement) {
    787             this._previousHoveredElement.hovered = false;
    788             delete this._previousHoveredElement;
    789         }
    790 
    791         if (element instanceof WebInspector.FrameTreeElement) {
    792             this._previousHoveredElement = element;
    793             element.hovered = true;
    794         }
    795     },
    796 
    797     _onmouseout: function(event)
    798     {
    799         if (this._previousHoveredElement) {
    800             this._previousHoveredElement.hovered = false;
    801             delete this._previousHoveredElement;
    802         }
    803     }
    804 }
    805 
    806 WebInspector.ResourcesPanel.prototype.__proto__ = WebInspector.Panel.prototype;
    807 
    808 WebInspector.BaseStorageTreeElement = function(storagePanel, representedObject, title, iconClasses, hasChildren, noIcon)
    809 {
    810     TreeElement.call(this, "", representedObject, hasChildren);
    811     this._storagePanel = storagePanel;
    812     this._titleText = title;
    813     this._iconClasses = iconClasses;
    814     this._noIcon = noIcon;
    815 }
    816 
    817 WebInspector.BaseStorageTreeElement.prototype = {
    818     onattach: function()
    819     {
    820         this.listItemElement.removeChildren();
    821         if (this._iconClasses) {
    822             for (var i = 0; i < this._iconClasses.length; ++i)
    823                 this.listItemElement.addStyleClass(this._iconClasses[i]);
    824         }
    825 
    826         var selectionElement = document.createElement("div");
    827         selectionElement.className = "selection";
    828         this.listItemElement.appendChild(selectionElement);
    829 
    830         if (!this._noIcon) {
    831             this.imageElement = document.createElement("img");
    832             this.imageElement.className = "icon";
    833             this.listItemElement.appendChild(this.imageElement);
    834         }
    835 
    836         this.titleElement = document.createElement("div");
    837         this.titleElement.className = "base-storage-tree-element-title";
    838         this.titleElement.textContent = this._titleText;
    839         this.listItemElement.appendChild(this.titleElement);
    840     },
    841 
    842     onselect: function()
    843     {
    844         var itemURL = this.itemURL;
    845         if (itemURL)
    846             WebInspector.settings.resourcesLastSelectedItem = itemURL;
    847     },
    848 
    849     onreveal: function()
    850     {
    851         if (this.listItemElement)
    852             this.listItemElement.scrollIntoViewIfNeeded(false);
    853     },
    854 
    855     get titleText()
    856     {
    857         return this._titleText;
    858     },
    859 
    860     set titleText(titleText)
    861     {
    862         this._titleText = titleText;
    863         if (this.titleElement)
    864             this.titleElement.textContent = this._titleText;
    865     },
    866 
    867     isEventWithinDisclosureTriangle: function()
    868     {
    869         // Override it since we use margin-left in place of treeoutline's text-indent.
    870         // Hence we need to take padding into consideration. This all is needed for leading
    871         // icons in the tree.
    872         const paddingLeft = 14;
    873         var left = this.listItemElement.totalOffsetLeft + paddingLeft;
    874         return event.pageX >= left && event.pageX <= left + this.arrowToggleWidth && this.hasChildren;
    875     }
    876 }
    877 
    878 WebInspector.BaseStorageTreeElement.prototype.__proto__ = TreeElement.prototype;
    879 
    880 WebInspector.StorageCategoryTreeElement = function(storagePanel, categoryName, settingsKey, iconClasses, noIcon)
    881 {
    882     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, categoryName, iconClasses, true, noIcon);
    883     this._expandedSettingKey = "resources" + settingsKey + "Expanded";
    884     WebInspector.settings.installApplicationSetting(this._expandedSettingKey, settingsKey === "Frames");
    885     this._categoryName = categoryName;
    886 }
    887 
    888 WebInspector.StorageCategoryTreeElement.prototype = {
    889     get itemURL()
    890     {
    891         return "category://" + this._categoryName;
    892     },
    893 
    894     onselect: function()
    895     {
    896         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
    897         this._storagePanel.showCategoryView(this._categoryName);
    898     },
    899 
    900     onattach: function()
    901     {
    902         WebInspector.BaseStorageTreeElement.prototype.onattach.call(this);
    903         if (WebInspector.settings[this._expandedSettingKey])
    904             this.expand();
    905     },
    906 
    907     onexpand: function()
    908     {
    909         WebInspector.settings[this._expandedSettingKey] = true;
    910     },
    911 
    912     oncollapse: function()
    913     {
    914         WebInspector.settings[this._expandedSettingKey] = false;
    915     }
    916 }
    917 WebInspector.StorageCategoryTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
    918 
    919 WebInspector.FrameTreeElement = function(storagePanel, frameId, title, subtitle)
    920 {
    921     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, "", ["frame-storage-tree-item"]);
    922     this._frameId = frameId;
    923     this.setTitles(title, subtitle);
    924     this._categoryElements = {};
    925 }
    926 
    927 WebInspector.FrameTreeElement.prototype = {
    928     get itemURL()
    929     {
    930         return "frame://" + encodeURI(this._displayName);
    931     },
    932 
    933     onattach: function()
    934     {
    935         WebInspector.BaseStorageTreeElement.prototype.onattach.call(this);
    936         if (this._titleToSetOnAttach || this._subtitleToSetOnAttach) {
    937             this.setTitles(this._titleToSetOnAttach, this._subtitleToSetOnAttach);
    938             delete this._titleToSetOnAttach;
    939             delete this._subtitleToSetOnAttach;
    940         }
    941     },
    942 
    943     onselect: function()
    944     {
    945         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
    946         this._storagePanel.showCategoryView(this._displayName);
    947 
    948         this.listItemElement.removeStyleClass("hovered");
    949         DOMAgent.hideFrameHighlight();
    950     },
    951 
    952     get displayName()
    953     {
    954         return this._displayName;
    955     },
    956 
    957     setTitles: function(title, subtitle)
    958     {
    959         this._displayName = title || "";
    960         if (subtitle)
    961             this._displayName += " (" + subtitle + ")";
    962 
    963         if (this.parent) {
    964             this.titleElement.textContent = title || "";
    965             if (subtitle) {
    966                 var subtitleElement = document.createElement("span");
    967                 subtitleElement.className = "base-storage-tree-element-subtitle";
    968                 subtitleElement.textContent = "(" + subtitle + ")";
    969                 this.titleElement.appendChild(subtitleElement);
    970             }
    971         } else {
    972             this._titleToSetOnAttach = title;
    973             this._subtitleToSetOnAttach = subtitle;
    974         }
    975     },
    976 
    977     set hovered(hovered)
    978     {
    979         if (hovered) {
    980             this.listItemElement.addStyleClass("hovered");
    981             DOMAgent.highlightFrame(this._frameId);
    982         } else {
    983             this.listItemElement.removeStyleClass("hovered");
    984             DOMAgent.hideFrameHighlight();
    985         }
    986     },
    987 
    988     appendResource: function(resource)
    989     {
    990         var categoryName = resource.category.name;
    991         var categoryElement = resource.category === WebInspector.resourceCategories.documents ? this : this._categoryElements[categoryName];
    992         if (!categoryElement) {
    993             categoryElement = new WebInspector.StorageCategoryTreeElement(this._storagePanel, resource.category.title, categoryName, null, true);
    994             this._categoryElements[resource.category.name] = categoryElement;
    995             this._insertInPresentationOrder(this, categoryElement);
    996         }
    997         var resourceTreeElement = new WebInspector.FrameResourceTreeElement(this._storagePanel, resource);
    998         this._insertInPresentationOrder(categoryElement, resourceTreeElement);
    999         resourceTreeElement._populateRevisions();
   1000     },
   1001 
   1002     appendChild: function(treeElement)
   1003     {
   1004         this._insertInPresentationOrder(this, treeElement);
   1005     },
   1006 
   1007     _insertInPresentationOrder: function(parentTreeElement, childTreeElement)
   1008     {
   1009         // Insert in the alphabetical order, first frames, then resources. Document resource goes last.
   1010         function typeWeight(treeElement)
   1011         {
   1012             if (treeElement instanceof WebInspector.StorageCategoryTreeElement)
   1013                 return 2;
   1014             if (treeElement instanceof WebInspector.FrameTreeElement)
   1015                 return 1;
   1016             return 3;
   1017         }
   1018 
   1019         function compare(treeElement1, treeElement2)
   1020         {
   1021             var typeWeight1 = typeWeight(treeElement1);
   1022             var typeWeight2 = typeWeight(treeElement2);
   1023 
   1024             var result;
   1025             if (typeWeight1 > typeWeight2)
   1026                 result = 1;
   1027             else if (typeWeight1 < typeWeight2)
   1028                 result = -1;
   1029             else {
   1030                 var title1 = treeElement1.displayName || treeElement1.titleText;
   1031                 var title2 = treeElement2.displayName || treeElement2.titleText;
   1032                 result = title1.localeCompare(title2);
   1033             }
   1034             return result;
   1035         }
   1036 
   1037         var children = parentTreeElement.children;
   1038         var i;
   1039         for (i = 0; i < children.length; ++i) {
   1040             if (compare(childTreeElement, children[i]) < 0)
   1041                 break;
   1042         }
   1043         parentTreeElement.insertChild(childTreeElement, i);
   1044     }
   1045 }
   1046 
   1047 WebInspector.FrameTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
   1048 
   1049 WebInspector.FrameResourceTreeElement = function(storagePanel, resource)
   1050 {
   1051     WebInspector.BaseStorageTreeElement.call(this, storagePanel, resource, resource.displayName, ["resource-sidebar-tree-item", "resources-category-" + resource.category.name]);
   1052     this._resource = resource;
   1053     this._resource.addEventListener("errors-warnings-updated", this._errorsWarningsUpdated, this);
   1054     this._resource.addEventListener(WebInspector.Resource.Events.RevisionAdded, this._revisionAdded, this);
   1055     this.tooltip = resource.url;
   1056 }
   1057 
   1058 WebInspector.FrameResourceTreeElement.prototype = {
   1059     get itemURL()
   1060     {
   1061         return this._resource.url;
   1062     },
   1063 
   1064     onselect: function()
   1065     {
   1066         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
   1067         this._storagePanel._showResourceView(this._resource);
   1068     },
   1069 
   1070     ondblclick: function(event)
   1071     {
   1072         PageAgent.openInInspectedWindow(this._resource.url);
   1073     },
   1074 
   1075     onattach: function()
   1076     {
   1077         WebInspector.BaseStorageTreeElement.prototype.onattach.call(this);
   1078 
   1079         if (this._resource.category === WebInspector.resourceCategories.images) {
   1080             var previewImage = document.createElement("img");
   1081             previewImage.className = "image-resource-icon-preview";
   1082             this._resource.populateImageSource(previewImage);
   1083 
   1084             var iconElement = document.createElement("div");
   1085             iconElement.className = "icon";
   1086             iconElement.appendChild(previewImage);
   1087             this.listItemElement.replaceChild(iconElement, this.imageElement);
   1088         }
   1089 
   1090         this._statusElement = document.createElement("div");
   1091         this._statusElement.className = "status";
   1092         this.listItemElement.insertBefore(this._statusElement, this.titleElement);
   1093 
   1094         this.listItemElement.draggable = true;
   1095         this.listItemElement.addEventListener("dragstart", this._ondragstart.bind(this), false);
   1096     },
   1097 
   1098     _ondragstart: function(event)
   1099     {
   1100         event.dataTransfer.setData("text/plain", this._resource.content);
   1101         event.dataTransfer.effectAllowed = "copy";
   1102         return true;
   1103     },
   1104 
   1105     _setBubbleText: function(x)
   1106     {
   1107         if (!this._bubbleElement) {
   1108             this._bubbleElement = document.createElement("div");
   1109             this._bubbleElement.className = "bubble";
   1110             this._statusElement.appendChild(this._bubbleElement);
   1111         }
   1112 
   1113         this._bubbleElement.textContent = x;
   1114     },
   1115 
   1116     _resetBubble: function()
   1117     {
   1118         if (this._bubbleElement) {
   1119             this._bubbleElement.textContent = "";
   1120             this._bubbleElement.removeStyleClass("search-matches");
   1121             this._bubbleElement.removeStyleClass("warning");
   1122             this._bubbleElement.removeStyleClass("error");
   1123         }
   1124     },
   1125 
   1126     searchMatchFound: function(matches)
   1127     {
   1128         this._resetBubble();
   1129 
   1130         this._setBubbleText(matches);
   1131         this._bubbleElement.addStyleClass("search-matches");
   1132 
   1133         // Expand, do not scroll into view.
   1134         var currentAncestor = this.parent;
   1135         while (currentAncestor && !currentAncestor.root) {
   1136             if (!currentAncestor.expanded)
   1137                 currentAncestor.expand();
   1138             currentAncestor = currentAncestor.parent;
   1139         }
   1140     },
   1141 
   1142     _errorsWarningsUpdated: function()
   1143     {
   1144         // FIXME: move to the SourceFrame.
   1145         if (!this._resource.warnings && !this._resource.errors) {
   1146             var view = WebInspector.ResourceView.existingResourceViewForResource(this._resource);
   1147             if (view && view.clearMessages)
   1148                 view.clearMessages();
   1149         }
   1150 
   1151         if (this._storagePanel.currentQuery)
   1152             return;
   1153 
   1154         this._resetBubble();
   1155 
   1156         if (this._resource.warnings || this._resource.errors)
   1157             this._setBubbleText(this._resource.warnings + this._resource.errors);
   1158 
   1159         if (this._resource.warnings)
   1160             this._bubbleElement.addStyleClass("warning");
   1161 
   1162         if (this._resource.errors)
   1163             this._bubbleElement.addStyleClass("error");
   1164     },
   1165 
   1166     _populateRevisions: function()
   1167     {
   1168         for (var i = 0; i < this._resource.history.length; ++i)
   1169             this._appendRevision(this._resource.history[i]);
   1170     },
   1171 
   1172     _revisionAdded: function(event)
   1173     {
   1174         this._appendRevision(event.data);
   1175     },
   1176 
   1177     _appendRevision: function(revision)
   1178     {
   1179         this.insertChild(new WebInspector.ResourceRevisionTreeElement(this._storagePanel, revision), 0);
   1180         var oldView = WebInspector.ResourceView.existingResourceViewForResource(this._resource);
   1181         if (oldView) {
   1182             var newView = WebInspector.ResourceView.recreateResourceView(this._resource);
   1183             if (oldView === this._storagePanel.visibleView)
   1184                 this._storagePanel._showResourceView(this._resource);
   1185         }
   1186     }
   1187 }
   1188 
   1189 WebInspector.FrameResourceTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
   1190 
   1191 WebInspector.DatabaseTreeElement = function(storagePanel, database)
   1192 {
   1193     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, database.name, ["database-storage-tree-item"], true);
   1194     this._database = database;
   1195 }
   1196 
   1197 WebInspector.DatabaseTreeElement.prototype = {
   1198     get itemURL()
   1199     {
   1200         return "database://" + encodeURI(this._database.name);
   1201     },
   1202 
   1203     onselect: function()
   1204     {
   1205         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
   1206         this._storagePanel.showDatabase(this._database);
   1207     },
   1208 
   1209     oncollapse: function()
   1210     {
   1211         // Request a refresh after every collapse so the next
   1212         // expand will have an updated table list.
   1213         this.shouldRefreshChildren = true;
   1214     },
   1215 
   1216     onpopulate: function()
   1217     {
   1218         this.removeChildren();
   1219 
   1220         function tableNamesCallback(tableNames)
   1221         {
   1222             var tableNamesLength = tableNames.length;
   1223             for (var i = 0; i < tableNamesLength; ++i)
   1224                 this.appendChild(new WebInspector.DatabaseTableTreeElement(this._storagePanel, this._database, tableNames[i]));
   1225         }
   1226         this._database.getTableNames(tableNamesCallback.bind(this));
   1227     }
   1228 
   1229 }
   1230 WebInspector.DatabaseTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
   1231 
   1232 WebInspector.DatabaseTableTreeElement = function(storagePanel, database, tableName)
   1233 {
   1234     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, tableName, ["database-storage-tree-item"]);
   1235     this._database = database;
   1236     this._tableName = tableName;
   1237 }
   1238 
   1239 WebInspector.DatabaseTableTreeElement.prototype = {
   1240     get itemURL()
   1241     {
   1242         return "database://" + encodeURI(this._database.name) + "/" + encodeURI(this._tableName);
   1243     },
   1244 
   1245     onselect: function()
   1246     {
   1247         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
   1248         this._storagePanel.showDatabase(this._database, this._tableName);
   1249     }
   1250 }
   1251 WebInspector.DatabaseTableTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
   1252 
   1253 WebInspector.DOMStorageTreeElement = function(storagePanel, domStorage, className)
   1254 {
   1255     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, domStorage.domain ? domStorage.domain : WebInspector.UIString("Local Files"), ["domstorage-storage-tree-item", className]);
   1256     this._domStorage = domStorage;
   1257 }
   1258 
   1259 WebInspector.DOMStorageTreeElement.prototype = {
   1260     get itemURL()
   1261     {
   1262         return "storage://" + this._domStorage.domain + "/" + (this._domStorage.isLocalStorage ? "local" : "session");
   1263     },
   1264 
   1265     onselect: function()
   1266     {
   1267         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
   1268         this._storagePanel.showDOMStorage(this._domStorage);
   1269     }
   1270 }
   1271 WebInspector.DOMStorageTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
   1272 
   1273 WebInspector.CookieTreeElement = function(storagePanel, cookieDomain)
   1274 {
   1275     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, cookieDomain ? cookieDomain : WebInspector.UIString("Local Files"), ["cookie-storage-tree-item"]);
   1276     this._cookieDomain = cookieDomain;
   1277 }
   1278 
   1279 WebInspector.CookieTreeElement.prototype = {
   1280     get itemURL()
   1281     {
   1282         return "cookies://" + this._cookieDomain;
   1283     },
   1284 
   1285     onselect: function()
   1286     {
   1287         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
   1288         this._storagePanel.showCookies(this, this._cookieDomain);
   1289     }
   1290 }
   1291 WebInspector.CookieTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
   1292 
   1293 WebInspector.ApplicationCacheTreeElement = function(storagePanel, appcacheDomain)
   1294 {
   1295     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, appcacheDomain ? appcacheDomain : WebInspector.UIString("Local Files"), ["application-cache-storage-tree-item"]);
   1296     this._appcacheDomain = appcacheDomain;
   1297 }
   1298 
   1299 WebInspector.ApplicationCacheTreeElement.prototype = {
   1300     get itemURL()
   1301     {
   1302         return "appcache://" + this._appcacheDomain;
   1303     },
   1304 
   1305     onselect: function()
   1306     {
   1307         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
   1308         this._storagePanel.showApplicationCache(this, this._appcacheDomain);
   1309     }
   1310 }
   1311 WebInspector.ApplicationCacheTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
   1312 
   1313 WebInspector.ResourceRevisionTreeElement = function(storagePanel, revision)
   1314 {
   1315     var title = revision.timestamp ? revision.timestamp.toLocaleTimeString() : WebInspector.UIString("(original)");
   1316     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, title, ["resource-sidebar-tree-item", "resources-category-" + revision.resource.category.name]);
   1317     if (revision.timestamp)
   1318         this.tooltip = revision.timestamp.toLocaleString();
   1319     this._revision = revision;
   1320 }
   1321 
   1322 WebInspector.ResourceRevisionTreeElement.prototype = {
   1323     get itemURL()
   1324     {
   1325         return this._revision.resource.url;
   1326     },
   1327 
   1328     onattach: function()
   1329     {
   1330         WebInspector.BaseStorageTreeElement.prototype.onattach.call(this);
   1331         this.listItemElement.draggable = true;
   1332         this.listItemElement.addEventListener("dragstart", this._ondragstart.bind(this), false);
   1333         this.listItemElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true);
   1334     },
   1335 
   1336     onselect: function()
   1337     {
   1338         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
   1339         this._storagePanel._showRevisionView(this._revision);
   1340     },
   1341 
   1342     _ondragstart: function(event)
   1343     {
   1344         if (this._revision.content) {
   1345             event.dataTransfer.setData("text/plain", this._revision.content);
   1346             event.dataTransfer.effectAllowed = "copy";
   1347             return true;
   1348         }
   1349     },
   1350 
   1351     _handleContextMenuEvent: function(event)
   1352     {
   1353         var contextMenu = new WebInspector.ContextMenu();
   1354         contextMenu.appendItem(WebInspector.UIString("Revert to this revision"), this._revision.revertToThis.bind(this._revision));
   1355         contextMenu.show(event);
   1356     }
   1357 }
   1358 
   1359 WebInspector.ResourceRevisionTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
   1360 
   1361 WebInspector.StorageCategoryView = function()
   1362 {
   1363     WebInspector.View.call(this);
   1364 
   1365     this.element.addStyleClass("storage-view");
   1366 
   1367     this._emptyMsgElement = document.createElement("div");
   1368     this._emptyMsgElement.className = "storage-empty-view";
   1369     this.element.appendChild(this._emptyMsgElement);
   1370 }
   1371 
   1372 WebInspector.StorageCategoryView.prototype = {
   1373     setText: function(text)
   1374     {
   1375         this._emptyMsgElement.textContent = text;
   1376     }
   1377 }
   1378 
   1379 WebInspector.StorageCategoryView.prototype.__proto__ = WebInspector.View.prototype;
   1380