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