Web Inspector: move sources panel out of experimental.
[WebKit-https.git] / Source / WebCore / inspector / front-end / ResourcesPanel.js
1 /*
2  * Copyright (C) 2007, 2008, 2010 Apple Inc.  All rights reserved.
3  * Copyright (C) 2009 Joseph Pecoraro
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
8  *
9  * 1.  Redistributions of source code must retain the above copyright
10  *     notice, this list of conditions and the following disclaimer.
11  * 2.  Redistributions in binary form must reproduce the above copyright
12  *     notice, this list of conditions and the following disclaimer in the
13  *     documentation and/or other materials provided with the distribution.
14  * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15  *     its contributors may be used to endorse or promote products derived
16  *     from this software without specific prior written permission.
17  *
18  * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20  * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21  * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22  * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23  * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25  * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28  */
29
30 /**
31  * @constructor
32  * @extends {WebInspector.Panel}
33  */
34 WebInspector.ResourcesPanel = function(database)
35 {
36     WebInspector.Panel.call(this, "resources");
37     this.registerRequiredCSS("resourcesPanel.css");
38
39     WebInspector.settings.resourcesLastSelectedItem = WebInspector.settings.createSetting("resourcesLastSelectedItem", {});
40
41     this.createSplitViewWithSidebarTree();
42     this.sidebarElement.addStyleClass("outline-disclosure");
43     this.sidebarElement.addStyleClass("filter-all");
44     this.sidebarElement.addStyleClass("children");
45     this.sidebarElement.addStyleClass("small");
46
47     this.sidebarTreeElement.removeStyleClass("sidebar-tree");
48
49     this.resourcesListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Frames"), "Frames", ["frame-storage-tree-item"]);
50     this.sidebarTree.appendChild(this.resourcesListTreeElement);
51
52     this.databasesListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Databases"), "Databases", ["database-storage-tree-item"]);
53     this.sidebarTree.appendChild(this.databasesListTreeElement);
54
55     this.indexedDBListTreeElement = new WebInspector.IndexedDBTreeElement(this);
56     this.sidebarTree.appendChild(this.indexedDBListTreeElement);
57
58     this.localStorageListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Local Storage"), "LocalStorage", ["domstorage-storage-tree-item", "local-storage"]);
59     this.sidebarTree.appendChild(this.localStorageListTreeElement);
60
61     this.sessionStorageListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Session Storage"), "SessionStorage", ["domstorage-storage-tree-item", "session-storage"]);
62     this.sidebarTree.appendChild(this.sessionStorageListTreeElement);
63
64     this.cookieListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Cookies"), "Cookies", ["cookie-storage-tree-item"]);
65     this.sidebarTree.appendChild(this.cookieListTreeElement);
66
67     this.applicationCacheListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Application Cache"), "ApplicationCache", ["application-cache-storage-tree-item"]);
68     this.sidebarTree.appendChild(this.applicationCacheListTreeElement);
69
70     this.storageViews = this.splitView.mainElement;
71     this.storageViews.addStyleClass("diff-container");
72
73     this.storageViewStatusBarItemsContainer = document.createElement("div");
74     this.storageViewStatusBarItemsContainer.className = "status-bar-items";
75
76     this._databases = [];
77     this._domStorage = [];
78     this._cookieViews = {};
79     this._origins = {};
80     this._domains = {};
81
82     this.sidebarElement.addEventListener("mousemove", this._onmousemove.bind(this), false);
83     this.sidebarElement.addEventListener("mouseout", this._onmouseout.bind(this), false);
84
85     function viewGetter()
86     {
87         return this.visibleView;
88     }
89     WebInspector.GoToLineDialog.install(this, viewGetter.bind(this));
90
91     WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.OnLoad, this._onLoadEventFired, this);
92     WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.CachedResourcesLoaded, this._cachedResourcesLoaded, this);
93     WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.WillLoadCachedResources, this._resetWithFrames, this);
94 }
95
96 WebInspector.ResourcesPanel.prototype = {
97     get toolbarItemLabel()
98     {
99         return WebInspector.UIString("Resources");
100     },
101
102     get statusBarItems()
103     {
104         return [this.storageViewStatusBarItemsContainer];
105     },
106
107     wasShown: function()
108     {
109         WebInspector.Panel.prototype.wasShown.call(this);
110         this._initialize();
111     },
112
113     _initialize: function()
114     {
115         if (!this._initialized && this.isShowing() && this._cachedResourcesWereLoaded) {
116             this._populateResourceTree();
117             this._populateApplicationCacheTree();
118             this._initDefaultSelection();
119             this._initialized = true;
120         }
121     },
122
123     _onLoadEventFired: function()
124     {
125         this._initDefaultSelection();
126     },
127
128     _initDefaultSelection: function()
129     {
130         if (!this._initialized)
131             return;
132
133         var itemURL = WebInspector.settings.resourcesLastSelectedItem.get();
134         if (itemURL) {
135             for (var treeElement = this.sidebarTree.children[0]; treeElement; treeElement = treeElement.traverseNextTreeElement(false, this.sidebarTree, true)) {
136                 if (treeElement.itemURL === itemURL) {
137                     treeElement.revealAndSelect(true);
138                     return;
139                 }
140             }
141         }
142
143         var mainResource = WebInspector.inspectedPageURL && this.resourcesListTreeElement && this.resourcesListTreeElement.expanded && WebInspector.resourceTreeModel.resourceForURL(WebInspector.inspectedPageURL);
144         if (mainResource)
145             this.showResource(mainResource);
146     },
147
148     _resetWithFrames: function()
149     {
150         this.resourcesListTreeElement.removeChildren();
151         this._treeElementForFrameId = {};
152         this._reset();
153     },
154
155     _reset: function()
156     {
157         this._origins = {};
158         this._domains = {};
159         for (var i = 0; i < this._databases.length; ++i) {
160             var database = this._databases[i];
161             delete database._tableViews;
162             if (database._queryView)
163                 database._queryView.removeEventListener(WebInspector.DatabaseQueryView.Events.SchemaUpdated, this._updateDatabaseTables, this);
164             delete database._queryView;
165         }
166         this._databases = [];
167
168         var domStorageLength = this._domStorage.length;
169         for (var i = 0; i < this._domStorage.length; ++i) {
170             var domStorage = this._domStorage[i];
171             delete domStorage._domStorageView;
172         }
173         this._domStorage = [];
174
175         this._cookieViews = {};
176
177         this.databasesListTreeElement.removeChildren();
178         this.localStorageListTreeElement.removeChildren();
179         this.sessionStorageListTreeElement.removeChildren();
180         this.cookieListTreeElement.removeChildren();
181
182         if (this.visibleView)
183             this.visibleView.detach();
184
185         this.storageViewStatusBarItemsContainer.removeChildren();
186
187         if (this.sidebarTree.selectedTreeElement)
188             this.sidebarTree.selectedTreeElement.deselect();
189     },
190
191     _populateResourceTree: function()
192     {
193         this._treeElementForFrameId = {};
194         WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameAdded, this._frameAdded, this);
195         WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameNavigated, this._frameNavigated, this);
196         WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameDetached, this._frameDetached, this);
197         WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.ResourceAdded, this._resourceAdded, this);
198
199         function populateFrame(frame)
200         {
201             this._frameAdded({data:frame});
202             for (var i = 0; i < frame.childFrames.length; ++i)
203                 populateFrame.call(this, frame.childFrames[i]);
204
205             var resources = frame.resources();
206             for (var i = 0; i < resources.length; ++i)
207                 this._resourceAdded({data:resources[i]});
208         }
209         populateFrame.call(this, WebInspector.resourceTreeModel.mainFrame);
210     },
211
212     _frameAdded: function(event)
213     {
214         var frame = event.data;
215         var parentFrame = frame.parentFrame;
216
217         var parentTreeElement = parentFrame ? this._treeElementForFrameId[parentFrame.id] : this.resourcesListTreeElement;
218         if (!parentTreeElement) {
219             console.warn("No frame to route " + frame.url + " to.")
220             return;
221         }
222
223         var frameTreeElement = new WebInspector.FrameTreeElement(this, frame);
224         this._treeElementForFrameId[frame.id] = frameTreeElement;
225         parentTreeElement.appendChild(frameTreeElement);
226     },
227
228     _frameDetached: function(event)
229     {
230         var frame = event.data;
231         var frameTreeElement = this._treeElementForFrameId[frame.id];
232         if (!frameTreeElement)
233             return;
234
235         delete this._treeElementForFrameId[frame.id];
236         if (frameTreeElement.parent)
237             frameTreeElement.parent.removeChild(frameTreeElement);
238     },
239
240     _resourceAdded: function(event)
241     {
242         var resource = event.data;
243         var frameId = resource.frameId;
244
245         if (resource.statusCode >= 301 && resource.statusCode <= 303)
246             return;
247
248         var frameTreeElement = this._treeElementForFrameId[frameId];
249         if (!frameTreeElement) {
250             // This is a frame's main resource, it will be retained
251             // and re-added by the resource manager;
252             return;
253         }
254
255         frameTreeElement.appendResource(resource);
256     },
257
258     _frameNavigated: function(event)
259     {
260         var frame = event.data;
261
262         if (!frame.parentFrame)
263             this._reset();
264
265         var frameId = frame.id;
266         var frameTreeElement = this._treeElementForFrameId[frameId];
267         if (frameTreeElement)
268             frameTreeElement.frameNavigated(frame);
269
270         var applicationCacheFrameTreeElement = this._applicationCacheFrameElements[frameId];
271         if (applicationCacheFrameTreeElement)
272             applicationCacheFrameTreeElement.frameNavigated(frame);
273     },
274
275     _cachedResourcesLoaded: function()
276     {
277         this._cachedResourcesWereLoaded = true;
278         this._initialize();
279     },
280
281     addDatabase: function(database)
282     {
283         this._databases.push(database);
284
285         var databaseTreeElement = new WebInspector.DatabaseTreeElement(this, database);
286         database._databasesTreeElement = databaseTreeElement;
287         this.databasesListTreeElement.appendChild(databaseTreeElement);
288     },
289
290     addDocumentURL: function(url)
291     {
292         var parsedURL = url.asParsedURL();
293         if (!parsedURL)
294             return;
295
296         var domain = parsedURL.host;
297         if (!this._domains[domain]) {
298             this._domains[domain] = true;
299
300             var cookieDomainTreeElement = new WebInspector.CookieTreeElement(this, domain);
301             this.cookieListTreeElement.appendChild(cookieDomainTreeElement);
302         }
303     },
304
305     addDOMStorage: function(domStorage)
306     {
307         this._domStorage.push(domStorage);
308         var domStorageTreeElement = new WebInspector.DOMStorageTreeElement(this, domStorage, (domStorage.isLocalStorage ? "local-storage" : "session-storage"));
309         domStorage._domStorageTreeElement = domStorageTreeElement;
310         if (domStorage.isLocalStorage)
311             this.localStorageListTreeElement.appendChild(domStorageTreeElement);
312         else
313             this.sessionStorageListTreeElement.appendChild(domStorageTreeElement);
314     },
315
316     selectDatabase: function(databaseId)
317     {
318         var database;
319         for (var i = 0, len = this._databases.length; i < len; ++i) {
320             database = this._databases[i];
321             if (database.id === databaseId) {
322                 this.showDatabase(database);
323                 database._databasesTreeElement.select();
324                 return;
325             }
326         }
327     },
328
329     selectDOMStorage: function(storageId)
330     {
331         var domStorage = this._domStorageForId(storageId);
332         if (domStorage) {
333             this.showDOMStorage(domStorage);
334             domStorage._domStorageTreeElement.select();
335         }
336     },
337
338     canShowAnchorLocation: function(anchor)
339     {
340         return !!WebInspector.resourceForURL(anchor.href);
341     },
342
343     showAnchorLocation: function(anchor)
344     {
345         var resource = WebInspector.resourceForURL(anchor.href);
346         this.showResource(resource, anchor.lineNumber);
347     },
348
349     /**
350      * @param {number=} line
351      */
352     showResource: function(resource, line)
353     {
354         var resourceTreeElement = this._findTreeElementForResource(resource);
355         if (resourceTreeElement)
356             resourceTreeElement.revealAndSelect();
357
358         if (typeof line === "number") {
359             var view = this._resourceViewForResource(resource);
360             if (view.canHighlightLine())
361                 view.highlightLine(line);
362         }
363         return true;
364     },
365
366     _showResourceView: function(resource)
367     {
368         var view = this._resourceViewForResource(resource);
369         if (!view) {
370             this.visibleView.detach();
371             return;
372         }
373         if (view.searchCanceled)
374             view.searchCanceled();
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     /**
390      * @param {string=} tableName
391      */
392     showDatabase: function(database, tableName)
393     {
394         if (!database)
395             return;
396
397         var view;
398         if (tableName) {
399             if (!("_tableViews" in database))
400                 database._tableViews = {};
401             view = database._tableViews[tableName];
402             if (!view) {
403                 view = new WebInspector.DatabaseTableView(database, tableName);
404                 database._tableViews[tableName] = view;
405             }
406         } else {
407             view = database._queryView;
408             if (!view) {
409                 view = new WebInspector.DatabaseQueryView(database);
410                 database._queryView = view;
411                 view.addEventListener(WebInspector.DatabaseQueryView.Events.SchemaUpdated, this._updateDatabaseTables, this);
412             }
413         }
414
415         this._innerShowView(view);
416     },
417
418     /**
419      * @param {WebInspector.View} view
420      */
421     showIndexedDB: function(view)
422     {
423         this._innerShowView(view);
424     },
425
426     showDOMStorage: function(domStorage)
427     {
428         if (!domStorage)
429             return;
430
431         var view;
432         view = domStorage._domStorageView;
433         if (!view) {
434             view = new WebInspector.DOMStorageItemsView(domStorage);
435             domStorage._domStorageView = view;
436         }
437
438         this._innerShowView(view);
439     },
440
441     showCookies: function(treeElement, cookieDomain)
442     {
443         var view = this._cookieViews[cookieDomain];
444         if (!view) {
445             view = new WebInspector.CookieItemsView(treeElement, cookieDomain);
446             this._cookieViews[cookieDomain] = view;
447         }
448
449         this._innerShowView(view);
450     },
451
452     showApplicationCache: function(frameId)
453     {
454         if (!this._applicationCacheViews[frameId])
455             this._applicationCacheViews[frameId] = new WebInspector.ApplicationCacheItemsView(this._applicationCacheModel, frameId);
456
457         this._innerShowView(this._applicationCacheViews[frameId]);
458     },
459
460     showCategoryView: function(categoryName)
461     {
462         if (!this._categoryView)
463             this._categoryView = new WebInspector.StorageCategoryView();
464         this._categoryView.setText(categoryName);
465         this._innerShowView(this._categoryView);
466     },
467
468     _innerShowView: function(view)
469     {
470         if (this.visibleView === view)
471             return;
472
473         if (this.visibleView)
474             this.visibleView.detach();
475
476         view.show(this.storageViews);
477         this.visibleView = view;
478
479         this.storageViewStatusBarItemsContainer.removeChildren();
480         var statusBarItems = view.statusBarItems || [];
481         for (var i = 0; i < statusBarItems.length; ++i)
482             this.storageViewStatusBarItemsContainer.appendChild(statusBarItems[i]);
483     },
484
485     closeVisibleView: function()
486     {
487         if (!this.visibleView)
488             return;
489         this.visibleView.detach();
490         delete this.visibleView;
491     },
492
493     _updateDatabaseTables: function(event)
494     {
495         var database = event.data;
496
497         if (!database || !database._databasesTreeElement)
498             return;
499
500         database._databasesTreeElement.shouldRefreshChildren = true;
501
502         if (!("_tableViews" in database))
503             return;
504
505         var tableNamesHash = {};
506         var self = this;
507         function tableNamesCallback(tableNames)
508         {
509             var tableNamesLength = tableNames.length;
510             for (var i = 0; i < tableNamesLength; ++i)
511                 tableNamesHash[tableNames[i]] = true;
512
513             for (var tableName in database._tableViews) {
514                 if (!(tableName in tableNamesHash)) {
515                     if (self.visibleView === database._tableViews[tableName])
516                         self.closeVisibleView();
517                     delete database._tableViews[tableName];
518                 }
519             }
520         }
521         database.getTableNames(tableNamesCallback);
522     },
523
524     updateDOMStorage: function(storageId)
525     {
526         var domStorage = this._domStorageForId(storageId);
527         if (!domStorage)
528             return;
529
530         var view = domStorage._domStorageView;
531         if (this.visibleView && view === this.visibleView)
532             domStorage._domStorageView.update();
533     },
534
535     _populateApplicationCacheTree: function()
536     {
537         this._applicationCacheModel = new WebInspector.ApplicationCacheModel();
538
539         this._applicationCacheViews = {};
540         this._applicationCacheFrameElements = {};
541         this._applicationCacheManifestElements = {};
542
543         this._applicationCacheModel.addEventListener(WebInspector.ApplicationCacheModel.EventTypes.FrameManifestAdded, this._applicationCacheFrameManifestAdded, this);
544         this._applicationCacheModel.addEventListener(WebInspector.ApplicationCacheModel.EventTypes.FrameManifestRemoved, this._applicationCacheFrameManifestRemoved, this);
545
546         this._applicationCacheModel.addEventListener(WebInspector.ApplicationCacheModel.EventTypes.FrameManifestStatusUpdated, this._applicationCacheFrameManifestStatusChanged, this);
547         this._applicationCacheModel.addEventListener(WebInspector.ApplicationCacheModel.EventTypes.NetworkStateChanged, this._applicationCacheNetworkStateChanged, this);
548     },
549
550     _applicationCacheFrameManifestAdded: function(event)
551     {
552         var frameId = event.data;
553         var manifestURL = this._applicationCacheModel.frameManifestURL(frameId);
554         var status = this._applicationCacheModel.frameManifestStatus(frameId)
555
556         var manifestTreeElement = this._applicationCacheManifestElements[manifestURL]
557         if (!manifestTreeElement) {
558             manifestTreeElement = new WebInspector.ApplicationCacheManifestTreeElement(this, manifestURL);
559             this.applicationCacheListTreeElement.appendChild(manifestTreeElement);
560             this._applicationCacheManifestElements[manifestURL] = manifestTreeElement;
561         }
562
563         var frameTreeElement = new WebInspector.ApplicationCacheFrameTreeElement(this, frameId, manifestURL);
564         manifestTreeElement.appendChild(frameTreeElement);
565         manifestTreeElement.expand();
566         this._applicationCacheFrameElements[frameId] = frameTreeElement;
567     },
568
569     _applicationCacheFrameManifestRemoved: function(event)
570     {
571         var frameId = event.data;
572         var frameTreeElement = this._applicationCacheFrameElements[frameId];
573         if (!frameTreeElement)
574             return;
575
576         var manifestURL = frameTreeElement.manifestURL;
577         delete this._applicationCacheFrameElements[frameId];
578         delete this._applicationCacheViews[frameId];
579         frameTreeElement.parent.removeChild(frameTreeElement);
580
581         var manifestTreeElement = this._applicationCacheManifestElements[manifestURL];
582         if (manifestTreeElement.children.length !== 0)
583             return;
584
585         delete this._applicationCacheManifestElements[manifestURL];
586         manifestTreeElement.parent.removeChild(manifestTreeElement);
587     },
588
589     _applicationCacheFrameManifestStatusChanged: function(event)
590     {
591         var frameId = event.data;
592         var status = this._applicationCacheModel.frameManifestStatus(frameId)
593
594         if (this._applicationCacheViews[frameId])
595             this._applicationCacheViews[frameId].updateStatus(status);
596     },
597
598     _applicationCacheNetworkStateChanged: function(event)
599     {
600         var isNowOnline = event.data;
601
602         for (var manifestURL in this._applicationCacheViews)
603             this._applicationCacheViews[manifestURL].updateNetworkState(isNowOnline);
604     },
605
606     _domStorageForId: function(storageId)
607     {
608         if (!this._domStorage)
609             return null;
610         var domStorageLength = this._domStorage.length;
611         for (var i = 0; i < domStorageLength; ++i) {
612             var domStorage = this._domStorage[i];
613             if (domStorage.id == storageId)
614                 return domStorage;
615         }
616         return null;
617     },
618
619     sidebarResized: function(event)
620     {
621         var width = event.data;
622         this.storageViewStatusBarItemsContainer.style.left = width + "px";
623     },
624
625     performSearch: function(query)
626     {
627         this._resetSearchResults();
628         var regex = WebInspector.SourceFrame.createSearchRegex(query);
629         var totalMatchesCount = 0;
630
631         function searchInEditedResource(treeElement)
632         {
633             var resource = treeElement.representedObject;
634             if (resource.history.length == 0)
635                 return;
636             var matchesCount = countRegexMatches(regex, resource.content)
637             treeElement.searchMatchesFound(matchesCount);
638             totalMatchesCount += matchesCount;
639         }
640
641         function callback(error, result)
642         {
643             if (!error) {
644                 for (var i = 0; i < result.length; i++) {
645                     var searchResult = result[i];
646                     var frameTreeElement = this._treeElementForFrameId[searchResult.frameId];
647                     if (!frameTreeElement)
648                         continue;
649                     var resource = frameTreeElement.resourceByURL(searchResult.url);
650
651                     // FIXME: When the same script is used in several frames and this script contains at least
652                     // one search result then some search results can not be matched with a resource on panel.
653                     // https://bugs.webkit.org/show_bug.cgi?id=66005
654                     if (!resource)
655                         continue;
656
657                     if (resource.history.length > 0)
658                         continue; // Skip edited resources.
659                     this._findTreeElementForResource(resource).searchMatchesFound(searchResult.matchesCount);
660                     totalMatchesCount += searchResult.matchesCount;
661                 }
662             }
663
664             WebInspector.searchController.updateSearchMatchesCount(totalMatchesCount, this);
665             this._searchController = new WebInspector.ResourcesSearchController(this.resourcesListTreeElement, totalMatchesCount);
666
667             if (this.sidebarTree.selectedTreeElement && this.sidebarTree.selectedTreeElement.searchMatchesCount)
668                 this.jumpToNextSearchResult();
669         }
670
671         this._forAllResourceTreeElements(searchInEditedResource.bind(this));
672         PageAgent.searchInResources(regex.source, !regex.ignoreCase, true, callback.bind(this));
673     },
674
675     _ensureViewSearchPerformed: function(callback)
676     {
677         function viewSearchPerformedCallback(searchId)
678         {
679             if (searchId !== this._lastViewSearchId)
680                 return; // Search is obsolete.
681             this._viewSearchInProgress = false;
682             callback();
683         }
684
685         if (!this._viewSearchInProgress) {
686             if (!this.visibleView.hasSearchResults()) {
687                 // We give id to each search, so that we can skip callbacks for obsolete searches.
688                 this._lastViewSearchId = this._lastViewSearchId ? this._lastViewSearchId + 1 : 0;
689                 this._viewSearchInProgress = true;
690                 this.visibleView.performSearch(this.currentQuery, viewSearchPerformedCallback.bind(this, this._lastViewSearchId));
691             } else
692                 callback();
693         }
694     },
695
696     _showSearchResult: function(searchResult)
697     {
698         this._lastSearchResultIndex = searchResult.index;
699         this._lastSearchResultTreeElement = searchResult.treeElement;
700
701         // At first show view for treeElement.
702         if (searchResult.treeElement !== this.sidebarTree.selectedTreeElement) {
703             this.showResource(searchResult.treeElement.representedObject);
704             WebInspector.searchController.focusSearchField();
705         }
706
707         function callback(searchId)
708         {
709             if (this.sidebarTree.selectedTreeElement !== this._lastSearchResultTreeElement)
710                 return; // User has selected another view while we were searching.
711             if (this._lastSearchResultIndex != -1)
712                 this.visibleView.jumpToSearchResult(this._lastSearchResultIndex);
713             WebInspector.searchController.updateCurrentMatchIndex(searchResult.currentMatchIndex - 1, this);
714         }
715
716         // Then run SourceFrame search if needed and jump to search result index when done.
717         this._ensureViewSearchPerformed(callback.bind(this));
718     },
719
720     _resetSearchResults: function()
721     {
722         function callback(resourceTreeElement)
723         {
724             resourceTreeElement._resetSearchResults();
725         }
726
727         this._forAllResourceTreeElements(callback);
728         if (this.visibleView && this.visibleView.searchCanceled)
729             this.visibleView.searchCanceled();
730
731         this._lastSearchResultTreeElement = null;
732         this._lastSearchResultIndex = -1;
733         this._viewSearchInProgress = false;
734     },
735
736     searchCanceled: function()
737     {
738         function callback(resourceTreeElement)
739         {
740             resourceTreeElement._updateErrorsAndWarningsBubbles();
741         }
742
743         WebInspector.searchController.updateSearchMatchesCount(0, this);
744         this._resetSearchResults();
745         this._forAllResourceTreeElements(callback);
746     },
747
748     jumpToNextSearchResult: function()
749     {
750         if (!this.currentSearchMatches)
751             return;
752         var currentTreeElement = this.sidebarTree.selectedTreeElement;
753         var nextSearchResult = this._searchController.nextSearchResult(currentTreeElement);
754         this._showSearchResult(nextSearchResult);
755     },
756
757     jumpToPreviousSearchResult: function()
758     {
759         if (!this.currentSearchMatches)
760             return;
761         var currentTreeElement = this.sidebarTree.selectedTreeElement;
762         var previousSearchResult = this._searchController.previousSearchResult(currentTreeElement);
763         this._showSearchResult(previousSearchResult);
764     },
765
766     _forAllResourceTreeElements: function(callback)
767     {
768         var stop = false;
769         for (var treeElement = this.resourcesListTreeElement; !stop && treeElement; treeElement = treeElement.traverseNextTreeElement(false, this.resourcesListTreeElement, true)) {
770             if (treeElement instanceof WebInspector.FrameResourceTreeElement)
771                 stop = callback(treeElement);
772         }
773     },
774
775     _findTreeElementForResource: function(resource)
776     {
777         function isAncestor(ancestor, object)
778         {
779             // Redirects, XHRs do not belong to the tree, it is fine to silently return false here.
780             return false;
781         }
782
783         function getParent(object)
784         {
785             // Redirects, XHRs do not belong to the tree, it is fine to silently return false here.
786             return null;
787         }
788
789         return this.sidebarTree.findTreeElement(resource, isAncestor, getParent);
790     },
791
792     showView: function(view)
793     {
794         if (view)
795             this.showResource(view.resource);
796     },
797
798     _onmousemove: function(event)
799     {
800         var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
801         if (!nodeUnderMouse)
802             return;
803
804         var listNode = nodeUnderMouse.enclosingNodeOrSelfWithNodeName("li");
805         if (!listNode)
806             return;
807
808         var element = listNode.treeElement;
809         if (this._previousHoveredElement === element)
810             return;
811
812         if (this._previousHoveredElement) {
813             this._previousHoveredElement.hovered = false;
814             delete this._previousHoveredElement;
815         }
816
817         if (element instanceof WebInspector.FrameTreeElement) {
818             this._previousHoveredElement = element;
819             element.hovered = true;
820         }
821     },
822
823     _onmouseout: function(event)
824     {
825         if (this._previousHoveredElement) {
826             this._previousHoveredElement.hovered = false;
827             delete this._previousHoveredElement;
828         }
829     }
830 }
831
832 WebInspector.ResourcesPanel.prototype.__proto__ = WebInspector.Panel.prototype;
833
834 /**
835  * @constructor
836  * @extends {TreeElement}
837  * @param {boolean=} hasChildren
838  * @param {boolean=} noIcon
839  */
840 WebInspector.BaseStorageTreeElement = function(storagePanel, representedObject, title, iconClasses, hasChildren, noIcon)
841 {
842     TreeElement.call(this, "", representedObject, hasChildren);
843     this._storagePanel = storagePanel;
844     this._titleText = title;
845     this._iconClasses = iconClasses;
846     this._noIcon = noIcon;
847 }
848
849 WebInspector.BaseStorageTreeElement.prototype = {
850     onattach: function()
851     {
852         this.listItemElement.removeChildren();
853         if (this._iconClasses) {
854             for (var i = 0; i < this._iconClasses.length; ++i)
855                 this.listItemElement.addStyleClass(this._iconClasses[i]);
856         }
857
858         var selectionElement = document.createElement("div");
859         selectionElement.className = "selection";
860         this.listItemElement.appendChild(selectionElement);
861
862         if (!this._noIcon) {
863             this.imageElement = document.createElement("img");
864             this.imageElement.className = "icon";
865             this.listItemElement.appendChild(this.imageElement);
866         }
867
868         this.titleElement = document.createElement("div");
869         this.titleElement.className = "base-storage-tree-element-title";
870         this._titleTextNode = document.createTextNode("");
871         this.titleElement.appendChild(this._titleTextNode);
872         this._updateTitle();
873         this._updateSubtitle();
874         this.listItemElement.appendChild(this.titleElement);
875     },
876
877     get displayName()
878     {
879         return this._displayName;
880     },
881
882     _updateDisplayName: function()
883     {
884         this._displayName = this._titleText || "";
885         if (this._subtitleText)
886             this._displayName += " (" + this._subtitleText + ")";
887     },
888
889     _updateTitle: function()
890     {
891         this._updateDisplayName();
892
893         if (!this.titleElement)
894             return;
895
896         this._titleTextNode.textContent = this._titleText || "";
897     },
898
899     _updateSubtitle: function()
900     {
901         this._updateDisplayName();
902
903         if (!this.titleElement)
904             return;
905
906         if (this._subtitleText) {
907             if (!this._subtitleElement) {
908                 this._subtitleElement = document.createElement("span");
909                 this._subtitleElement.className = "base-storage-tree-element-subtitle";
910                 this.titleElement.appendChild(this._subtitleElement);
911             }
912             this._subtitleElement.textContent = "(" + this._subtitleText + ")";
913         } else if (this._subtitleElement) {
914             this.titleElement.removeChild(this._subtitleElement);
915             delete this._subtitleElement;
916         }
917     },
918
919     onselect: function()
920     {
921         var itemURL = this.itemURL;
922         if (itemURL)
923             WebInspector.settings.resourcesLastSelectedItem.set(itemURL);
924     },
925
926     onreveal: function()
927     {
928         if (this.listItemElement)
929             this.listItemElement.scrollIntoViewIfNeeded(false);
930     },
931
932     get titleText()
933     {
934         return this._titleText;
935     },
936
937     set titleText(titleText)
938     {
939         this._titleText = titleText;
940         this._updateTitle();
941     },
942
943     get subtitleText()
944     {
945         return this._subtitleText;
946     },
947
948     set subtitleText(subtitleText)
949     {
950         this._subtitleText = subtitleText;
951         this._updateSubtitle();
952     },
953
954     get searchMatchesCount()
955     {
956         return 0;
957     }
958 }
959
960 WebInspector.BaseStorageTreeElement.prototype.__proto__ = TreeElement.prototype;
961
962 /**
963  * @constructor
964  * @extends {WebInspector.BaseStorageTreeElement}
965  * @param {boolean=} noIcon
966  */
967 WebInspector.StorageCategoryTreeElement = function(storagePanel, categoryName, settingsKey, iconClasses, noIcon)
968 {
969     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, categoryName, iconClasses, true, noIcon);
970     this._expandedSettingKey = "resources" + settingsKey + "Expanded";
971     WebInspector.settings[this._expandedSettingKey] = WebInspector.settings.createSetting(this._expandedSettingKey, settingsKey === "Frames");
972     this._categoryName = categoryName;
973 }
974
975 WebInspector.StorageCategoryTreeElement.prototype = {
976     get itemURL()
977     {
978         return "category://" + this._categoryName;
979     },
980
981     onselect: function()
982     {
983         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
984         this._storagePanel.showCategoryView(this._categoryName);
985     },
986
987     onattach: function()
988     {
989         WebInspector.BaseStorageTreeElement.prototype.onattach.call(this);
990         if (WebInspector.settings[this._expandedSettingKey].get())
991             this.expand();
992     },
993
994     onexpand: function()
995     {
996         WebInspector.settings[this._expandedSettingKey].set(true);
997     },
998
999     oncollapse: function()
1000     {
1001         WebInspector.settings[this._expandedSettingKey].set(false);
1002     }
1003 }
1004
1005 WebInspector.StorageCategoryTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1006
1007 /**
1008  * @constructor
1009  * @extends {WebInspector.BaseStorageTreeElement}
1010  */
1011 WebInspector.FrameTreeElement = function(storagePanel, frame)
1012 {
1013     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, "", ["frame-storage-tree-item"]);
1014     this._frame = frame;
1015     this.frameNavigated(frame);
1016 }
1017
1018 WebInspector.FrameTreeElement.prototype = {
1019     frameNavigated: function(frame)
1020     {
1021         this.removeChildren();
1022         this._frameId = frame.id;
1023
1024         this.titleText = frame.name;
1025         this.subtitleText = new WebInspector.ParsedURL(frame.url).displayName;
1026
1027         this._categoryElements = {};
1028         this._treeElementForResource = {};
1029
1030         this._storagePanel.addDocumentURL(frame.url);
1031     },
1032
1033     get itemURL()
1034     {
1035         return "frame://" + encodeURI(this.displayName);
1036     },
1037
1038     onselect: function()
1039     {
1040         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1041         this._storagePanel.showCategoryView(this.displayName);
1042
1043         this.listItemElement.removeStyleClass("hovered");
1044         DOMAgent.hideHighlight();
1045     },
1046
1047     set hovered(hovered)
1048     {
1049         if (hovered) {
1050             this.listItemElement.addStyleClass("hovered");
1051             DOMAgent.highlightFrame(this._frameId, WebInspector.Color.PageHighlight.Content.toProtocolRGBA(), WebInspector.Color.PageHighlight.ContentOutline.toProtocolRGBA());
1052         } else {
1053             this.listItemElement.removeStyleClass("hovered");
1054             DOMAgent.hideHighlight();
1055         }
1056     },
1057
1058     appendResource: function(resource)
1059     {
1060         var categoryName = resource.type.name();
1061         var categoryElement = resource.type === WebInspector.resourceTypes.Document ? this : this._categoryElements[categoryName];
1062         if (!categoryElement) {
1063             categoryElement = new WebInspector.StorageCategoryTreeElement(this._storagePanel, resource.type.categoryTitle(), categoryName, null, true);
1064             this._categoryElements[resource.type.name()] = categoryElement;
1065             this._insertInPresentationOrder(this, categoryElement);
1066         }
1067         var resourceTreeElement = new WebInspector.FrameResourceTreeElement(this._storagePanel, resource);
1068         this._insertInPresentationOrder(categoryElement, resourceTreeElement);
1069         this._treeElementForResource[resource.url] = resourceTreeElement;
1070     },
1071
1072     resourceByURL: function(url)
1073     {
1074         var treeElement = this._treeElementForResource[url];
1075         return treeElement ? treeElement.representedObject : null;
1076     },
1077
1078     appendChild: function(treeElement)
1079     {
1080         this._insertInPresentationOrder(this, treeElement);
1081     },
1082
1083     _insertInPresentationOrder: function(parentTreeElement, childTreeElement)
1084     {
1085         // Insert in the alphabetical order, first frames, then resources. Document resource goes last.
1086         function typeWeight(treeElement)
1087         {
1088             if (treeElement instanceof WebInspector.StorageCategoryTreeElement)
1089                 return 2;
1090             if (treeElement instanceof WebInspector.FrameTreeElement)
1091                 return 1;
1092             return 3;
1093         }
1094
1095         function compare(treeElement1, treeElement2)
1096         {
1097             var typeWeight1 = typeWeight(treeElement1);
1098             var typeWeight2 = typeWeight(treeElement2);
1099
1100             var result;
1101             if (typeWeight1 > typeWeight2)
1102                 result = 1;
1103             else if (typeWeight1 < typeWeight2)
1104                 result = -1;
1105             else {
1106                 var title1 = treeElement1.displayName || treeElement1.titleText;
1107                 var title2 = treeElement2.displayName || treeElement2.titleText;
1108                 result = title1.localeCompare(title2);
1109             }
1110             return result;
1111         }
1112
1113         var children = parentTreeElement.children;
1114         var i;
1115         for (i = 0; i < children.length; ++i) {
1116             if (compare(childTreeElement, children[i]) < 0)
1117                 break;
1118         }
1119         parentTreeElement.insertChild(childTreeElement, i);
1120     }
1121 }
1122
1123 WebInspector.FrameTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1124
1125 /**
1126  * @constructor
1127  * @extends {WebInspector.BaseStorageTreeElement}
1128  */
1129 WebInspector.FrameResourceTreeElement = function(storagePanel, resource)
1130 {
1131     WebInspector.BaseStorageTreeElement.call(this, storagePanel, resource, resource.displayName, ["resource-sidebar-tree-item", "resources-type-" + resource.type.name()]);
1132     this._resource = resource;
1133     this._resource.addEventListener(WebInspector.Resource.Events.MessageAdded, this._consoleMessageAdded, this);
1134     this._resource.addEventListener(WebInspector.Resource.Events.MessagesCleared, this._consoleMessagesCleared, this);
1135     this.tooltip = resource.url;
1136 }
1137
1138 WebInspector.FrameResourceTreeElement.prototype = {
1139     get itemURL()
1140     {
1141         return this._resource.url;
1142     },
1143
1144     onselect: function()
1145     {
1146         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1147         this._storagePanel._showResourceView(this._resource);
1148     },
1149
1150     ondblclick: function(event)
1151     {
1152         InspectorFrontendHost.openInNewTab(this._resource.url);
1153     },
1154
1155     onattach: function()
1156     {
1157         WebInspector.BaseStorageTreeElement.prototype.onattach.call(this);
1158
1159         if (this._resource.type === WebInspector.resourceTypes.Image) {
1160             var previewImage = document.createElement("img");
1161             previewImage.className = "image-resource-icon-preview";
1162             this._resource.populateImageSource(previewImage);
1163
1164             var iconElement = document.createElement("div");
1165             iconElement.className = "icon";
1166             iconElement.appendChild(previewImage);
1167             this.listItemElement.replaceChild(iconElement, this.imageElement);
1168         }
1169
1170         this._statusElement = document.createElement("div");
1171         this._statusElement.className = "status";
1172         this.listItemElement.insertBefore(this._statusElement, this.titleElement);
1173
1174         this.listItemElement.draggable = true;
1175         this.listItemElement.addEventListener("dragstart", this._ondragstart.bind(this), false);
1176         this.listItemElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true);
1177
1178         this._updateErrorsAndWarningsBubbles();
1179     },
1180
1181     _ondragstart: function(event)
1182     {
1183         event.dataTransfer.setData("text/plain", this._resource.content);
1184         event.dataTransfer.effectAllowed = "copy";
1185         return true;
1186     },
1187
1188     _handleContextMenuEvent: function(event)
1189     {
1190         var contextMenu = new WebInspector.ContextMenu();
1191         contextMenu.appendItem(WebInspector.openLinkExternallyLabel(), WebInspector.openResource.bind(WebInspector, this._resource.url, false));
1192         contextMenu.appendItem(WebInspector.copyLinkAddressLabel(), InspectorFrontendHost.copyText.bind(InspectorFrontendHost, this._resource.url));
1193         this._appendOpenInNetworkPanelAction(contextMenu, event);
1194         WebInspector.populateResourceContextMenu(contextMenu, this._resource.url, null);
1195         this._appendSaveAsAction(contextMenu, event);
1196         contextMenu.show(event);
1197     },
1198
1199     _appendOpenInNetworkPanelAction: function(contextMenu, event)
1200     {
1201         if (!this._resource.request)
1202             return;
1203
1204         contextMenu.appendItem(WebInspector.openInNetworkPanelLabel(), WebInspector.openRequestInNetworkPanel.bind(WebInspector, this._resource.request));
1205     },
1206
1207     _appendSaveAsAction: function(contextMenu, event)
1208     {
1209         if (!InspectorFrontendHost.canSave())
1210             return;
1211
1212         if (this._resource.type !== WebInspector.resourceTypes.Document &&
1213             this._resource.type !== WebInspector.resourceTypes.Stylesheet &&
1214             this._resource.type !== WebInspector.resourceTypes.Script)
1215             return;
1216
1217         function doSave(forceSaveAs, content)
1218         {
1219             WebInspector.fileManager.save(this._resource.url, content, forceSaveAs);
1220         }
1221
1222         function save(forceSaveAs)
1223         {
1224             this._resource.requestContent(doSave.bind(this, forceSaveAs));
1225         }
1226
1227         contextMenu.appendSeparator();
1228         contextMenu.appendItem(WebInspector.UIString("Save"), save.bind(this, false));
1229         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Save as..." : "Save As..."), save.bind(this, true));
1230     },
1231
1232     _setBubbleText: function(x)
1233     {
1234         if (!this._bubbleElement) {
1235             this._bubbleElement = document.createElement("div");
1236             this._bubbleElement.className = "bubble";
1237             this._statusElement.appendChild(this._bubbleElement);
1238         }
1239
1240         this._bubbleElement.textContent = x;
1241     },
1242
1243     _resetBubble: function()
1244     {
1245         if (this._bubbleElement) {
1246             this._bubbleElement.textContent = "";
1247             this._bubbleElement.removeStyleClass("search-matches");
1248             this._bubbleElement.removeStyleClass("warning");
1249             this._bubbleElement.removeStyleClass("error");
1250         }
1251     },
1252
1253     _resetSearchResults: function()
1254     {
1255         this._resetBubble();
1256         this._searchMatchesCount = 0;
1257     },
1258
1259     get searchMatchesCount()
1260     {
1261         return this._searchMatchesCount;
1262     },
1263
1264     searchMatchesFound: function(matchesCount)
1265     {
1266         this._resetSearchResults();
1267
1268         this._searchMatchesCount = matchesCount;
1269         this._setBubbleText(matchesCount);
1270         this._bubbleElement.addStyleClass("search-matches");
1271
1272         // Expand, do not scroll into view.
1273         var currentAncestor = this.parent;
1274         while (currentAncestor && !currentAncestor.root) {
1275             if (!currentAncestor.expanded)
1276                 currentAncestor.expand();
1277             currentAncestor = currentAncestor.parent;
1278         }
1279     },
1280
1281     _updateErrorsAndWarningsBubbles: function()
1282     {
1283         if (this._storagePanel.currentQuery)
1284             return;
1285
1286         this._resetBubble();
1287
1288         if (this._resource.warnings || this._resource.errors)
1289             this._setBubbleText(this._resource.warnings + this._resource.errors);
1290
1291         if (this._resource.warnings)
1292             this._bubbleElement.addStyleClass("warning");
1293
1294         if (this._resource.errors)
1295             this._bubbleElement.addStyleClass("error");
1296     },
1297
1298     _consoleMessagesCleared: function()
1299     {
1300         // FIXME: move to the SourceFrame.
1301         if (this._sourceView)
1302             this._sourceView.clearMessages();
1303
1304         this._updateErrorsAndWarningsBubbles();
1305     },
1306
1307     _consoleMessageAdded: function(event)
1308     {
1309         var msg = event.data;
1310         if (this._sourceView)
1311             this._sourceView.addMessage(msg);
1312         this._updateErrorsAndWarningsBubbles();
1313     },
1314
1315     sourceView: function()
1316     {
1317         if (!this._sourceView) {
1318             this._sourceView = new WebInspector.ResourceSourceFrame(this._resource);
1319             if (this._resource.messages) {
1320                 for (var i = 0; i < this._resource.messages.length; i++)
1321                     this._sourceView.addMessage(this._resource.messages[i]);
1322             }
1323         }
1324         return this._sourceView;
1325     }
1326 }
1327
1328 WebInspector.FrameResourceTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1329
1330 /**
1331  * @constructor
1332  * @extends {WebInspector.BaseStorageTreeElement}
1333  */
1334 WebInspector.DatabaseTreeElement = function(storagePanel, database)
1335 {
1336     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, database.name, ["database-storage-tree-item"], true);
1337     this._database = database;
1338 }
1339
1340 WebInspector.DatabaseTreeElement.prototype = {
1341     get itemURL()
1342     {
1343         return "database://" + encodeURI(this._database.name);
1344     },
1345
1346     onselect: function()
1347     {
1348         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1349         this._storagePanel.showDatabase(this._database);
1350     },
1351
1352     onexpand: function()
1353     {
1354         this._updateChildren();
1355     },
1356
1357     _updateChildren: function()
1358     {
1359         this.removeChildren();
1360
1361         function tableNamesCallback(tableNames)
1362         {
1363             var tableNamesLength = tableNames.length;
1364             for (var i = 0; i < tableNamesLength; ++i)
1365                 this.appendChild(new WebInspector.DatabaseTableTreeElement(this._storagePanel, this._database, tableNames[i]));
1366         }
1367         this._database.getTableNames(tableNamesCallback.bind(this));
1368     }
1369 }
1370
1371 WebInspector.DatabaseTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1372
1373 /**
1374  * @constructor
1375  * @extends {WebInspector.BaseStorageTreeElement}
1376  */
1377 WebInspector.DatabaseTableTreeElement = function(storagePanel, database, tableName)
1378 {
1379     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, tableName, ["database-storage-tree-item"]);
1380     this._database = database;
1381     this._tableName = tableName;
1382 }
1383
1384 WebInspector.DatabaseTableTreeElement.prototype = {
1385     get itemURL()
1386     {
1387         return "database://" + encodeURI(this._database.name) + "/" + encodeURI(this._tableName);
1388     },
1389
1390     onselect: function()
1391     {
1392         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1393         this._storagePanel.showDatabase(this._database, this._tableName);
1394     }
1395 }
1396 WebInspector.DatabaseTableTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1397
1398 /**
1399  * @constructor
1400  * @extends {WebInspector.StorageCategoryTreeElement}
1401  * @param {WebInspector.ResourcesPanel} storagePanel
1402  */
1403 WebInspector.IndexedDBTreeElement = function(storagePanel)
1404 {
1405     WebInspector.StorageCategoryTreeElement.call(this, storagePanel, WebInspector.UIString("IndexedDB"), "IndexedDB", ["indexed-db-storage-tree-item"]);
1406 }
1407
1408 WebInspector.IndexedDBTreeElement.prototype = {
1409     onexpand: function()
1410     {
1411         WebInspector.StorageCategoryTreeElement.prototype.onexpand.call(this);
1412         if (!this._indexedDBModel)
1413             this._createIndexedDBModel();
1414     },
1415
1416     onattach: function()
1417     {
1418         WebInspector.StorageCategoryTreeElement.prototype.onattach.call(this);
1419         this.listItemElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true);
1420     },
1421
1422     _handleContextMenuEvent: function(event)
1423     {
1424         var contextMenu = new WebInspector.ContextMenu();
1425         contextMenu.appendItem(WebInspector.UIString("Refresh IndexedDB"), this.refreshIndexedDB.bind(this));
1426         contextMenu.show(event);
1427     },
1428
1429     _createIndexedDBModel: function()
1430     {
1431         this._indexedDBModel = new WebInspector.IndexedDBModel();
1432         this._idbDatabaseTreeElements = [];
1433         this._indexedDBModel.addEventListener(WebInspector.IndexedDBModel.EventTypes.DatabaseAdded, this._indexedDBAdded, this);
1434         this._indexedDBModel.addEventListener(WebInspector.IndexedDBModel.EventTypes.DatabaseRemoved, this._indexedDBRemoved, this);
1435         this._indexedDBModel.addEventListener(WebInspector.IndexedDBModel.EventTypes.DatabaseLoaded, this._indexedDBLoaded, this);
1436     },
1437
1438     refreshIndexedDB: function()
1439     {
1440         if (!this._indexedDBModel) {
1441             this._createIndexedDBModel();
1442             return;
1443         }
1444
1445         this._indexedDBModel.refreshDatabaseNames();
1446     },
1447
1448     /**
1449      * @param {WebInspector.Event} event
1450      */
1451     _indexedDBAdded: function(event)
1452     {
1453         var databaseId = /** @type {WebInspector.IndexedDBModel.DatabaseId} */ event.data;
1454
1455         var idbDatabaseTreeElement = new WebInspector.IDBDatabaseTreeElement(this._storagePanel, this._indexedDBModel, databaseId);
1456         this._idbDatabaseTreeElements.push(idbDatabaseTreeElement);
1457         this.appendChild(idbDatabaseTreeElement);
1458
1459         this._indexedDBModel.refreshDatabase(databaseId);
1460     },
1461
1462     /**
1463      * @param {WebInspector.Event} event
1464      */
1465     _indexedDBRemoved: function(event)
1466     {
1467         var databaseId = /** @type {WebInspector.IndexedDBModel.DatabaseId} */ event.data;
1468
1469         var idbDatabaseTreeElement = this._idbDatabaseTreeElement(databaseId)
1470         if (!idbDatabaseTreeElement)
1471             return;
1472
1473         idbDatabaseTreeElement.clear();
1474         this.removeChild(idbDatabaseTreeElement);
1475         this._idbDatabaseTreeElements.remove(idbDatabaseTreeElement);
1476     },
1477
1478     /**
1479      * @param {WebInspector.Event} event
1480      */
1481     _indexedDBLoaded: function(event)
1482     {
1483         var database = /** @type {WebInspector.IndexedDBModel.Database} */ event.data;
1484
1485         var idbDatabaseTreeElement = this._idbDatabaseTreeElement(database.databaseId)
1486         if (!idbDatabaseTreeElement)
1487             return;
1488
1489         idbDatabaseTreeElement.update(database);
1490     },
1491
1492     /**
1493      * @param {WebInspector.IndexedDBModel.DatabaseId} databaseId
1494      * @return {WebInspector.IDBDatabaseTreeElement}
1495      */
1496     _idbDatabaseTreeElement: function(databaseId)
1497     {
1498         var index = -1;
1499         for (var i = 0; i < this._idbDatabaseTreeElements.length; ++i) {
1500             if (this._idbDatabaseTreeElements[i]._databaseId.equals(databaseId)) {
1501                 index = i;
1502                 break;
1503             }
1504         }
1505         if (index !== -1)
1506             return this._idbDatabaseTreeElements[i];
1507         return null;
1508     }
1509 }
1510
1511 WebInspector.IndexedDBTreeElement.prototype.__proto__ = WebInspector.StorageCategoryTreeElement.prototype;
1512
1513 /**
1514  * @constructor
1515  * @extends {WebInspector.BaseStorageTreeElement}
1516  * @param {WebInspector.ResourcesPanel} storagePanel
1517  * @param {WebInspector.IndexedDBModel} model
1518  * @param {WebInspector.IndexedDBModel.DatabaseId} databaseId
1519  */
1520 WebInspector.IDBDatabaseTreeElement = function(storagePanel, model, databaseId)
1521 {
1522     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, databaseId.name + " - " + databaseId.securityOrigin, ["indexed-db-storage-tree-item"]);
1523     this._model = model;
1524     this._databaseId = databaseId;
1525     this._idbObjectStoreTreeElements = {};
1526 }
1527
1528 WebInspector.IDBDatabaseTreeElement.prototype = {
1529     get itemURL()
1530     {
1531         return "indexedDB://" + this._databaseId.securityOrigin + "/" + this._databaseId.name;
1532     },
1533
1534     onattach: function()
1535     {
1536         WebInspector.BaseStorageTreeElement.prototype.onattach.call(this);
1537         this.listItemElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true);
1538     },
1539
1540     _handleContextMenuEvent: function(event)
1541     {
1542         var contextMenu = new WebInspector.ContextMenu();
1543         contextMenu.appendItem(WebInspector.UIString("Refresh IndexedDB"), this._refreshIndexedDB.bind(this));
1544         contextMenu.show(event);
1545     },
1546
1547     _refreshIndexedDB: function(event)
1548     {
1549         this._model.refreshDatabaseNames();
1550     },
1551
1552     /**
1553      * @param {WebInspector.IndexedDBModel.Database} database
1554      */
1555     update: function(database)
1556     {
1557         this._database = database;
1558         var objectStoreNames = {};
1559         for (var objectStoreName in this._database.objectStores) {
1560             var objectStore = this._database.objectStores[objectStoreName];
1561             objectStoreNames[objectStore.name] = true;
1562             if (!this._idbObjectStoreTreeElements[objectStore.name]) {
1563                 var idbObjectStoreTreeElement = new WebInspector.IDBObjectStoreTreeElement(this._storagePanel, this._model, this._databaseId, objectStore);
1564                 this._idbObjectStoreTreeElements[objectStore.name] = idbObjectStoreTreeElement;
1565                 this.appendChild(idbObjectStoreTreeElement);
1566             }
1567             this._idbObjectStoreTreeElements[objectStore.name].update(objectStore);
1568         }
1569         for (var objectStoreName in this._idbObjectStoreTreeElements) {
1570             if (!objectStoreNames[objectStoreName])
1571                 this._objectStoreRemoved(objectStoreName);
1572         }
1573
1574         if (this.children.length) {
1575             this.hasChildren = true;
1576             this.expand();
1577         }
1578
1579         if (this._view)
1580             this._view.update(database);
1581         
1582         this._updateTooltip();
1583     },
1584
1585     _updateTooltip: function()
1586     {
1587         this.tooltip = WebInspector.UIString("Version") + ": " + this._database.version;
1588     },
1589
1590     onselect: function()
1591     {
1592         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1593         if (!this._view)
1594             this._view = new WebInspector.IDBDatabaseView(this._database);
1595
1596         this._storagePanel.showIndexedDB(this._view);
1597     },
1598
1599     /**
1600      * @param {string} objectStoreName
1601      */
1602     _objectStoreRemoved: function(objectStoreName)
1603     {
1604         var objectStoreTreeElement = this._idbObjectStoreTreeElements[objectStoreName];
1605         objectStoreTreeElement.clear();
1606         this.removeChild(objectStoreTreeElement);
1607         delete this._idbObjectStoreTreeElements[objectStoreName];
1608     },
1609
1610     clear: function()
1611     {
1612         for (var objectStoreName in this._idbObjectStoreTreeElements)
1613             this._objectStoreRemoved(objectStoreName);
1614     }
1615 }
1616
1617 WebInspector.IDBDatabaseTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1618
1619 /**
1620  * @constructor
1621  * @extends {WebInspector.BaseStorageTreeElement}
1622  * @param {WebInspector.ResourcesPanel} storagePanel
1623  * @param {WebInspector.IndexedDBModel} model
1624  * @param {WebInspector.IndexedDBModel.DatabaseId} databaseId
1625  * @param {WebInspector.IndexedDBModel.ObjectStore} objectStore
1626  */
1627 WebInspector.IDBObjectStoreTreeElement = function(storagePanel, model, databaseId, objectStore)
1628 {
1629     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, objectStore.name, ["indexed-db-object-store-storage-tree-item"]);
1630     this._model = model;
1631     this._databaseId = databaseId;
1632     this._idbIndexTreeElements = {};
1633 }
1634
1635 WebInspector.IDBObjectStoreTreeElement.prototype = {
1636     get itemURL()
1637     {
1638         return "indexedDB://" + this._databaseId.securityOrigin + "/" + this._databaseId.name + "/" + this._objectStore.name;
1639     },
1640
1641    /**
1642      * @param {WebInspector.IndexedDBModel.ObjectStore} objectStore
1643      */
1644     update: function(objectStore)
1645     {
1646         this._objectStore = objectStore;
1647
1648         var indexNames = {};
1649         for (var indexName in this._objectStore.indexes) {
1650             var index = this._objectStore.indexes[indexName];
1651             indexNames[index.name] = true;
1652             if (!this._idbIndexTreeElements[index.name]) {
1653                 var idbIndexTreeElement = new WebInspector.IDBIndexTreeElement(this._storagePanel, this._model, this._databaseId, this._objectStore, index);
1654                 this._idbIndexTreeElements[index.name] = idbIndexTreeElement;
1655                 this.appendChild(idbIndexTreeElement);
1656             }
1657             this._idbIndexTreeElements[index.name].update(index);
1658         }
1659         for (var indexName in this._idbIndexTreeElements) {
1660             if (!indexNames[indexName])
1661                 this._indexRemoved(indexName);
1662         }
1663         for (var indexName in this._idbIndexTreeElements) {
1664             if (!indexNames[indexName]) {
1665                 this.removeChild(this._idbIndexTreeElements[indexName]);
1666                 delete this._idbIndexTreeElements[indexName];
1667             }
1668         }
1669
1670         if (this.children.length) {
1671             this.hasChildren = true;
1672             this.expand();
1673         }
1674
1675         if (this._view)
1676             this._view.update(this._objectStore);
1677         
1678         this._updateTooltip();
1679     },
1680
1681     _updateTooltip: function()
1682     {
1683         this.tooltip = this._objectStore.keyPath ? (WebInspector.UIString("Key path") + ": " + this._objectStore.keyPath) : "";
1684     },
1685
1686     onselect: function()
1687     {
1688         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1689         if (!this._view)
1690             this._view = new WebInspector.IDBDataView(this._model, this._databaseId, this._objectStore, null);
1691
1692         this._storagePanel.showIndexedDB(this._view);
1693     },
1694
1695     /**
1696      * @param {string} indexName
1697      */
1698     _indexRemoved: function(indexName)
1699     {
1700         var indexTreeElement = this._idbIndexTreeElements[indexName];
1701         indexTreeElement.clear();
1702         this.removeChild(indexTreeElement);
1703         delete this._idbIndexTreeElements[indexName];
1704     },
1705
1706     clear: function()
1707     {
1708         for (var indexName in this._idbIndexTreeElements)
1709             this._indexRemoved(indexName);
1710         if (this._view)
1711             this._view.clear();
1712     }
1713 }
1714
1715 WebInspector.IDBObjectStoreTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1716
1717 /**
1718  * @constructor
1719  * @extends {WebInspector.BaseStorageTreeElement}
1720  * @param {WebInspector.ResourcesPanel} storagePanel
1721  * @param {WebInspector.IndexedDBModel} model
1722  * @param {WebInspector.IndexedDBModel.DatabaseId} databaseId
1723  * @param {WebInspector.IndexedDBModel.ObjectStore} objectStore
1724  * @param {WebInspector.IndexedDBModel.Index} index
1725  */
1726 WebInspector.IDBIndexTreeElement = function(storagePanel, model, databaseId, objectStore, index)
1727 {
1728     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, index.name, ["indexed-db-index-storage-tree-item"]);
1729     this._model = model;
1730     this._databaseId = databaseId;
1731     this._objectStore = objectStore;
1732     this._index = index;
1733 }
1734
1735 WebInspector.IDBIndexTreeElement.prototype = {
1736     get itemURL()
1737     {
1738         return "indexedDB://" + this._databaseId.securityOrigin + "/" + this._databaseId.name + "/" + this._objectStore.name + "/" + this._index.name;
1739     },
1740
1741     /**
1742      * @param {WebInspector.IndexedDBModel.Index} index
1743      */
1744     update: function(index)
1745     {
1746         this._index = index;
1747
1748         if (this._view)
1749             this._view.update(this._index);
1750         
1751         this._updateTooltip();
1752     },
1753
1754     _updateTooltip: function()
1755     {
1756         var tooltipLines = [];
1757         tooltipLines.push(WebInspector.UIString("Key path") + ": " + this._index.keyPath);
1758         if (this._index.unique)
1759             tooltipLines.push(WebInspector.UIString("unique"));
1760         if (this._index.multiEntry)
1761             tooltipLines.push(WebInspector.UIString("multiEntry"));
1762         this.tooltip = tooltipLines.join("\n");
1763     },
1764
1765     onselect: function()
1766     {
1767         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1768         if (!this._view)
1769             this._view = new WebInspector.IDBDataView(this._model, this._databaseId, this._objectStore, this._index);
1770
1771         this._storagePanel.showIndexedDB(this._view);
1772     },
1773
1774     clear: function()
1775     {
1776         if (this._view)
1777             this._view.clear();
1778     }
1779 }
1780
1781 WebInspector.IDBIndexTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1782
1783 /**
1784  * @constructor
1785  * @extends {WebInspector.BaseStorageTreeElement}
1786  */
1787 WebInspector.DOMStorageTreeElement = function(storagePanel, domStorage, className)
1788 {
1789     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, domStorage.domain ? domStorage.domain : WebInspector.UIString("Local Files"), ["domstorage-storage-tree-item", className]);
1790     this._domStorage = domStorage;
1791 }
1792
1793 WebInspector.DOMStorageTreeElement.prototype = {
1794     get itemURL()
1795     {
1796         return "storage://" + this._domStorage.domain + "/" + (this._domStorage.isLocalStorage ? "local" : "session");
1797     },
1798
1799     onselect: function()
1800     {
1801         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1802         this._storagePanel.showDOMStorage(this._domStorage);
1803     }
1804 }
1805 WebInspector.DOMStorageTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1806
1807 /**
1808  * @constructor
1809  * @extends {WebInspector.BaseStorageTreeElement}
1810  */
1811 WebInspector.CookieTreeElement = function(storagePanel, cookieDomain)
1812 {
1813     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, cookieDomain ? cookieDomain : WebInspector.UIString("Local Files"), ["cookie-storage-tree-item"]);
1814     this._cookieDomain = cookieDomain;
1815 }
1816
1817 WebInspector.CookieTreeElement.prototype = {
1818     get itemURL()
1819     {
1820         return "cookies://" + this._cookieDomain;
1821     },
1822
1823     onselect: function()
1824     {
1825         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1826         this._storagePanel.showCookies(this, this._cookieDomain);
1827     }
1828 }
1829 WebInspector.CookieTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1830
1831 /**
1832  * @constructor
1833  * @extends {WebInspector.BaseStorageTreeElement}
1834  */
1835 WebInspector.ApplicationCacheManifestTreeElement = function(storagePanel, manifestURL)
1836 {
1837     var title = new WebInspector.ParsedURL(manifestURL).displayName;
1838     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, title, ["application-cache-storage-tree-item"]);
1839     this.tooltip = manifestURL;
1840     this._manifestURL = manifestURL;
1841 }
1842
1843 WebInspector.ApplicationCacheManifestTreeElement.prototype = {
1844     get itemURL()
1845     {
1846         return "appcache://" + this._manifestURL;
1847     },
1848
1849     get manifestURL()
1850     {
1851         return this._manifestURL;
1852     },
1853
1854     onselect: function()
1855     {
1856         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1857         this._storagePanel.showCategoryView(this._manifestURL);
1858     }
1859 }
1860 WebInspector.ApplicationCacheManifestTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1861
1862 /**
1863  * @constructor
1864  * @extends {WebInspector.BaseStorageTreeElement}
1865  */
1866 WebInspector.ApplicationCacheFrameTreeElement = function(storagePanel, frameId, manifestURL)
1867 {
1868     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, "", ["frame-storage-tree-item"]);
1869     this._frameId = frameId;
1870     this._manifestURL = manifestURL;
1871     this._refreshTitles();
1872 }
1873
1874 WebInspector.ApplicationCacheFrameTreeElement.prototype = {
1875     get itemURL()
1876     {
1877         return "appcache://" + this._manifestURL + "/" + encodeURI(this.displayName);
1878     },
1879
1880     get frameId()
1881     {
1882         return this._frameId;
1883     },
1884
1885     get manifestURL()
1886     {
1887         return this._manifestURL;
1888     },
1889
1890     _refreshTitles: function()
1891     {
1892         var frame = WebInspector.resourceTreeModel.frameForId(this._frameId);
1893         if (!frame) {
1894             this.subtitleText = WebInspector.UIString("new frame");
1895             return;
1896         }
1897         this.titleText = frame.name;
1898         this.subtitleText = new WebInspector.ParsedURL(frame.url).displayName;
1899     },
1900
1901     frameNavigated: function()
1902     {
1903         this._refreshTitles();
1904     },
1905
1906     onselect: function()
1907     {
1908         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1909         this._storagePanel.showApplicationCache(this._frameId);
1910     }
1911 }
1912 WebInspector.ApplicationCacheFrameTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1913
1914 /**
1915  * @constructor
1916  * @extends {WebInspector.View}
1917  */
1918 WebInspector.StorageCategoryView = function()
1919 {
1920     WebInspector.View.call(this);
1921
1922     this.element.addStyleClass("storage-view");
1923     this._emptyView = new WebInspector.EmptyView("");
1924     this._emptyView.show(this.element);
1925 }
1926
1927 WebInspector.StorageCategoryView.prototype = {
1928     setText: function(text)
1929     {
1930         this._emptyView.text = text;
1931     }
1932 }
1933
1934 WebInspector.StorageCategoryView.prototype.__proto__ = WebInspector.View.prototype;
1935
1936 /**
1937  * @constructor
1938  * @param {WebInspector.BaseStorageTreeElement} rootElement
1939  * @param {number} matchesCount
1940  */
1941 WebInspector.ResourcesSearchController = function(rootElement, matchesCount)
1942 {
1943     this._root = rootElement;
1944     this._matchesCount = matchesCount;
1945     this._traverser = new WebInspector.SearchResultsTreeElementsTraverser(rootElement);
1946     this._lastTreeElement = null;
1947     this._lastIndex = -1;
1948 }
1949
1950 WebInspector.ResourcesSearchController.prototype = {
1951     /**
1952      * @param {WebInspector.BaseStorageTreeElement} currentTreeElement
1953      */
1954     nextSearchResult: function(currentTreeElement)
1955     {
1956         if (!currentTreeElement)
1957             return this._searchResult(this._traverser.first(), 0, 1);
1958
1959         if (!currentTreeElement.searchMatchesCount)
1960             return this._searchResult(this._traverser.next(currentTreeElement), 0);
1961
1962         if (this._lastTreeElement !== currentTreeElement || this._lastIndex === -1)
1963             return this._searchResult(currentTreeElement, 0);
1964
1965         if (this._lastIndex == currentTreeElement.searchMatchesCount - 1)
1966             return this._searchResult(this._traverser.next(currentTreeElement), 0, this._currentMatchIndex % this._matchesCount + 1);
1967
1968         return this._searchResult(currentTreeElement, this._lastIndex + 1, this._currentMatchIndex + 1);
1969     },
1970
1971     /**
1972      * @param {WebInspector.BaseStorageTreeElement} currentTreeElement
1973      */
1974     previousSearchResult: function(currentTreeElement)
1975     {
1976         if (!currentTreeElement) {
1977             var treeElement = this._traverser.last();
1978             return this._searchResult(treeElement, treeElement.searchMatchesCount - 1, this._matchesCount);
1979         }
1980
1981         if (currentTreeElement.searchMatchesCount && this._lastTreeElement === currentTreeElement) {
1982             if (this._lastIndex > 0)
1983                 return this._searchResult(currentTreeElement, this._lastIndex - 1, this._currentMatchIndex - 1);
1984             else {
1985                 var treeElement = this._traverser.previous(currentTreeElement);
1986                 var currentMatchIndex = this._currentMatchIndex - 1 ? this._currentMatchIndex - 1 : this._matchesCount;
1987                 return this._searchResult(treeElement, treeElement.searchMatchesCount - 1, currentMatchIndex);
1988             }
1989         }
1990
1991         var treeElement = this._traverser.previous(currentTreeElement)
1992         return this._searchResult(treeElement, treeElement.searchMatchesCount - 1);
1993     },
1994
1995     /**
1996      * @param {WebInspector.BaseStorageTreeElement} treeElement
1997      * @param {number} index
1998      * @param {number=} currentMatchIndex
1999      * @return {Object}
2000      */
2001     _searchResult: function(treeElement, index, currentMatchIndex)
2002     {
2003         this._lastTreeElement = treeElement;
2004         this._lastIndex = index;
2005         if (!currentMatchIndex)
2006             currentMatchIndex = this._traverser.matchIndex(treeElement, index);
2007         this._currentMatchIndex = currentMatchIndex;
2008         return {treeElement: treeElement, index: index, currentMatchIndex: currentMatchIndex};
2009     }
2010 }
2011
2012 /**
2013  * @constructor
2014  * @param {WebInspector.BaseStorageTreeElement} rootElement
2015  */
2016 WebInspector.SearchResultsTreeElementsTraverser = function(rootElement)
2017 {
2018     this._root = rootElement;
2019 }
2020
2021 WebInspector.SearchResultsTreeElementsTraverser.prototype = {
2022     /**
2023      * @return {WebInspector.BaseStorageTreeElement}
2024      */
2025     first: function()
2026     {
2027         return this.next(this._root);
2028     },
2029
2030     /**
2031      * @return {WebInspector.BaseStorageTreeElement}
2032      */
2033     last: function()
2034     {
2035         return this.previous(this._root);
2036     },
2037
2038     /**
2039      * @param {WebInspector.BaseStorageTreeElement} startTreeElement
2040      * @return {WebInspector.BaseStorageTreeElement}
2041      */
2042     next: function(startTreeElement)
2043     {
2044         var treeElement = startTreeElement;
2045         do {
2046             treeElement = this._traverseNext(treeElement) || this._root;
2047         } while (treeElement != startTreeElement && !this._elementSearchMatchesCount(treeElement));
2048         return treeElement;
2049     },
2050
2051     /**
2052      * @param {WebInspector.BaseStorageTreeElement} startTreeElement
2053      * @return {WebInspector.BaseStorageTreeElement}
2054      */
2055     previous: function(startTreeElement)
2056     {
2057         var treeElement = startTreeElement;
2058         do {
2059             treeElement = this._traversePrevious(treeElement) || this._lastTreeElement();
2060         } while (treeElement != startTreeElement && !this._elementSearchMatchesCount(treeElement));
2061         return treeElement;
2062     },
2063
2064     /**
2065      * @param {WebInspector.BaseStorageTreeElement} startTreeElement
2066      * @param {number} index
2067      * @return {number}
2068      */
2069     matchIndex: function(startTreeElement, index)
2070     {
2071         var matchIndex = 1;
2072         var treeElement = this._root;
2073         while (treeElement != startTreeElement) {
2074             matchIndex += this._elementSearchMatchesCount(treeElement);
2075             treeElement = this._traverseNext(treeElement) || this._root;
2076             if (treeElement === this._root)
2077                 return 0;
2078         }
2079         return matchIndex + index;
2080     },
2081
2082     /**
2083      * @param {WebInspector.BaseStorageTreeElement} treeElement
2084      * @return {number}
2085      */
2086     _elementSearchMatchesCount: function(treeElement)
2087     {
2088         return treeElement.searchMatchesCount;
2089     },
2090
2091     /**
2092      * @param {WebInspector.BaseStorageTreeElement} treeElement
2093      * @return {WebInspector.BaseStorageTreeElement}
2094      */
2095     _traverseNext: function(treeElement)
2096     {
2097         return /** @type {WebInspector.BaseStorageTreeElement} */ treeElement.traverseNextTreeElement(false, this._root, true);
2098     },
2099
2100     /**
2101      * @param {WebInspector.BaseStorageTreeElement} treeElement
2102      * @return {WebInspector.BaseStorageTreeElement}
2103      */
2104     _traversePrevious: function(treeElement)
2105     {
2106         return /** @type {WebInspector.BaseStorageTreeElement} */ treeElement.traversePreviousTreeElement(false, true);
2107     },
2108
2109     /**
2110      * @return {WebInspector.BaseStorageTreeElement}
2111      */
2112     _lastTreeElement: function()
2113     {
2114         var treeElement = this._root;
2115         var nextTreeElement;
2116         while (nextTreeElement = this._traverseNext(treeElement))
2117             treeElement = nextTreeElement;
2118         return treeElement;
2119     }
2120 }