5860bfdc0ece3c087e72a60042262e9b5aae5f9d
[framework/web/webkit-efl.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 /**
31  * @constructor
32  * @extends {WebInspector.Panel}
33  */
34 WebInspector.ResourcesPanel = function(database)
35 {
36     WebInspector.Panel.call(this, "resources");
37
38     WebInspector.settings.resourcesLastSelectedItem = WebInspector.settings.createSetting("resourcesLastSelectedItem", {});
39
40     this.createSidebar();
41     this.sidebarElement.addStyleClass("outline-disclosure");
42     this.sidebarElement.addStyleClass("filter-all");
43     this.sidebarElement.addStyleClass("children");
44     this.sidebarElement.addStyleClass("small");
45     this.sidebarTreeElement.removeStyleClass("sidebar-tree");
46
47     this.resourcesListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Frames"), "Frames", ["frame-storage-tree-item"]);
48     this.sidebarTree.appendChild(this.resourcesListTreeElement);
49
50     this.databasesListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Databases"), "Databases", ["database-storage-tree-item"]);
51     this.sidebarTree.appendChild(this.databasesListTreeElement);
52
53     this.localStorageListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Local Storage"), "LocalStorage", ["domstorage-storage-tree-item", "local-storage"]);
54     this.sidebarTree.appendChild(this.localStorageListTreeElement);
55
56     this.sessionStorageListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Session Storage"), "SessionStorage", ["domstorage-storage-tree-item", "session-storage"]);
57     this.sidebarTree.appendChild(this.sessionStorageListTreeElement);
58
59     this.cookieListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Cookies"), "Cookies", ["cookie-storage-tree-item"]);
60     this.sidebarTree.appendChild(this.cookieListTreeElement);
61
62     this.applicationCacheListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Application Cache"), "ApplicationCache", ["application-cache-storage-tree-item"]);
63     this.sidebarTree.appendChild(this.applicationCacheListTreeElement);
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     function viewGetter()
83     {
84         return this.visibleView;
85     }
86     WebInspector.GoToLineDialog.install(this, viewGetter.bind(this));
87
88     WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.OnLoad, this._onLoadEventFired, this);
89 }
90
91 WebInspector.ResourcesPanel.prototype = {
92     get toolbarItemLabel()
93     {
94         return WebInspector.UIString("Resources");
95     },
96
97     get statusBarItems()
98     {
99         return [this.storageViewStatusBarItemsContainer];
100     },
101
102     elementsToRestoreScrollPositionsFor: function()
103     {
104         return [this.sidebarElement];
105     },
106
107     show: function()
108     {
109         WebInspector.Panel.prototype.show.call(this);
110
111         this._populateResourceTree();
112     },
113
114     _onLoadEventFired: function()
115     {
116         this._initDefaultSelection();
117     },
118
119     _initDefaultSelection: function()
120     {
121         if (!this._treeElementForFrameId)
122             return;
123
124         var itemURL = WebInspector.settings.resourcesLastSelectedItem.get();
125         if (itemURL) {
126             for (var treeElement = this.sidebarTree.children[0]; treeElement; treeElement = treeElement.traverseNextTreeElement(false, this.sidebarTree, true)) {
127                 if (treeElement.itemURL === itemURL) {
128                     treeElement.revealAndSelect(true);
129                     return;
130                 }
131             }
132         }
133
134         if (WebInspector.mainResource && this.resourcesListTreeElement && this.resourcesListTreeElement.expanded)
135             this.showResource(WebInspector.mainResource);
136     },
137
138     reset: function()
139     {
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             if (database._queryView)
146                 database._queryView.removeEventListener(WebInspector.DatabaseQueryView.Events.SchemaUpdated, this._updateDatabaseTables, this);
147             delete database._queryView;
148         }
149         this._databases = [];
150
151         var domStorageLength = this._domStorage.length;
152         for (var i = 0; i < this._domStorage.length; ++i) {
153             var domStorage = this._domStorage[i];
154             delete domStorage._domStorageView;
155         }
156         this._domStorage = [];
157
158         this._cookieViews = {};
159
160         this._applicationCacheView = null;
161         delete this._cachedApplicationCacheViewStatus;
162
163         this.databasesListTreeElement.removeChildren();
164         this.localStorageListTreeElement.removeChildren();
165         this.sessionStorageListTreeElement.removeChildren();
166         this.cookieListTreeElement.removeChildren();
167         this.applicationCacheListTreeElement.removeChildren();
168         this.storageViews.removeChildren();
169
170         this.storageViewStatusBarItemsContainer.removeChildren();
171
172         if (this.sidebarTree.selectedTreeElement)
173             this.sidebarTree.selectedTreeElement.deselect();
174
175         var childViews = this.childViews();
176         for (var i = 0; i < childViews.length; ++i)
177             this.removeChildView(childViews[i]);
178     },
179
180     _populateResourceTree: function()
181     {
182         if (this._treeElementForFrameId)
183             return;
184
185         this._treeElementForFrameId = {};
186         WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameAdded, this._frameAdded, this);
187         WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameNavigated, this._frameNavigated, this);
188         WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameDetached, this._frameDetached, this);
189         WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.ResourceAdded, this._resourceAdded, this);
190         WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.CachedResourcesLoaded, this._cachedResourcesLoaded, this);
191         WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.WillLoadCachedResources, this._resetResourcesTree, this);
192
193         function populateFrame(frameId)
194         {
195             var subframes = WebInspector.resourceTreeModel.subframes(frameId);
196             for (var i = 0; i < subframes.length; ++i) {
197                 this._frameAdded({data:subframes[i]});
198                 populateFrame.call(this, subframes[i].id);
199             }
200
201             var resources = WebInspector.resourceTreeModel.resources(frameId);
202             for (var i = 0; i < resources.length; ++i)
203                 this._resourceAdded({data:resources[i]});
204         }
205         populateFrame.call(this, "");
206
207         this._initDefaultSelection();
208     },
209
210     _frameAdded: function(event)
211     {
212         var frame = event.data;
213         var parentFrameId = frame.parentId;
214
215         var parentTreeElement = parentFrameId ? this._treeElementForFrameId[parentFrameId] : this.resourcesListTreeElement;
216         if (!parentTreeElement) {
217             console.warn("No frame with id:" + parentFrameId + " to route " + frame.name + "/" + frame.url + " to.")
218             return;
219         }
220
221         var frameTreeElement = new WebInspector.FrameTreeElement(this, frame);
222         this._treeElementForFrameId[frame.id] = frameTreeElement;
223         parentTreeElement.appendChild(frameTreeElement);
224     },
225
226     _frameDetached: function(event)
227     {
228         var frameId = event.data;
229         var frameTreeElement = this._treeElementForFrameId[frameId];
230         if (!frameTreeElement)
231             return;
232
233         delete this._treeElementForFrameId[frameId];
234         if (frameTreeElement.parent)
235             frameTreeElement.parent.removeChild(frameTreeElement);
236     },
237
238     _resourceAdded: function(event)
239     {
240         var resource = event.data;
241         var frameId = resource.frameId;
242
243         if (resource.statusCode >= 301 && resource.statusCode <= 303)
244             return;
245
246         var frameTreeElement = this._treeElementForFrameId[frameId];
247         if (!frameTreeElement) {
248             // This is a frame's main resource, it will be retained
249             // and re-added by the resource manager;
250             return;
251         }
252
253         frameTreeElement.appendResource(resource);
254     },
255
256     _frameNavigated: function(event)
257     {
258         var frameId = event.data.frame.id;
259         var frameTreeElement = this._treeElementForFrameId[frameId];
260         if (frameTreeElement)
261             frameTreeElement.frameNavigated(event.data.frame);
262     },
263
264     _resetResourcesTree: function()
265     {
266         this.resourcesListTreeElement.removeChildren();
267         this._treeElementForFrameId = {};
268         this.reset();
269     },
270
271     _cachedResourcesLoaded: function()
272     {
273         this._initDefaultSelection();
274     },
275
276     addDatabase: function(database)
277     {
278         this._databases.push(database);
279
280         var databaseTreeElement = new WebInspector.DatabaseTreeElement(this, database);
281         database._databasesTreeElement = databaseTreeElement;
282         this.databasesListTreeElement.appendChild(databaseTreeElement);
283     },
284
285     addDocumentURL: function(url)
286     {
287         var parsedURL = url.asParsedURL();
288         if (!parsedURL)
289             return;
290
291         var domain = parsedURL.host;
292         if (!this._domains[domain]) {
293             this._domains[domain] = true;
294
295             var cookieDomainTreeElement = new WebInspector.CookieTreeElement(this, domain);
296             this.cookieListTreeElement.appendChild(cookieDomainTreeElement);
297
298             var applicationCacheTreeElement = new WebInspector.ApplicationCacheTreeElement(this, domain);
299             this.applicationCacheListTreeElement.appendChild(applicationCacheTreeElement);
300         }
301     },
302
303     addDOMStorage: function(domStorage)
304     {
305         this._domStorage.push(domStorage);
306         var domStorageTreeElement = new WebInspector.DOMStorageTreeElement(this, domStorage, (domStorage.isLocalStorage ? "local-storage" : "session-storage"));
307         domStorage._domStorageTreeElement = domStorageTreeElement;
308         if (domStorage.isLocalStorage)
309             this.localStorageListTreeElement.appendChild(domStorageTreeElement);
310         else
311             this.sessionStorageListTreeElement.appendChild(domStorageTreeElement);
312     },
313
314     selectDatabase: function(databaseId)
315     {
316         var database;
317         for (var i = 0, len = this._databases.length; i < len; ++i) {
318             database = this._databases[i];
319             if (database.id === databaseId) {
320                 this.showDatabase(database);
321                 database._databasesTreeElement.select();
322                 return;
323             }
324         }
325     },
326
327     selectDOMStorage: function(storageId)
328     {
329         var domStorage = this._domStorageForId(storageId);
330         if (domStorage) {
331             this.showDOMStorage(domStorage);
332             domStorage._domStorageTreeElement.select();
333         }
334     },
335
336     canShowAnchorLocation: function(anchor)
337     {
338         return !!WebInspector.resourceForURL(anchor.href);
339     },
340
341     showAnchorLocation: function(anchor)
342     {
343         var resource = WebInspector.resourceForURL(anchor.href);
344         var lineNumber = anchor.hasAttribute("line_number") ? parseInt(anchor.getAttribute("line_number"), 10) : undefined;
345         this.showResource(resource, lineNumber);
346     },
347
348     /**
349      * @param {number=} line
350      */
351     showResource: function(resource, line)
352     {
353         var resourceTreeElement = this._findTreeElementForResource(resource);
354         if (resourceTreeElement)
355             resourceTreeElement.revealAndSelect();
356
357         if (typeof line === "number") {
358             var view = this._resourceViewForResource(resource);
359             if (view.canHighlightLine())
360                 view.highlightLine(line);
361         }
362         return true;
363     },
364
365     _showResourceView: function(resource)
366     {
367         var view = this._resourceViewForResource(resource);
368         if (!view) {
369             this.visibleView.hide();
370             return;
371         }
372         if (view.searchCanceled)
373             view.searchCanceled();
374         this._fetchAndApplyDiffMarkup(view, resource);
375         this._innerShowView(view);
376     },
377
378     _resourceViewForResource: function(resource)
379     {
380         if (WebInspector.ResourceView.hasTextContent(resource)) {
381             var treeElement = this._findTreeElementForResource(resource);
382             if (!treeElement)
383                 return null;
384             return treeElement.sourceView();
385         }
386         return WebInspector.ResourceView.nonSourceViewForResource(resource);
387     },
388
389     _showRevisionView: function(revision)
390     {
391         var view = this._sourceViewForRevision(revision);
392         this._fetchAndApplyDiffMarkup(view, revision.resource, revision);
393         this._innerShowView(view);
394     },
395
396     _sourceViewForRevision: function(revision)
397     {
398         var treeElement = this._findTreeElementForRevision(revision);
399         return treeElement.sourceView();
400     },
401
402     /**
403      * @param {WebInspector.ResourceRevision=} revision
404      */
405     _fetchAndApplyDiffMarkup: function(view, resource, revision)
406     {
407         var baseRevision = resource.history[0];
408         if (!baseRevision)
409             return;
410         if (!(view instanceof WebInspector.SourceFrame))
411             return;
412
413         baseRevision.requestContent(step1.bind(this));
414
415         function step1(baseContent)
416         {
417             (revision ? revision : resource).requestContent(step2.bind(this, baseContent));
418         }
419
420         function step2(baseContent, revisionContent)
421         {
422             this._applyDiffMarkup(view, baseContent, revisionContent);
423         }
424     },
425
426     _applyDiffMarkup: function(view, baseContent, newContent)
427     {
428         var diffData = TextDiff.compute(baseContent, newContent);
429         view.markDiff(diffData);
430     },
431
432     /**
433      * @param {string=} tableName
434      */
435     showDatabase: function(database, tableName)
436     {
437         if (!database)
438             return;
439
440         var view;
441         if (tableName) {
442             if (!("_tableViews" in database))
443                 database._tableViews = {};
444             view = database._tableViews[tableName];
445             if (!view) {
446                 view = new WebInspector.DatabaseTableView(database, tableName);
447                 database._tableViews[tableName] = view;
448             }
449         } else {
450             view = database._queryView;
451             if (!view) {
452                 view = new WebInspector.DatabaseQueryView(database);
453                 database._queryView = view;
454                 view.addEventListener(WebInspector.DatabaseQueryView.Events.SchemaUpdated, this._updateDatabaseTables, this);
455             }
456         }
457
458         this._innerShowView(view);
459     },
460
461     showDOMStorage: function(domStorage)
462     {
463         if (!domStorage)
464             return;
465
466         var view;
467         view = domStorage._domStorageView;
468         if (!view) {
469             view = new WebInspector.DOMStorageItemsView(domStorage);
470             domStorage._domStorageView = view;
471         }
472
473         this._innerShowView(view);
474     },
475
476     showCookies: function(treeElement, cookieDomain)
477     {
478         var view = this._cookieViews[cookieDomain];
479         if (!view) {
480             view = new WebInspector.CookieItemsView(treeElement, cookieDomain);
481             this._cookieViews[cookieDomain] = view;
482         }
483
484         this._innerShowView(view);
485     },
486
487     showApplicationCache: function(treeElement, appcacheDomain)
488     {
489         var view = this._applicationCacheView;
490         if (!view) {
491             view = new WebInspector.ApplicationCacheItemsView(treeElement, appcacheDomain);
492             this._applicationCacheView = view;
493         }
494
495         this._innerShowView(view);
496
497         if ("_cachedApplicationCacheViewStatus" in this)
498             this._applicationCacheView.updateStatus(this._cachedApplicationCacheViewStatus);
499     },
500
501     showCategoryView: function(categoryName)
502     {
503         if (!this._categoryView)
504             this._categoryView = new WebInspector.StorageCategoryView();
505         this._categoryView.setText(categoryName);
506         this._innerShowView(this._categoryView);
507     },
508
509     _innerShowView: function(view)
510     {
511         if (this.visibleView)
512             this.visibleView.hide();
513
514         this.addChildView(view);
515         view.show(this.storageViews);
516         this.visibleView = view;
517
518         this.storageViewStatusBarItemsContainer.removeChildren();
519         var statusBarItems = view.statusBarItems || [];
520         for (var i = 0; i < statusBarItems.length; ++i)
521             this.storageViewStatusBarItemsContainer.appendChild(statusBarItems[i]);
522     },
523
524     closeVisibleView: function()
525     {
526         if (!this.visibleView)
527             return;
528         this.visibleView.hide();
529         delete this.visibleView;
530     },
531
532     _updateDatabaseTables: function(event)
533     {
534         var database = event.data;
535
536         if (!database || !database._databasesTreeElement)
537             return;
538
539         database._databasesTreeElement.shouldRefreshChildren = true;
540
541         if (!("_tableViews" in database))
542             return;
543
544         var tableNamesHash = {};
545         var self = this;
546         function tableNamesCallback(tableNames)
547         {
548             var tableNamesLength = tableNames.length;
549             for (var i = 0; i < tableNamesLength; ++i)
550                 tableNamesHash[tableNames[i]] = true;
551
552             for (var tableName in database._tableViews) {
553                 if (!(tableName in tableNamesHash)) {
554                     if (self.visibleView === database._tableViews[tableName])
555                         self.closeVisibleView();
556                     delete database._tableViews[tableName];
557                 }
558             }
559         }
560         database.getTableNames(tableNamesCallback);
561     },
562
563     updateDOMStorage: function(storageId)
564     {
565         var domStorage = this._domStorageForId(storageId);
566         if (!domStorage)
567             return;
568
569         var view = domStorage._domStorageView;
570         if (this.visibleView && view === this.visibleView)
571             domStorage._domStorageView.update();
572     },
573
574     updateApplicationCacheStatus: function(status)
575     {
576         this._cachedApplicationCacheViewStatus = status;
577         if (this._applicationCacheView && this._applicationCacheView === this.visibleView)
578             this._applicationCacheView.updateStatus(status);
579     },
580
581     updateNetworkState: function(isNowOnline)
582     {
583         if (this._applicationCacheView && this._applicationCacheView === this.visibleView)
584             this._applicationCacheView.updateNetworkState(isNowOnline);
585     },
586
587     updateManifest: function(manifest)
588     {
589         if (this._applicationCacheView && this._applicationCacheView === this.visibleView)
590             this._applicationCacheView.updateManifest(manifest);
591     },
592
593     _domStorageForId: function(storageId)
594     {
595         if (!this._domStorage)
596             return null;
597         var domStorageLength = this._domStorage.length;
598         for (var i = 0; i < domStorageLength; ++i) {
599             var domStorage = this._domStorage[i];
600             if (domStorage.id == storageId)
601                 return domStorage;
602         }
603         return null;
604     },
605
606     updateMainViewWidth: function(width)
607     {
608         this.storageViews.style.left = width + "px";
609         this.storageViewStatusBarItemsContainer.style.left = width + "px";
610     },
611
612     performSearch: function(query)
613     {
614         this._resetSearchResults();
615         var regex = WebInspector.SourceFrame.createSearchRegex(query);
616         var totalMatchesCount = 0;
617
618         function searchInEditedResource(treeElement)
619         {
620             var resource = treeElement.representedObject;
621             if (resource.history.length == 0)
622                 return;
623             var matchesCount = countRegexMatches(regex, resource.content)
624             treeElement.searchMatchesFound(matchesCount);
625             totalMatchesCount += matchesCount;
626         }
627
628         function callback(error, result)
629         {
630             if (!error) {
631                 for (var i = 0; i < result.length; i++) {
632                     var searchResult = result[i];
633                     var frameTreeElement = this._treeElementForFrameId[searchResult.frameId];
634                     if (!frameTreeElement)
635                         continue;
636                     var resource = frameTreeElement.resourceByURL(searchResult.url);
637
638                     // FIXME: When the same script is used in several frames and this script contains at least
639                     // one search result then some search results can not be matched with a resource on panel.
640                     // https://bugs.webkit.org/show_bug.cgi?id=66005
641                     if (!resource)
642                         continue;
643
644                     if (resource.history.length > 0)
645                         continue; // Skip edited resources.
646                     this._findTreeElementForResource(resource).searchMatchesFound(searchResult.matchesCount);
647                     totalMatchesCount += searchResult.matchesCount;
648                 }
649             }
650
651             WebInspector.searchController.updateSearchMatchesCount(totalMatchesCount, this);
652             this._searchController = new WebInspector.ResourcesSearchController(this.resourcesListTreeElement);
653
654             if (this.sidebarTree.selectedTreeElement && this.sidebarTree.selectedTreeElement.searchMatchesCount)
655                 this.jumpToNextSearchResult();
656         }
657
658         this._forAllResourceTreeElements(searchInEditedResource.bind(this));
659         PageAgent.searchInResources(regex.source, !regex.ignoreCase, true, callback.bind(this));
660     },
661
662     _ensureViewSearchPerformed: function(callback)
663     {
664         function viewSearchPerformedCallback(searchId)
665         {
666             if (searchId !== this._lastViewSearchId)
667                 return; // Search is obsolete.
668             this._viewSearchInProgress = false;
669             callback();
670         }
671
672         if (!this._viewSearchInProgress) {
673             if (!this.visibleView.hasSearchResults()) {
674                 // We give id to each search, so that we can skip callbacks for obsolete searches.
675                 this._lastViewSearchId = this._lastViewSearchId ? this._lastViewSearchId + 1 : 0;
676                 this._viewSearchInProgress = true;
677                 this.visibleView.performSearch(this.currentQuery, viewSearchPerformedCallback.bind(this, this._lastViewSearchId));
678             } else
679                 callback();
680         }
681     },
682
683     _showSearchResult: function(searchResult)
684     {
685         this._lastSearchResultIndex = searchResult.index;
686         this._lastSearchResultTreeElement = searchResult.treeElement;
687
688         // At first show view for treeElement.
689         if (searchResult.treeElement !== this.sidebarTree.selectedTreeElement) {
690             this.showResource(searchResult.treeElement.representedObject);
691             WebInspector.searchController.focusSearchField();
692         }
693
694         function callback(searchId)
695         {
696             if (this.sidebarTree.selectedTreeElement !== this._lastSearchResultTreeElement)
697                 return; // User has selected another view while we were searching.
698             if (this._lastSearchResultIndex != -1)
699                 this.visibleView.jumpToSearchResult(this._lastSearchResultIndex);
700         }
701
702         // Then run SourceFrame search if needed and jump to search result index when done.
703         this._ensureViewSearchPerformed(callback.bind(this));
704     },
705
706     _resetSearchResults: function()
707     {
708         function callback(resourceTreeElement)
709         {
710             resourceTreeElement._resetSearchResults();
711         }
712
713         this._forAllResourceTreeElements(callback);
714         if (this.visibleView && this.visibleView.searchCanceled)
715             this.visibleView.searchCanceled();
716
717         this._lastSearchResultTreeElement = null;
718         this._lastSearchResultIndex = -1;
719         this._viewSearchInProgress = false;
720     },
721
722     searchCanceled: function()
723     {
724         function callback(resourceTreeElement)
725         {
726             resourceTreeElement._updateErrorsAndWarningsBubbles();
727         }
728
729         WebInspector.searchController.updateSearchMatchesCount(0, this);
730         this._resetSearchResults();
731         this._forAllResourceTreeElements(callback);
732     },
733
734     jumpToNextSearchResult: function()
735     {
736         if (!this.currentSearchMatches)
737             return;
738         var currentTreeElement = this.sidebarTree.selectedTreeElement;
739         var nextSearchResult = this._searchController.nextSearchResult(currentTreeElement);
740         this._showSearchResult(nextSearchResult);
741     },
742
743     jumpToPreviousSearchResult: function()
744     {
745         if (!this.currentSearchMatches)
746             return;
747         var currentTreeElement = this.sidebarTree.selectedTreeElement;
748         var previousSearchResult = this._searchController.previousSearchResult(currentTreeElement);
749         this._showSearchResult(previousSearchResult);
750     },
751
752     _forAllResourceTreeElements: function(callback)
753     {
754         var stop = false;
755         for (var treeElement = this.resourcesListTreeElement; !stop && treeElement; treeElement = treeElement.traverseNextTreeElement(false, this.resourcesListTreeElement, true)) {
756             if (treeElement instanceof WebInspector.FrameResourceTreeElement)
757                 stop = callback(treeElement);
758         }
759     },
760
761     _findTreeElementForResource: function(resource)
762     {
763         function isAncestor(ancestor, object)
764         {
765             // Redirects, XHRs do not belong to the tree, it is fine to silently return false here.
766             return false;
767         }
768
769         function getParent(object)
770         {
771             // Redirects, XHRs do not belong to the tree, it is fine to silently return false here.
772             return null;
773         }
774
775         return this.sidebarTree.findTreeElement(resource, isAncestor, getParent);
776     },
777
778     _findTreeElementForRevision: function(revision)
779     {
780         function isAncestor(ancestor, object)
781         {
782             return false;
783         }
784
785         function getParent(object)
786         {
787             return null;
788         }
789
790         return this.sidebarTree.findTreeElement(revision, isAncestor, getParent);
791     },
792
793     showView: function(view)
794     {
795         if (view)
796             this.showResource(view.resource);
797     },
798
799     _onmousemove: function(event)
800     {
801         var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
802         if (!nodeUnderMouse)
803             return;
804
805         var listNode = nodeUnderMouse.enclosingNodeOrSelfWithNodeName("li");
806         if (!listNode)
807             return;
808
809         var element = listNode.treeElement;
810         if (this._previousHoveredElement === element)
811             return;
812
813         if (this._previousHoveredElement) {
814             this._previousHoveredElement.hovered = false;
815             delete this._previousHoveredElement;
816         }
817
818         if (element instanceof WebInspector.FrameTreeElement) {
819             this._previousHoveredElement = element;
820             element.hovered = true;
821         }
822     },
823
824     _onmouseout: function(event)
825     {
826         if (this._previousHoveredElement) {
827             this._previousHoveredElement.hovered = false;
828             delete this._previousHoveredElement;
829         }
830     }
831 }
832
833 WebInspector.ResourcesPanel.prototype.__proto__ = WebInspector.Panel.prototype;
834
835 /**
836  * @constructor
837  * @extends {TreeElement}
838  * @param {boolean=} hasChildren
839  * @param {boolean=} noIcon
840  */
841 WebInspector.BaseStorageTreeElement = function(storagePanel, representedObject, title, iconClasses, hasChildren, noIcon)
842 {
843     TreeElement.call(this, "", representedObject, hasChildren);
844     this._storagePanel = storagePanel;
845     this._titleText = title;
846     this._iconClasses = iconClasses;
847     this._noIcon = noIcon;
848 }
849
850 WebInspector.BaseStorageTreeElement.prototype = {
851     onattach: function()
852     {
853         this.listItemElement.removeChildren();
854         if (this._iconClasses) {
855             for (var i = 0; i < this._iconClasses.length; ++i)
856                 this.listItemElement.addStyleClass(this._iconClasses[i]);
857         }
858
859         var selectionElement = document.createElement("div");
860         selectionElement.className = "selection";
861         this.listItemElement.appendChild(selectionElement);
862
863         if (!this._noIcon) {
864             this.imageElement = document.createElement("img");
865             this.imageElement.className = "icon";
866             this.listItemElement.appendChild(this.imageElement);
867         }
868
869         this.titleElement = document.createElement("div");
870         this.titleElement.className = "base-storage-tree-element-title";
871         this.titleElement.textContent = this._titleText;
872         this.listItemElement.appendChild(this.titleElement);
873     },
874
875     onselect: function()
876     {
877         var itemURL = this.itemURL;
878         if (itemURL)
879             WebInspector.settings.resourcesLastSelectedItem.set(itemURL);
880     },
881
882     onreveal: function()
883     {
884         if (this.listItemElement)
885             this.listItemElement.scrollIntoViewIfNeeded(false);
886     },
887
888     get titleText()
889     {
890         return this._titleText;
891     },
892
893     set titleText(titleText)
894     {
895         this._titleText = titleText;
896         if (this.titleElement)
897             this.titleElement.textContent = this._titleText;
898     },
899
900     isEventWithinDisclosureTriangle: function(event)
901     {
902         // Override it since we use margin-left in place of treeoutline's text-indent.
903         // Hence we need to take padding into consideration. This all is needed for leading
904         // icons in the tree.
905         const paddingLeft = 14;
906         var left = this.listItemElement.totalOffsetLeft() + paddingLeft;
907         return event.pageX >= left && event.pageX <= left + this.arrowToggleWidth && this.hasChildren;
908     }
909 }
910
911 WebInspector.BaseStorageTreeElement.prototype.__proto__ = TreeElement.prototype;
912
913 /**
914  * @constructor
915  * @extends {WebInspector.BaseStorageTreeElement}
916  * @param {boolean=} noIcon
917  */
918 WebInspector.StorageCategoryTreeElement = function(storagePanel, categoryName, settingsKey, iconClasses, noIcon)
919 {
920     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, categoryName, iconClasses, true, noIcon);
921     this._expandedSettingKey = "resources" + settingsKey + "Expanded";
922     WebInspector.settings[this._expandedSettingKey] = WebInspector.settings.createSetting(this._expandedSettingKey, settingsKey === "Frames");
923     this._categoryName = categoryName;
924 }
925
926 WebInspector.StorageCategoryTreeElement.prototype = {
927     get itemURL()
928     {
929         return "category://" + this._categoryName;
930     },
931
932     onselect: function()
933     {
934         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
935         this._storagePanel.showCategoryView(this._categoryName);
936     },
937
938     onattach: function()
939     {
940         WebInspector.BaseStorageTreeElement.prototype.onattach.call(this);
941         if (WebInspector.settings[this._expandedSettingKey].get())
942             this.expand();
943     },
944
945     onexpand: function()
946     {
947         WebInspector.settings[this._expandedSettingKey].set(true);
948     },
949
950     oncollapse: function()
951     {
952         WebInspector.settings[this._expandedSettingKey].set(false);
953     }
954 }
955
956 WebInspector.StorageCategoryTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
957
958 /**
959  * @constructor
960  * @extends {WebInspector.BaseStorageTreeElement}
961  */
962 WebInspector.FrameTreeElement = function(storagePanel, frame)
963 {
964     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, "", ["frame-storage-tree-item"]);
965     this._frame = frame;
966     this.frameNavigated(frame);
967 }
968
969 WebInspector.FrameTreeElement.prototype = {
970     frameNavigated: function(frame)
971     {
972         this.removeChildren();
973         this._frameId = frame.id;
974
975         var title = frame.name;
976         var subtitle = WebInspector.Resource.displayName(frame.url);
977         this.setTitles(title, subtitle);
978
979         this._categoryElements = {};
980         this._treeElementForResource = {};
981
982         this._storagePanel.addDocumentURL(frame.url);
983     },
984
985     get itemURL()
986     {
987         return "frame://" + encodeURI(this._displayName);
988     },
989
990     onattach: function()
991     {
992         WebInspector.BaseStorageTreeElement.prototype.onattach.call(this);
993         if (this._titleToSetOnAttach || this._subtitleToSetOnAttach) {
994             this.setTitles(this._titleToSetOnAttach, this._subtitleToSetOnAttach);
995             delete this._titleToSetOnAttach;
996             delete this._subtitleToSetOnAttach;
997         }
998     },
999
1000     onselect: function()
1001     {
1002         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1003         this._storagePanel.showCategoryView(this._displayName);
1004
1005         this.listItemElement.removeStyleClass("hovered");
1006         DOMAgent.hideHighlight();
1007     },
1008
1009     get displayName()
1010     {
1011         return this._displayName;
1012     },
1013
1014     setTitles: function(title, subtitle)
1015     {
1016         this._displayName = title || "";
1017         if (subtitle)
1018             this._displayName += " (" + subtitle + ")";
1019
1020         if (this.parent) {
1021             this.titleElement.textContent = title || "";
1022             if (subtitle) {
1023                 var subtitleElement = document.createElement("span");
1024                 subtitleElement.className = "base-storage-tree-element-subtitle";
1025                 subtitleElement.textContent = "(" + subtitle + ")";
1026                 this.titleElement.appendChild(subtitleElement);
1027             }
1028         } else {
1029             this._titleToSetOnAttach = title;
1030             this._subtitleToSetOnAttach = subtitle;
1031         }
1032     },
1033
1034     set hovered(hovered)
1035     {
1036         if (hovered) {
1037             this.listItemElement.addStyleClass("hovered");
1038             DOMAgent.highlightFrame(this._frameId, WebInspector.Color.PageHighlight.Content.toProtocolRGBA(), WebInspector.Color.PageHighlight.ContentOutline.toProtocolRGBA());
1039         } else {
1040             this.listItemElement.removeStyleClass("hovered");
1041             DOMAgent.hideHighlight();
1042         }
1043     },
1044
1045     appendResource: function(resource)
1046     {
1047         var categoryName = resource.category.name;
1048         var categoryElement = resource.category === WebInspector.resourceCategories.documents ? this : this._categoryElements[categoryName];
1049         if (!categoryElement) {
1050             categoryElement = new WebInspector.StorageCategoryTreeElement(this._storagePanel, resource.category.title, categoryName, null, true);
1051             this._categoryElements[resource.category.name] = categoryElement;
1052             this._insertInPresentationOrder(this, categoryElement);
1053         }
1054         var resourceTreeElement = new WebInspector.FrameResourceTreeElement(this._storagePanel, resource);
1055         this._insertInPresentationOrder(categoryElement, resourceTreeElement);
1056         resourceTreeElement._populateRevisions();
1057
1058         this._treeElementForResource[resource.url] = resourceTreeElement;
1059     },
1060
1061     resourceByURL: function(url)
1062     {
1063         var treeElement = this._treeElementForResource[url];
1064         return treeElement ? treeElement.representedObject : null;
1065     },
1066
1067     appendChild: function(treeElement)
1068     {
1069         this._insertInPresentationOrder(this, treeElement);
1070     },
1071
1072     _insertInPresentationOrder: function(parentTreeElement, childTreeElement)
1073     {
1074         // Insert in the alphabetical order, first frames, then resources. Document resource goes last.
1075         function typeWeight(treeElement)
1076         {
1077             if (treeElement instanceof WebInspector.StorageCategoryTreeElement)
1078                 return 2;
1079             if (treeElement instanceof WebInspector.FrameTreeElement)
1080                 return 1;
1081             return 3;
1082         }
1083
1084         function compare(treeElement1, treeElement2)
1085         {
1086             var typeWeight1 = typeWeight(treeElement1);
1087             var typeWeight2 = typeWeight(treeElement2);
1088
1089             var result;
1090             if (typeWeight1 > typeWeight2)
1091                 result = 1;
1092             else if (typeWeight1 < typeWeight2)
1093                 result = -1;
1094             else {
1095                 var title1 = treeElement1.displayName || treeElement1.titleText;
1096                 var title2 = treeElement2.displayName || treeElement2.titleText;
1097                 result = title1.localeCompare(title2);
1098             }
1099             return result;
1100         }
1101
1102         var children = parentTreeElement.children;
1103         var i;
1104         for (i = 0; i < children.length; ++i) {
1105             if (compare(childTreeElement, children[i]) < 0)
1106                 break;
1107         }
1108         parentTreeElement.insertChild(childTreeElement, i);
1109     }
1110 }
1111
1112 WebInspector.FrameTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1113
1114 /**
1115  * @constructor
1116  * @extends {WebInspector.BaseStorageTreeElement}
1117  */
1118 WebInspector.FrameResourceTreeElement = function(storagePanel, resource)
1119 {
1120     WebInspector.BaseStorageTreeElement.call(this, storagePanel, resource, resource.displayName, ["resource-sidebar-tree-item", "resources-category-" + resource.category.name]);
1121     this._resource = resource;
1122     this._resource.addEventListener(WebInspector.Resource.Events.MessageAdded, this._consoleMessageAdded, this);
1123     this._resource.addEventListener(WebInspector.Resource.Events.MessagesCleared, this._consoleMessagesCleared, this);
1124     this._resource.addEventListener(WebInspector.Resource.Events.RevisionAdded, this._revisionAdded, this);
1125     this.tooltip = resource.url;
1126 }
1127
1128 WebInspector.FrameResourceTreeElement.prototype = {
1129     get itemURL()
1130     {
1131         return this._resource.url;
1132     },
1133
1134     onselect: function()
1135     {
1136         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1137         this._storagePanel._showResourceView(this._resource);
1138     },
1139
1140     ondblclick: function(event)
1141     {
1142         PageAgent.open(this._resource.url, true);
1143     },
1144
1145     onattach: function()
1146     {
1147         WebInspector.BaseStorageTreeElement.prototype.onattach.call(this);
1148
1149         if (this._resource.category === WebInspector.resourceCategories.images) {
1150             var previewImage = document.createElement("img");
1151             previewImage.className = "image-resource-icon-preview";
1152             this._resource.populateImageSource(previewImage);
1153
1154             var iconElement = document.createElement("div");
1155             iconElement.className = "icon";
1156             iconElement.appendChild(previewImage);
1157             this.listItemElement.replaceChild(iconElement, this.imageElement);
1158         }
1159
1160         this._statusElement = document.createElement("div");
1161         this._statusElement.className = "status";
1162         this.listItemElement.insertBefore(this._statusElement, this.titleElement);
1163
1164         this.listItemElement.draggable = true;
1165         this.listItemElement.addEventListener("dragstart", this._ondragstart.bind(this), false);
1166         this.listItemElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true);
1167
1168         this._updateErrorsAndWarningsBubbles();
1169     },
1170
1171     _ondragstart: function(event)
1172     {
1173         event.dataTransfer.setData("text/plain", this._resource.content);
1174         event.dataTransfer.effectAllowed = "copy";
1175         return true;
1176     },
1177
1178     _handleContextMenuEvent: function(event)
1179     {
1180         var contextMenu = new WebInspector.ContextMenu();
1181         contextMenu.appendItem(WebInspector.openLinkExternallyLabel(), WebInspector.openResource.bind(WebInspector, this._resource.url, false));
1182         this._appendOpenInNetworkPanelAction(contextMenu, event);
1183         this._appendSaveAsAction(contextMenu, event);
1184         contextMenu.show(event);
1185     },
1186
1187     _appendOpenInNetworkPanelAction: function(contextMenu, event)
1188     {
1189         if (!this._resource.requestId)
1190             return;
1191
1192         contextMenu.appendItem(WebInspector.openInNetworkPanelLabel(), WebInspector.openRequestInNetworkPanel.bind(WebInspector, this._resource));
1193     },
1194
1195     _appendSaveAsAction: function(contextMenu, event)
1196     {
1197         if (!Preferences.saveAsAvailable)
1198             return;
1199
1200         if (this._resource.type !== WebInspector.Resource.Type.Document &&
1201             this._resource.type !== WebInspector.Resource.Type.Stylesheet &&
1202             this._resource.type !== WebInspector.Resource.Type.Script)
1203             return;
1204
1205         function save()
1206         {
1207             var fileName = this._resource.displayName;
1208             this._resource.requestContent(InspectorFrontendHost.saveAs.bind(InspectorFrontendHost, fileName));
1209         }
1210
1211         contextMenu.appendSeparator();
1212         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Save as..." : "Save As..."), save.bind(this));
1213     },
1214
1215     _setBubbleText: function(x)
1216     {
1217         if (!this._bubbleElement) {
1218             this._bubbleElement = document.createElement("div");
1219             this._bubbleElement.className = "bubble";
1220             this._statusElement.appendChild(this._bubbleElement);
1221         }
1222
1223         this._bubbleElement.textContent = x;
1224     },
1225
1226     _resetBubble: function()
1227     {
1228         if (this._bubbleElement) {
1229             this._bubbleElement.textContent = "";
1230             this._bubbleElement.removeStyleClass("search-matches");
1231             this._bubbleElement.removeStyleClass("warning");
1232             this._bubbleElement.removeStyleClass("error");
1233         }
1234     },
1235
1236     _resetSearchResults: function()
1237     {
1238         this._resetBubble();
1239         this._searchMatchesCount = 0;
1240     },
1241
1242     get searchMatchesCount()
1243     {
1244         return this._searchMatchesCount;
1245     },
1246
1247     searchMatchesFound: function(matchesCount)
1248     {
1249         this._resetSearchResults();
1250
1251         this._searchMatchesCount = matchesCount;
1252         this._setBubbleText(matchesCount);
1253         this._bubbleElement.addStyleClass("search-matches");
1254
1255         // Expand, do not scroll into view.
1256         var currentAncestor = this.parent;
1257         while (currentAncestor && !currentAncestor.root) {
1258             if (!currentAncestor.expanded)
1259                 currentAncestor.expand();
1260             currentAncestor = currentAncestor.parent;
1261         }
1262     },
1263
1264     _updateErrorsAndWarningsBubbles: function()
1265     {
1266         if (this._storagePanel.currentQuery)
1267             return;
1268
1269         this._resetBubble();
1270
1271         if (this._resource.warnings || this._resource.errors)
1272             this._setBubbleText(this._resource.warnings + this._resource.errors);
1273
1274         if (this._resource.warnings)
1275             this._bubbleElement.addStyleClass("warning");
1276
1277         if (this._resource.errors)
1278             this._bubbleElement.addStyleClass("error");
1279     },
1280
1281     _consoleMessagesCleared: function()
1282     {
1283         // FIXME: move to the SourceFrame.
1284         if (this._sourceView)
1285             this._sourceView.clearMessages();
1286
1287         this._updateErrorsAndWarningsBubbles();
1288     },
1289
1290     _consoleMessageAdded: function(event)
1291     {
1292         var msg = event.data;
1293         if (this._sourceView)
1294             this._sourceView.addMessage(msg);
1295         this._updateErrorsAndWarningsBubbles();
1296     },
1297
1298     _populateRevisions: function()
1299     {
1300         for (var i = 0; i < this._resource.history.length; ++i)
1301             this._appendRevision(this._resource.history[i]);
1302     },
1303
1304     _revisionAdded: function(event)
1305     {
1306         this._appendRevision(event.data);
1307     },
1308
1309     _appendRevision: function(revision)
1310     {
1311         this.insertChild(new WebInspector.ResourceRevisionTreeElement(this._storagePanel, revision), 0);
1312         var oldView = this._sourceView;
1313         if (oldView) {
1314             // This is needed when resource content was changed from scripts panel.
1315             var newView = this._recreateSourceView();
1316             if (oldView === this._storagePanel.visibleView)
1317                 this._storagePanel._showResourceView(this._resource);
1318         }
1319     },
1320
1321     sourceView: function()
1322     {
1323         if (!this._sourceView) {
1324             this._sourceView = this._createSourceView();
1325             if (this._resource.messages) {
1326                 for (var i = 0; i < this._resource.messages.length; i++)
1327                     this._sourceView.addMessage(this._resource.messages[i]);
1328             }
1329         }
1330         return this._sourceView;
1331     },
1332
1333     _createSourceView: function()
1334     {
1335         return new WebInspector.EditableResourceSourceFrame(this._resource);
1336     },
1337
1338     _recreateSourceView: function()
1339     {
1340         var oldView = this._sourceView;
1341         var newView = this._createSourceView();
1342
1343         var oldViewParentNode = oldView.visible ? oldView.element.parentNode : null;
1344         newView.inheritScrollPositions(oldView);
1345
1346         this._storagePanel.removeChildView(this._sourceView);
1347         this._storagePanel.addChildView(newView);
1348         this._sourceView = newView;
1349
1350         if (oldViewParentNode)
1351             newView.show(oldViewParentNode);
1352
1353         return newView;
1354     }
1355 }
1356
1357 WebInspector.FrameResourceTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1358
1359 /**
1360  * @constructor
1361  * @extends {WebInspector.BaseStorageTreeElement}
1362  */
1363 WebInspector.DatabaseTreeElement = function(storagePanel, database)
1364 {
1365     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, database.name, ["database-storage-tree-item"], true);
1366     this._database = database;
1367 }
1368
1369 WebInspector.DatabaseTreeElement.prototype = {
1370     get itemURL()
1371     {
1372         return "database://" + encodeURI(this._database.name);
1373     },
1374
1375     onselect: function()
1376     {
1377         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1378         this._storagePanel.showDatabase(this._database);
1379     },
1380
1381     oncollapse: function()
1382     {
1383         // Request a refresh after every collapse so the next
1384         // expand will have an updated table list.
1385         this.shouldRefreshChildren = true;
1386     },
1387
1388     onpopulate: function()
1389     {
1390         this.removeChildren();
1391
1392         function tableNamesCallback(tableNames)
1393         {
1394             var tableNamesLength = tableNames.length;
1395             for (var i = 0; i < tableNamesLength; ++i)
1396                 this.appendChild(new WebInspector.DatabaseTableTreeElement(this._storagePanel, this._database, tableNames[i]));
1397         }
1398         this._database.getTableNames(tableNamesCallback.bind(this));
1399     }
1400 }
1401
1402 WebInspector.DatabaseTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1403
1404 /**
1405  * @constructor
1406  * @extends {WebInspector.BaseStorageTreeElement}
1407  */
1408 WebInspector.DatabaseTableTreeElement = function(storagePanel, database, tableName)
1409 {
1410     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, tableName, ["database-storage-tree-item"]);
1411     this._database = database;
1412     this._tableName = tableName;
1413 }
1414
1415 WebInspector.DatabaseTableTreeElement.prototype = {
1416     get itemURL()
1417     {
1418         return "database://" + encodeURI(this._database.name) + "/" + encodeURI(this._tableName);
1419     },
1420
1421     onselect: function()
1422     {
1423         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1424         this._storagePanel.showDatabase(this._database, this._tableName);
1425     }
1426 }
1427 WebInspector.DatabaseTableTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1428
1429 /**
1430  * @constructor
1431  * @extends {WebInspector.BaseStorageTreeElement}
1432  */
1433 WebInspector.DOMStorageTreeElement = function(storagePanel, domStorage, className)
1434 {
1435     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, domStorage.domain ? domStorage.domain : WebInspector.UIString("Local Files"), ["domstorage-storage-tree-item", className]);
1436     this._domStorage = domStorage;
1437 }
1438
1439 WebInspector.DOMStorageTreeElement.prototype = {
1440     get itemURL()
1441     {
1442         return "storage://" + this._domStorage.domain + "/" + (this._domStorage.isLocalStorage ? "local" : "session");
1443     },
1444
1445     onselect: function()
1446     {
1447         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1448         this._storagePanel.showDOMStorage(this._domStorage);
1449     }
1450 }
1451 WebInspector.DOMStorageTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1452
1453 /**
1454  * @constructor
1455  * @extends {WebInspector.BaseStorageTreeElement}
1456  */
1457 WebInspector.CookieTreeElement = function(storagePanel, cookieDomain)
1458 {
1459     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, cookieDomain ? cookieDomain : WebInspector.UIString("Local Files"), ["cookie-storage-tree-item"]);
1460     this._cookieDomain = cookieDomain;
1461 }
1462
1463 WebInspector.CookieTreeElement.prototype = {
1464     get itemURL()
1465     {
1466         return "cookies://" + this._cookieDomain;
1467     },
1468
1469     onselect: function()
1470     {
1471         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1472         this._storagePanel.showCookies(this, this._cookieDomain);
1473     }
1474 }
1475 WebInspector.CookieTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1476
1477 /**
1478  * @constructor
1479  * @extends {WebInspector.BaseStorageTreeElement}
1480  */
1481 WebInspector.ApplicationCacheTreeElement = function(storagePanel, appcacheDomain)
1482 {
1483     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, appcacheDomain ? appcacheDomain : WebInspector.UIString("Local Files"), ["application-cache-storage-tree-item"]);
1484     this._appcacheDomain = appcacheDomain;
1485 }
1486
1487 WebInspector.ApplicationCacheTreeElement.prototype = {
1488     get itemURL()
1489     {
1490         return "appcache://" + this._appcacheDomain;
1491     },
1492
1493     onselect: function()
1494     {
1495         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1496         this._storagePanel.showApplicationCache(this, this._appcacheDomain);
1497     }
1498 }
1499 WebInspector.ApplicationCacheTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1500
1501 /**
1502  * @constructor
1503  * @extends {WebInspector.BaseStorageTreeElement}
1504  */
1505 WebInspector.ResourceRevisionTreeElement = function(storagePanel, revision)
1506 {
1507     var title = revision.timestamp ? revision.timestamp.toLocaleTimeString() : WebInspector.UIString("(original)");
1508     WebInspector.BaseStorageTreeElement.call(this, storagePanel, revision, title, ["resource-sidebar-tree-item", "resources-category-" + revision.resource.category.name]);
1509     if (revision.timestamp)
1510         this.tooltip = revision.timestamp.toLocaleString();
1511     this._revision = revision;
1512 }
1513
1514 WebInspector.ResourceRevisionTreeElement.prototype = {
1515     get itemURL()
1516     {
1517         return this._revision.resource.url;
1518     },
1519
1520     onattach: function()
1521     {
1522         WebInspector.BaseStorageTreeElement.prototype.onattach.call(this);
1523         this.listItemElement.draggable = true;
1524         this.listItemElement.addEventListener("dragstart", this._ondragstart.bind(this), false);
1525         this.listItemElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true);
1526     },
1527
1528     onselect: function()
1529     {
1530         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1531         this._storagePanel._showRevisionView(this._revision);
1532     },
1533
1534     _ondragstart: function(event)
1535     {
1536         if (this._revision.content) {
1537             event.dataTransfer.setData("text/plain", this._revision.content);
1538             event.dataTransfer.effectAllowed = "copy";
1539             return true;
1540         }
1541     },
1542
1543     _handleContextMenuEvent: function(event)
1544     {
1545         var contextMenu = new WebInspector.ContextMenu();
1546         contextMenu.appendItem(WebInspector.UIString("Revert to this revision"), this._revision.revertToThis.bind(this._revision));
1547
1548         if (Preferences.saveAsAvailable) {
1549             function save()
1550             {
1551                 var fileName = this._revision.resource.displayName;
1552                 this._revision.requestContent(InspectorFrontendHost.saveAs.bind(InspectorFrontendHost, fileName));
1553             }
1554             contextMenu.appendSeparator();
1555             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Save as..." : "Save As..."), save.bind(this));
1556         }
1557
1558         contextMenu.show(event);
1559     },
1560
1561     sourceView: function()
1562     {
1563         if (!this._sourceView)
1564             this._sourceView = new WebInspector.ResourceRevisionSourceFrame(this._revision);
1565         return this._sourceView;
1566     }
1567 }
1568
1569 WebInspector.ResourceRevisionTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1570
1571 /**
1572  * @constructor
1573  * @extends {WebInspector.View}
1574  */
1575 WebInspector.StorageCategoryView = function()
1576 {
1577     WebInspector.View.call(this);
1578
1579     this.element.addStyleClass("storage-view");
1580     this._emptyView = new WebInspector.EmptyView("");
1581     this._emptyView.show(this.element);
1582 }
1583
1584 WebInspector.StorageCategoryView.prototype = {
1585     setText: function(text)
1586     {
1587         this._emptyView.text = text;
1588     }
1589 }
1590
1591 WebInspector.StorageCategoryView.prototype.__proto__ = WebInspector.View.prototype;
1592
1593 /**
1594  * @constructor
1595  */
1596 WebInspector.ResourcesSearchController = function(rootElement)
1597 {
1598     this._root = rootElement;
1599     this._traverser = new WebInspector.SearchResultsTreeElementsTraverser(rootElement);
1600     this._lastTreeElement = null;
1601     this._lastIndex = -1;
1602 }
1603
1604 WebInspector.ResourcesSearchController.prototype = {
1605     nextSearchResult: function(currentTreeElement)
1606     {
1607         if (!currentTreeElement)
1608             return this._searchResult(this._traverser.first(), 0);
1609
1610         if (!currentTreeElement.searchMatchesCount)
1611             return this._searchResult(this._traverser.next(currentTreeElement), 0);
1612
1613         if (this._lastTreeElement !== currentTreeElement || this._lastIndex === -1)
1614             return this._searchResult(currentTreeElement, 0);
1615
1616         if (this._lastIndex == currentTreeElement.searchMatchesCount - 1)
1617             return this._searchResult(this._traverser.next(currentTreeElement), 0);
1618
1619         return this._searchResult(currentTreeElement, this._lastIndex + 1);
1620     },
1621
1622     previousSearchResult: function(currentTreeElement)
1623     {
1624         if (!currentTreeElement) {
1625             var treeElement = this._traverser.last();
1626             return this._searchResult(treeElement, treeElement.searchMatchesCount - 1);
1627         }
1628
1629         if (currentTreeElement.searchMatchesCount && this._lastTreeElement === currentTreeElement && this._lastIndex > 0)
1630             return this._searchResult(currentTreeElement, this._lastIndex - 1);
1631
1632         var treeElement = this._traverser.previous(currentTreeElement)
1633         return this._searchResult(treeElement, treeElement.searchMatchesCount - 1);
1634     },
1635
1636     _searchResult: function(treeElement, index)
1637     {
1638         this._lastTreeElement = treeElement;
1639         this._lastIndex = index;
1640         return {treeElement: treeElement, index: index};
1641     }
1642 }
1643
1644 /**
1645  * @constructor
1646  */
1647 WebInspector.SearchResultsTreeElementsTraverser = function(rootElement)
1648 {
1649     this._root = rootElement;
1650 }
1651
1652 WebInspector.SearchResultsTreeElementsTraverser.prototype = {
1653     first: function()
1654     {
1655         return this.next(this._root);
1656     },
1657
1658     last: function()
1659     {
1660         return this.previous(this._root);
1661     },
1662
1663     next: function(startTreeElement)
1664     {
1665         var treeElement = startTreeElement;
1666         do {
1667             treeElement = this._traverseNext(treeElement) || this._root;
1668         } while (treeElement != startTreeElement && !this._elementHasSearchResults(treeElement));
1669         return treeElement;
1670     },
1671
1672     previous: function(startTreeElement)
1673     {
1674         var treeElement = startTreeElement;
1675         do {
1676             treeElement = this._traversePrevious(treeElement) || this._lastTreeElement();
1677         } while (treeElement != startTreeElement && !this._elementHasSearchResults(treeElement));
1678         return treeElement;
1679     },
1680
1681     _traverseNext: function(treeElement)
1682     {
1683         return treeElement.traverseNextTreeElement(false, this._root, true);
1684     },
1685
1686     _elementHasSearchResults: function(treeElement)
1687     {
1688         return treeElement instanceof WebInspector.FrameResourceTreeElement && treeElement.searchMatchesCount;
1689     },
1690
1691     _traversePrevious: function(treeElement)
1692     {
1693         return treeElement.traversePreviousTreeElement(false, this._root, true);
1694     },
1695
1696     _lastTreeElement: function()
1697     {
1698         var treeElement = this._root;
1699         var nextTreeElement;
1700         while (nextTreeElement = this._traverseNext(treeElement))
1701             treeElement = nextTreeElement;
1702         return treeElement;
1703     }
1704 }