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