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