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