0b8351ba22337103585b219eda20dc8bb84f457e
[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._fetchAndApplyDiffMarkup(view, resource);
376         this._innerShowView(view);
377     },
378
379     _resourceViewForResource: function(resource)
380     {
381         if (WebInspector.ResourceView.hasTextContent(resource)) {
382             var treeElement = this._findTreeElementForResource(resource);
383             if (!treeElement)
384                 return null;
385             return treeElement.sourceView();
386         }
387         return WebInspector.ResourceView.nonSourceViewForResource(resource);
388     },
389
390     _showRevisionView: function(revision)
391     {
392         var view = this._sourceViewForRevision(revision);
393         this._fetchAndApplyDiffMarkup(view, revision.resource, revision);
394         this._innerShowView(view);
395     },
396
397     _sourceViewForRevision: function(revision)
398     {
399         var treeElement = this._findTreeElementForRevision(revision);
400         return treeElement.sourceView();
401     },
402
403     /**
404      * @param {WebInspector.ResourceRevision=} revision
405      */
406     _fetchAndApplyDiffMarkup: function(view, resource, revision)
407     {
408         var baseRevision = resource.history[0];
409         if (!baseRevision)
410             return;
411         if (!(view instanceof WebInspector.SourceFrame))
412             return;
413
414         baseRevision.requestContent(step1.bind(this));
415
416         function step1(baseContent)
417         {
418             (revision ? revision : resource).requestContent(step2.bind(this, baseContent));
419         }
420
421         function step2(baseContent, revisionContent)
422         {
423             this._applyDiffMarkup(view, baseContent, revisionContent);
424         }
425     },
426
427     _applyDiffMarkup: function(view, baseContent, newContent)
428     {
429         var diffData = TextDiff.compute(baseContent, newContent);
430         view.markDiff(diffData);
431     },
432
433     /**
434      * @param {string=} tableName
435      */
436     showDatabase: function(database, tableName)
437     {
438         if (!database)
439             return;
440
441         var view;
442         if (tableName) {
443             if (!("_tableViews" in database))
444                 database._tableViews = {};
445             view = database._tableViews[tableName];
446             if (!view) {
447                 view = new WebInspector.DatabaseTableView(database, tableName);
448                 database._tableViews[tableName] = view;
449             }
450         } else {
451             view = database._queryView;
452             if (!view) {
453                 view = new WebInspector.DatabaseQueryView(database);
454                 database._queryView = view;
455                 view.addEventListener(WebInspector.DatabaseQueryView.Events.SchemaUpdated, this._updateDatabaseTables, this);
456             }
457         }
458
459         this._innerShowView(view);
460     },
461
462     /**
463      * @param {WebInspector.View} view
464      */
465     showIndexedDB: function(view)
466     {
467         this._innerShowView(view);
468     },
469
470     showDOMStorage: function(domStorage)
471     {
472         if (!domStorage)
473             return;
474
475         var view;
476         view = domStorage._domStorageView;
477         if (!view) {
478             view = new WebInspector.DOMStorageItemsView(domStorage);
479             domStorage._domStorageView = view;
480         }
481
482         this._innerShowView(view);
483     },
484
485     showCookies: function(treeElement, cookieDomain)
486     {
487         var view = this._cookieViews[cookieDomain];
488         if (!view) {
489             view = new WebInspector.CookieItemsView(treeElement, cookieDomain);
490             this._cookieViews[cookieDomain] = view;
491         }
492
493         this._innerShowView(view);
494     },
495
496     showApplicationCache: function(frameId)
497     {
498         if (!this._applicationCacheViews[frameId])
499             this._applicationCacheViews[frameId] = new WebInspector.ApplicationCacheItemsView(this._applicationCacheModel, frameId);
500
501         this._innerShowView(this._applicationCacheViews[frameId]);
502     },
503
504     showCategoryView: function(categoryName)
505     {
506         if (!this._categoryView)
507             this._categoryView = new WebInspector.StorageCategoryView();
508         this._categoryView.setText(categoryName);
509         this._innerShowView(this._categoryView);
510     },
511
512     _innerShowView: function(view)
513     {
514         if (this.visibleView === view)
515             return;
516
517         if (this.visibleView)
518             this.visibleView.detach();
519
520         view.show(this.storageViews);
521         this.visibleView = view;
522
523         this.storageViewStatusBarItemsContainer.removeChildren();
524         var statusBarItems = view.statusBarItems || [];
525         for (var i = 0; i < statusBarItems.length; ++i)
526             this.storageViewStatusBarItemsContainer.appendChild(statusBarItems[i]);
527     },
528
529     closeVisibleView: function()
530     {
531         if (!this.visibleView)
532             return;
533         this.visibleView.detach();
534         delete this.visibleView;
535     },
536
537     _updateDatabaseTables: function(event)
538     {
539         var database = event.data;
540
541         if (!database || !database._databasesTreeElement)
542             return;
543
544         database._databasesTreeElement.shouldRefreshChildren = true;
545
546         if (!("_tableViews" in database))
547             return;
548
549         var tableNamesHash = {};
550         var self = this;
551         function tableNamesCallback(tableNames)
552         {
553             var tableNamesLength = tableNames.length;
554             for (var i = 0; i < tableNamesLength; ++i)
555                 tableNamesHash[tableNames[i]] = true;
556
557             for (var tableName in database._tableViews) {
558                 if (!(tableName in tableNamesHash)) {
559                     if (self.visibleView === database._tableViews[tableName])
560                         self.closeVisibleView();
561                     delete database._tableViews[tableName];
562                 }
563             }
564         }
565         database.getTableNames(tableNamesCallback);
566     },
567
568     updateDOMStorage: function(storageId)
569     {
570         var domStorage = this._domStorageForId(storageId);
571         if (!domStorage)
572             return;
573
574         var view = domStorage._domStorageView;
575         if (this.visibleView && view === this.visibleView)
576             domStorage._domStorageView.update();
577     },
578
579     _populateApplicationCacheTree: function()
580     {
581         this._applicationCacheModel = new WebInspector.ApplicationCacheModel();
582
583         this._applicationCacheViews = {};
584         this._applicationCacheFrameElements = {};
585         this._applicationCacheManifestElements = {};
586
587         this._applicationCacheModel.addEventListener(WebInspector.ApplicationCacheModel.EventTypes.FrameManifestAdded, this._applicationCacheFrameManifestAdded, this);
588         this._applicationCacheModel.addEventListener(WebInspector.ApplicationCacheModel.EventTypes.FrameManifestRemoved, this._applicationCacheFrameManifestRemoved, this);
589
590         this._applicationCacheModel.addEventListener(WebInspector.ApplicationCacheModel.EventTypes.FrameManifestStatusUpdated, this._applicationCacheFrameManifestStatusChanged, this);
591         this._applicationCacheModel.addEventListener(WebInspector.ApplicationCacheModel.EventTypes.NetworkStateChanged, this._applicationCacheNetworkStateChanged, this);
592     },
593
594     _applicationCacheFrameManifestAdded: function(event)
595     {
596         var frameId = event.data;
597         var manifestURL = this._applicationCacheModel.frameManifestURL(frameId);
598         var status = this._applicationCacheModel.frameManifestStatus(frameId)
599
600         var manifestTreeElement = this._applicationCacheManifestElements[manifestURL]
601         if (!manifestTreeElement) {
602             manifestTreeElement = new WebInspector.ApplicationCacheManifestTreeElement(this, manifestURL);
603             this.applicationCacheListTreeElement.appendChild(manifestTreeElement);
604             this._applicationCacheManifestElements[manifestURL] = manifestTreeElement;
605         }
606
607         var frameTreeElement = new WebInspector.ApplicationCacheFrameTreeElement(this, frameId, manifestURL);
608         manifestTreeElement.appendChild(frameTreeElement);
609         manifestTreeElement.expand();
610         this._applicationCacheFrameElements[frameId] = frameTreeElement;
611     },
612
613     _applicationCacheFrameManifestRemoved: function(event)
614     {
615         var frameId = event.data;
616         var frameTreeElement = this._applicationCacheFrameElements[frameId];
617         if (!frameTreeElement)
618             return;
619
620         var manifestURL = frameTreeElement.manifestURL;
621         delete this._applicationCacheFrameElements[frameId];
622         delete this._applicationCacheViews[frameId];
623         frameTreeElement.parent.removeChild(frameTreeElement);
624
625         var manifestTreeElement = this._applicationCacheManifestElements[manifestURL];
626         if (manifestTreeElement.children.length !== 0)
627             return;
628
629         delete this._applicationCacheManifestElements[manifestURL];
630         manifestTreeElement.parent.removeChild(manifestTreeElement);
631     },
632
633     _applicationCacheFrameManifestStatusChanged: function(event)
634     {
635         var frameId = event.data;
636         var status = this._applicationCacheModel.frameManifestStatus(frameId)
637
638         if (this._applicationCacheViews[frameId])
639             this._applicationCacheViews[frameId].updateStatus(status);
640     },
641
642     _applicationCacheNetworkStateChanged: function(event)
643     {
644         var isNowOnline = event.data;
645
646         for (var manifestURL in this._applicationCacheViews)
647             this._applicationCacheViews[manifestURL].updateNetworkState(isNowOnline);
648     },
649
650     _domStorageForId: function(storageId)
651     {
652         if (!this._domStorage)
653             return null;
654         var domStorageLength = this._domStorage.length;
655         for (var i = 0; i < domStorageLength; ++i) {
656             var domStorage = this._domStorage[i];
657             if (domStorage.id == storageId)
658                 return domStorage;
659         }
660         return null;
661     },
662
663     sidebarResized: function(event)
664     {
665         var width = event.data;
666         this.storageViewStatusBarItemsContainer.style.left = width + "px";
667     },
668
669     performSearch: function(query)
670     {
671         this._resetSearchResults();
672         var regex = WebInspector.SourceFrame.createSearchRegex(query);
673         var totalMatchesCount = 0;
674
675         function searchInEditedResource(treeElement)
676         {
677             var resource = treeElement.representedObject;
678             if (resource.history.length == 0)
679                 return;
680             var matchesCount = countRegexMatches(regex, resource.content)
681             treeElement.searchMatchesFound(matchesCount);
682             totalMatchesCount += matchesCount;
683         }
684
685         function callback(error, result)
686         {
687             if (!error) {
688                 for (var i = 0; i < result.length; i++) {
689                     var searchResult = result[i];
690                     var frameTreeElement = this._treeElementForFrameId[searchResult.frameId];
691                     if (!frameTreeElement)
692                         continue;
693                     var resource = frameTreeElement.resourceByURL(searchResult.url);
694
695                     // FIXME: When the same script is used in several frames and this script contains at least
696                     // one search result then some search results can not be matched with a resource on panel.
697                     // https://bugs.webkit.org/show_bug.cgi?id=66005
698                     if (!resource)
699                         continue;
700
701                     if (resource.history.length > 0)
702                         continue; // Skip edited resources.
703                     this._findTreeElementForResource(resource).searchMatchesFound(searchResult.matchesCount);
704                     totalMatchesCount += searchResult.matchesCount;
705                 }
706             }
707
708             WebInspector.searchController.updateSearchMatchesCount(totalMatchesCount, this);
709             this._searchController = new WebInspector.ResourcesSearchController(this.resourcesListTreeElement, totalMatchesCount);
710
711             if (this.sidebarTree.selectedTreeElement && this.sidebarTree.selectedTreeElement.searchMatchesCount)
712                 this.jumpToNextSearchResult();
713         }
714
715         this._forAllResourceTreeElements(searchInEditedResource.bind(this));
716         PageAgent.searchInResources(regex.source, !regex.ignoreCase, true, callback.bind(this));
717     },
718
719     _ensureViewSearchPerformed: function(callback)
720     {
721         function viewSearchPerformedCallback(searchId)
722         {
723             if (searchId !== this._lastViewSearchId)
724                 return; // Search is obsolete.
725             this._viewSearchInProgress = false;
726             callback();
727         }
728
729         if (!this._viewSearchInProgress) {
730             if (!this.visibleView.hasSearchResults()) {
731                 // We give id to each search, so that we can skip callbacks for obsolete searches.
732                 this._lastViewSearchId = this._lastViewSearchId ? this._lastViewSearchId + 1 : 0;
733                 this._viewSearchInProgress = true;
734                 this.visibleView.performSearch(this.currentQuery, viewSearchPerformedCallback.bind(this, this._lastViewSearchId));
735             } else
736                 callback();
737         }
738     },
739
740     _showSearchResult: function(searchResult)
741     {
742         this._lastSearchResultIndex = searchResult.index;
743         this._lastSearchResultTreeElement = searchResult.treeElement;
744
745         // At first show view for treeElement.
746         if (searchResult.treeElement !== this.sidebarTree.selectedTreeElement) {
747             this.showResource(searchResult.treeElement.representedObject);
748             WebInspector.searchController.focusSearchField();
749         }
750
751         function callback(searchId)
752         {
753             if (this.sidebarTree.selectedTreeElement !== this._lastSearchResultTreeElement)
754                 return; // User has selected another view while we were searching.
755             if (this._lastSearchResultIndex != -1)
756                 this.visibleView.jumpToSearchResult(this._lastSearchResultIndex);
757             WebInspector.searchController.updateCurrentMatchIndex(searchResult.currentMatchIndex - 1, this);
758         }
759
760         // Then run SourceFrame search if needed and jump to search result index when done.
761         this._ensureViewSearchPerformed(callback.bind(this));
762     },
763
764     _resetSearchResults: function()
765     {
766         function callback(resourceTreeElement)
767         {
768             resourceTreeElement._resetSearchResults();
769         }
770
771         this._forAllResourceTreeElements(callback);
772         if (this.visibleView && this.visibleView.searchCanceled)
773             this.visibleView.searchCanceled();
774
775         this._lastSearchResultTreeElement = null;
776         this._lastSearchResultIndex = -1;
777         this._viewSearchInProgress = false;
778     },
779
780     searchCanceled: function()
781     {
782         function callback(resourceTreeElement)
783         {
784             resourceTreeElement._updateErrorsAndWarningsBubbles();
785         }
786
787         WebInspector.searchController.updateSearchMatchesCount(0, this);
788         this._resetSearchResults();
789         this._forAllResourceTreeElements(callback);
790     },
791
792     jumpToNextSearchResult: function()
793     {
794         if (!this.currentSearchMatches)
795             return;
796         var currentTreeElement = this.sidebarTree.selectedTreeElement;
797         var nextSearchResult = this._searchController.nextSearchResult(currentTreeElement);
798         this._showSearchResult(nextSearchResult);
799     },
800
801     jumpToPreviousSearchResult: function()
802     {
803         if (!this.currentSearchMatches)
804             return;
805         var currentTreeElement = this.sidebarTree.selectedTreeElement;
806         var previousSearchResult = this._searchController.previousSearchResult(currentTreeElement);
807         this._showSearchResult(previousSearchResult);
808     },
809
810     _forAllResourceTreeElements: function(callback)
811     {
812         var stop = false;
813         for (var treeElement = this.resourcesListTreeElement; !stop && treeElement; treeElement = treeElement.traverseNextTreeElement(false, this.resourcesListTreeElement, true)) {
814             if (treeElement instanceof WebInspector.FrameResourceTreeElement)
815                 stop = callback(treeElement);
816         }
817     },
818
819     _findTreeElementForResource: function(resource)
820     {
821         function isAncestor(ancestor, object)
822         {
823             // Redirects, XHRs do not belong to the tree, it is fine to silently return false here.
824             return false;
825         }
826
827         function getParent(object)
828         {
829             // Redirects, XHRs do not belong to the tree, it is fine to silently return false here.
830             return null;
831         }
832
833         return this.sidebarTree.findTreeElement(resource, isAncestor, getParent);
834     },
835
836     _findTreeElementForRevision: function(revision)
837     {
838         function isAncestor(ancestor, object)
839         {
840             return false;
841         }
842
843         function getParent(object)
844         {
845             return null;
846         }
847
848         return this.sidebarTree.findTreeElement(revision, isAncestor, getParent);
849     },
850
851     showView: function(view)
852     {
853         if (view)
854             this.showResource(view.resource);
855     },
856
857     _onmousemove: function(event)
858     {
859         var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
860         if (!nodeUnderMouse)
861             return;
862
863         var listNode = nodeUnderMouse.enclosingNodeOrSelfWithNodeName("li");
864         if (!listNode)
865             return;
866
867         var element = listNode.treeElement;
868         if (this._previousHoveredElement === element)
869             return;
870
871         if (this._previousHoveredElement) {
872             this._previousHoveredElement.hovered = false;
873             delete this._previousHoveredElement;
874         }
875
876         if (element instanceof WebInspector.FrameTreeElement) {
877             this._previousHoveredElement = element;
878             element.hovered = true;
879         }
880     },
881
882     _onmouseout: function(event)
883     {
884         if (this._previousHoveredElement) {
885             this._previousHoveredElement.hovered = false;
886             delete this._previousHoveredElement;
887         }
888     }
889 }
890
891 WebInspector.ResourcesPanel.prototype.__proto__ = WebInspector.Panel.prototype;
892
893 /**
894  * @constructor
895  * @extends {TreeElement}
896  * @param {boolean=} hasChildren
897  * @param {boolean=} noIcon
898  */
899 WebInspector.BaseStorageTreeElement = function(storagePanel, representedObject, title, iconClasses, hasChildren, noIcon)
900 {
901     TreeElement.call(this, "", representedObject, hasChildren);
902     this._storagePanel = storagePanel;
903     this._titleText = title;
904     this._iconClasses = iconClasses;
905     this._noIcon = noIcon;
906 }
907
908 WebInspector.BaseStorageTreeElement.prototype = {
909     onattach: function()
910     {
911         this.listItemElement.removeChildren();
912         if (this._iconClasses) {
913             for (var i = 0; i < this._iconClasses.length; ++i)
914                 this.listItemElement.addStyleClass(this._iconClasses[i]);
915         }
916
917         var selectionElement = document.createElement("div");
918         selectionElement.className = "selection";
919         this.listItemElement.appendChild(selectionElement);
920
921         if (!this._noIcon) {
922             this.imageElement = document.createElement("img");
923             this.imageElement.className = "icon";
924             this.listItemElement.appendChild(this.imageElement);
925         }
926
927         this.titleElement = document.createElement("div");
928         this.titleElement.className = "base-storage-tree-element-title";
929         this._titleTextNode = document.createTextNode("");
930         this.titleElement.appendChild(this._titleTextNode);
931         this._updateTitle();
932         this._updateSubtitle();
933         this.listItemElement.appendChild(this.titleElement);
934     },
935
936     get displayName()
937     {
938         return this._displayName;
939     },
940
941     _updateDisplayName: function()
942     {
943         this._displayName = this._titleText || "";
944         if (this._subtitleText)
945             this._displayName += " (" + this._subtitleText + ")";
946     },
947
948     _updateTitle: function()
949     {
950         this._updateDisplayName();
951
952         if (!this.titleElement)
953             return;
954
955         this._titleTextNode.textContent = this._titleText || "";
956     },
957
958     _updateSubtitle: function()
959     {
960         this._updateDisplayName();
961
962         if (!this.titleElement)
963             return;
964
965         if (this._subtitleText) {
966             if (!this._subtitleElement) {
967                 this._subtitleElement = document.createElement("span");
968                 this._subtitleElement.className = "base-storage-tree-element-subtitle";
969                 this.titleElement.appendChild(this._subtitleElement);
970             }
971             this._subtitleElement.textContent = "(" + this._subtitleText + ")";
972         } else if (this._subtitleElement) {
973             this.titleElement.removeChild(this._subtitleElement);
974             delete this._subtitleElement;
975         }
976     },
977
978     onselect: function()
979     {
980         var itemURL = this.itemURL;
981         if (itemURL)
982             WebInspector.settings.resourcesLastSelectedItem.set(itemURL);
983     },
984
985     onreveal: function()
986     {
987         if (this.listItemElement)
988             this.listItemElement.scrollIntoViewIfNeeded(false);
989     },
990
991     get titleText()
992     {
993         return this._titleText;
994     },
995
996     set titleText(titleText)
997     {
998         this._titleText = titleText;
999         this._updateTitle();
1000     },
1001
1002     get subtitleText()
1003     {
1004         return this._subtitleText;
1005     },
1006
1007     set subtitleText(subtitleText)
1008     {
1009         this._subtitleText = subtitleText;
1010         this._updateSubtitle();
1011     },
1012
1013     get searchMatchesCount()
1014     {
1015         return 0;
1016     }
1017 }
1018
1019 WebInspector.BaseStorageTreeElement.prototype.__proto__ = TreeElement.prototype;
1020
1021 /**
1022  * @constructor
1023  * @extends {WebInspector.BaseStorageTreeElement}
1024  * @param {boolean=} noIcon
1025  */
1026 WebInspector.StorageCategoryTreeElement = function(storagePanel, categoryName, settingsKey, iconClasses, noIcon)
1027 {
1028     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, categoryName, iconClasses, true, noIcon);
1029     this._expandedSettingKey = "resources" + settingsKey + "Expanded";
1030     WebInspector.settings[this._expandedSettingKey] = WebInspector.settings.createSetting(this._expandedSettingKey, settingsKey === "Frames");
1031     this._categoryName = categoryName;
1032 }
1033
1034 WebInspector.StorageCategoryTreeElement.prototype = {
1035     get itemURL()
1036     {
1037         return "category://" + this._categoryName;
1038     },
1039
1040     onselect: function()
1041     {
1042         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1043         this._storagePanel.showCategoryView(this._categoryName);
1044     },
1045
1046     onattach: function()
1047     {
1048         WebInspector.BaseStorageTreeElement.prototype.onattach.call(this);
1049         if (WebInspector.settings[this._expandedSettingKey].get())
1050             this.expand();
1051     },
1052
1053     onexpand: function()
1054     {
1055         WebInspector.settings[this._expandedSettingKey].set(true);
1056     },
1057
1058     oncollapse: function()
1059     {
1060         WebInspector.settings[this._expandedSettingKey].set(false);
1061     }
1062 }
1063
1064 WebInspector.StorageCategoryTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1065
1066 /**
1067  * @constructor
1068  * @extends {WebInspector.BaseStorageTreeElement}
1069  */
1070 WebInspector.FrameTreeElement = function(storagePanel, frame)
1071 {
1072     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, "", ["frame-storage-tree-item"]);
1073     this._frame = frame;
1074     this.frameNavigated(frame);
1075 }
1076
1077 WebInspector.FrameTreeElement.prototype = {
1078     frameNavigated: function(frame)
1079     {
1080         this.removeChildren();
1081         this._frameId = frame.id;
1082
1083         this.titleText = frame.name;
1084         this.subtitleText = new WebInspector.ParsedURL(frame.url).displayName;
1085
1086         this._categoryElements = {};
1087         this._treeElementForResource = {};
1088
1089         this._storagePanel.addDocumentURL(frame.url);
1090     },
1091
1092     get itemURL()
1093     {
1094         return "frame://" + encodeURI(this.displayName);
1095     },
1096
1097     onselect: function()
1098     {
1099         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1100         this._storagePanel.showCategoryView(this.displayName);
1101
1102         this.listItemElement.removeStyleClass("hovered");
1103         DOMAgent.hideHighlight();
1104     },
1105
1106     set hovered(hovered)
1107     {
1108         if (hovered) {
1109             this.listItemElement.addStyleClass("hovered");
1110             DOMAgent.highlightFrame(this._frameId, WebInspector.Color.PageHighlight.Content.toProtocolRGBA(), WebInspector.Color.PageHighlight.ContentOutline.toProtocolRGBA());
1111         } else {
1112             this.listItemElement.removeStyleClass("hovered");
1113             DOMAgent.hideHighlight();
1114         }
1115     },
1116
1117     appendResource: function(resource)
1118     {
1119         var categoryName = resource.type.name();
1120         var categoryElement = resource.type === WebInspector.resourceTypes.Document ? this : this._categoryElements[categoryName];
1121         if (!categoryElement) {
1122             categoryElement = new WebInspector.StorageCategoryTreeElement(this._storagePanel, resource.type.categoryTitle(), categoryName, null, true);
1123             this._categoryElements[resource.type.name()] = categoryElement;
1124             this._insertInPresentationOrder(this, categoryElement);
1125         }
1126         var resourceTreeElement = new WebInspector.FrameResourceTreeElement(this._storagePanel, resource);
1127         this._insertInPresentationOrder(categoryElement, resourceTreeElement);
1128         resourceTreeElement._populateRevisions();
1129
1130         this._treeElementForResource[resource.url] = resourceTreeElement;
1131     },
1132
1133     resourceByURL: function(url)
1134     {
1135         var treeElement = this._treeElementForResource[url];
1136         return treeElement ? treeElement.representedObject : null;
1137     },
1138
1139     appendChild: function(treeElement)
1140     {
1141         this._insertInPresentationOrder(this, treeElement);
1142     },
1143
1144     _insertInPresentationOrder: function(parentTreeElement, childTreeElement)
1145     {
1146         // Insert in the alphabetical order, first frames, then resources. Document resource goes last.
1147         function typeWeight(treeElement)
1148         {
1149             if (treeElement instanceof WebInspector.StorageCategoryTreeElement)
1150                 return 2;
1151             if (treeElement instanceof WebInspector.FrameTreeElement)
1152                 return 1;
1153             return 3;
1154         }
1155
1156         function compare(treeElement1, treeElement2)
1157         {
1158             var typeWeight1 = typeWeight(treeElement1);
1159             var typeWeight2 = typeWeight(treeElement2);
1160
1161             var result;
1162             if (typeWeight1 > typeWeight2)
1163                 result = 1;
1164             else if (typeWeight1 < typeWeight2)
1165                 result = -1;
1166             else {
1167                 var title1 = treeElement1.displayName || treeElement1.titleText;
1168                 var title2 = treeElement2.displayName || treeElement2.titleText;
1169                 result = title1.localeCompare(title2);
1170             }
1171             return result;
1172         }
1173
1174         var children = parentTreeElement.children;
1175         var i;
1176         for (i = 0; i < children.length; ++i) {
1177             if (compare(childTreeElement, children[i]) < 0)
1178                 break;
1179         }
1180         parentTreeElement.insertChild(childTreeElement, i);
1181     }
1182 }
1183
1184 WebInspector.FrameTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1185
1186 /**
1187  * @constructor
1188  * @extends {WebInspector.BaseStorageTreeElement}
1189  */
1190 WebInspector.FrameResourceTreeElement = function(storagePanel, resource)
1191 {
1192     WebInspector.BaseStorageTreeElement.call(this, storagePanel, resource, resource.displayName, ["resource-sidebar-tree-item", "resources-type-" + resource.type.name()]);
1193     this._resource = resource;
1194     this._resource.addEventListener(WebInspector.Resource.Events.MessageAdded, this._consoleMessageAdded, this);
1195     this._resource.addEventListener(WebInspector.Resource.Events.MessagesCleared, this._consoleMessagesCleared, this);
1196     this._resource.addEventListener(WebInspector.Resource.Events.RevisionAdded, this._revisionAdded, this);
1197     this.tooltip = resource.url;
1198 }
1199
1200 WebInspector.FrameResourceTreeElement.prototype = {
1201     get itemURL()
1202     {
1203         return this._resource.url;
1204     },
1205
1206     onselect: function()
1207     {
1208         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1209         this._storagePanel._showResourceView(this._resource);
1210     },
1211
1212     ondblclick: function(event)
1213     {
1214         InspectorFrontendHost.openInNewTab(this._resource.url);
1215     },
1216
1217     onattach: function()
1218     {
1219         WebInspector.BaseStorageTreeElement.prototype.onattach.call(this);
1220
1221         if (this._resource.type === WebInspector.resourceTypes.Image) {
1222             var previewImage = document.createElement("img");
1223             previewImage.className = "image-resource-icon-preview";
1224             this._resource.populateImageSource(previewImage);
1225
1226             var iconElement = document.createElement("div");
1227             iconElement.className = "icon";
1228             iconElement.appendChild(previewImage);
1229             this.listItemElement.replaceChild(iconElement, this.imageElement);
1230         }
1231
1232         this._statusElement = document.createElement("div");
1233         this._statusElement.className = "status";
1234         this.listItemElement.insertBefore(this._statusElement, this.titleElement);
1235
1236         this.listItemElement.draggable = true;
1237         this.listItemElement.addEventListener("dragstart", this._ondragstart.bind(this), false);
1238         this.listItemElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true);
1239
1240         this._updateErrorsAndWarningsBubbles();
1241     },
1242
1243     _ondragstart: function(event)
1244     {
1245         event.dataTransfer.setData("text/plain", this._resource.content);
1246         event.dataTransfer.effectAllowed = "copy";
1247         return true;
1248     },
1249
1250     _handleContextMenuEvent: function(event)
1251     {
1252         var contextMenu = new WebInspector.ContextMenu();
1253         contextMenu.appendItem(WebInspector.openLinkExternallyLabel(), WebInspector.openResource.bind(WebInspector, this._resource.url, false));
1254         contextMenu.appendItem(WebInspector.copyLinkAddressLabel(), InspectorFrontendHost.copyText.bind(InspectorFrontendHost, this._resource.url));
1255         this._appendOpenInNetworkPanelAction(contextMenu, event);
1256         WebInspector.populateResourceContextMenu(contextMenu, this._resource.url, null);
1257         this._appendSaveAsAction(contextMenu, event);
1258         contextMenu.show(event);
1259     },
1260
1261     _appendOpenInNetworkPanelAction: function(contextMenu, event)
1262     {
1263         if (!this._resource.request)
1264             return;
1265
1266         contextMenu.appendItem(WebInspector.openInNetworkPanelLabel(), WebInspector.openRequestInNetworkPanel.bind(WebInspector, this._resource.request));
1267     },
1268
1269     _appendSaveAsAction: function(contextMenu, event)
1270     {
1271         if (!InspectorFrontendHost.canSave())
1272             return;
1273
1274         if (this._resource.type !== WebInspector.resourceTypes.Document &&
1275             this._resource.type !== WebInspector.resourceTypes.Stylesheet &&
1276             this._resource.type !== WebInspector.resourceTypes.Script)
1277             return;
1278
1279         function doSave(forceSaveAs, content)
1280         {
1281             WebInspector.fileManager.save(this._resource.url, content, forceSaveAs);
1282         }
1283
1284         function save(forceSaveAs)
1285         {
1286             this._resource.requestContent(doSave.bind(this, forceSaveAs));
1287         }
1288
1289         contextMenu.appendSeparator();
1290         contextMenu.appendItem(WebInspector.UIString("Save"), save.bind(this, false));
1291         contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Save as..." : "Save As..."), save.bind(this, true));
1292     },
1293
1294     _setBubbleText: function(x)
1295     {
1296         if (!this._bubbleElement) {
1297             this._bubbleElement = document.createElement("div");
1298             this._bubbleElement.className = "bubble";
1299             this._statusElement.appendChild(this._bubbleElement);
1300         }
1301
1302         this._bubbleElement.textContent = x;
1303     },
1304
1305     _resetBubble: function()
1306     {
1307         if (this._bubbleElement) {
1308             this._bubbleElement.textContent = "";
1309             this._bubbleElement.removeStyleClass("search-matches");
1310             this._bubbleElement.removeStyleClass("warning");
1311             this._bubbleElement.removeStyleClass("error");
1312         }
1313     },
1314
1315     _resetSearchResults: function()
1316     {
1317         this._resetBubble();
1318         this._searchMatchesCount = 0;
1319     },
1320
1321     get searchMatchesCount()
1322     {
1323         return this._searchMatchesCount;
1324     },
1325
1326     searchMatchesFound: function(matchesCount)
1327     {
1328         this._resetSearchResults();
1329
1330         this._searchMatchesCount = matchesCount;
1331         this._setBubbleText(matchesCount);
1332         this._bubbleElement.addStyleClass("search-matches");
1333
1334         // Expand, do not scroll into view.
1335         var currentAncestor = this.parent;
1336         while (currentAncestor && !currentAncestor.root) {
1337             if (!currentAncestor.expanded)
1338                 currentAncestor.expand();
1339             currentAncestor = currentAncestor.parent;
1340         }
1341     },
1342
1343     _updateErrorsAndWarningsBubbles: function()
1344     {
1345         if (this._storagePanel.currentQuery)
1346             return;
1347
1348         this._resetBubble();
1349
1350         if (this._resource.warnings || this._resource.errors)
1351             this._setBubbleText(this._resource.warnings + this._resource.errors);
1352
1353         if (this._resource.warnings)
1354             this._bubbleElement.addStyleClass("warning");
1355
1356         if (this._resource.errors)
1357             this._bubbleElement.addStyleClass("error");
1358     },
1359
1360     _consoleMessagesCleared: function()
1361     {
1362         // FIXME: move to the SourceFrame.
1363         if (this._sourceView)
1364             this._sourceView.clearMessages();
1365
1366         this._updateErrorsAndWarningsBubbles();
1367     },
1368
1369     _consoleMessageAdded: function(event)
1370     {
1371         var msg = event.data;
1372         if (this._sourceView)
1373             this._sourceView.addMessage(msg);
1374         this._updateErrorsAndWarningsBubbles();
1375     },
1376
1377     _populateRevisions: function()
1378     {
1379         for (var i = 0; i < this._resource.history.length; ++i)
1380             this._appendRevision(this._resource.history[i]);
1381     },
1382
1383     _revisionAdded: function(event)
1384     {
1385         this._appendRevision(event.data);
1386     },
1387
1388     _appendRevision: function(revision)
1389     {
1390         this.subtitleText = "";
1391         this.insertChild(new WebInspector.ResourceRevisionTreeElement(this._storagePanel, revision), 0);
1392         if (this._sourceView === this._storagePanel.visibleView)
1393             this._storagePanel._showResourceView(this._resource);
1394     },
1395
1396     sourceView: function()
1397     {
1398         if (!this._sourceView) {
1399             this._sourceView = new WebInspector.EditableResourceSourceFrame(this._resource);
1400             if (this._resource.messages) {
1401                 for (var i = 0; i < this._resource.messages.length; i++)
1402                     this._sourceView.addMessage(this._resource.messages[i]);
1403             }
1404             this._sourceView.addEventListener(WebInspector.EditableResourceSourceFrame.Events.TextEdited, this._sourceViewTextEdited, this);
1405         }
1406         return this._sourceView;
1407     },
1408
1409     _sourceViewTextEdited: function(event)
1410     {
1411         var sourceFrame = event.data;
1412         this.subtitleText = sourceFrame.isDirty() ? "*" : "";
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     onexpand: function()
1441     {
1442         this._updateChildren();
1443     },
1444
1445     _updateChildren: function()
1446     {
1447         this.removeChildren();
1448
1449         function tableNamesCallback(tableNames)
1450         {
1451             var tableNamesLength = tableNames.length;
1452             for (var i = 0; i < tableNamesLength; ++i)
1453                 this.appendChild(new WebInspector.DatabaseTableTreeElement(this._storagePanel, this._database, tableNames[i]));
1454         }
1455         this._database.getTableNames(tableNamesCallback.bind(this));
1456     }
1457 }
1458
1459 WebInspector.DatabaseTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1460
1461 /**
1462  * @constructor
1463  * @extends {WebInspector.BaseStorageTreeElement}
1464  */
1465 WebInspector.DatabaseTableTreeElement = function(storagePanel, database, tableName)
1466 {
1467     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, tableName, ["database-storage-tree-item"]);
1468     this._database = database;
1469     this._tableName = tableName;
1470 }
1471
1472 WebInspector.DatabaseTableTreeElement.prototype = {
1473     get itemURL()
1474     {
1475         return "database://" + encodeURI(this._database.name) + "/" + encodeURI(this._tableName);
1476     },
1477
1478     onselect: function()
1479     {
1480         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1481         this._storagePanel.showDatabase(this._database, this._tableName);
1482     }
1483 }
1484 WebInspector.DatabaseTableTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1485
1486 /**
1487  * @constructor
1488  * @extends {WebInspector.StorageCategoryTreeElement}
1489  * @param {WebInspector.ResourcesPanel} storagePanel
1490  */
1491 WebInspector.IndexedDBTreeElement = function(storagePanel)
1492 {
1493     WebInspector.StorageCategoryTreeElement.call(this, storagePanel, WebInspector.UIString("IndexedDB"), "IndexedDB", ["indexed-db-storage-tree-item"]);
1494 }
1495
1496 WebInspector.IndexedDBTreeElement.prototype = {
1497     onexpand: function()
1498     {
1499         WebInspector.StorageCategoryTreeElement.prototype.onexpand.call(this);
1500         if (!this._indexedDBModel)
1501             this._createIndexedDBModel();
1502     },
1503
1504     onattach: function()
1505     {
1506         WebInspector.StorageCategoryTreeElement.prototype.onattach.call(this);
1507         this.listItemElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true);
1508     },
1509
1510     _handleContextMenuEvent: function(event)
1511     {
1512         var contextMenu = new WebInspector.ContextMenu();
1513         contextMenu.appendItem(WebInspector.UIString("Refresh IndexedDB"), this.refreshIndexedDB.bind(this));
1514         contextMenu.show(event);
1515     },
1516
1517     _createIndexedDBModel: function()
1518     {
1519         this._indexedDBModel = new WebInspector.IndexedDBModel();
1520         this._idbDatabaseTreeElements = [];
1521         this._indexedDBModel.addEventListener(WebInspector.IndexedDBModel.EventTypes.DatabaseAdded, this._indexedDBAdded, this);
1522         this._indexedDBModel.addEventListener(WebInspector.IndexedDBModel.EventTypes.DatabaseRemoved, this._indexedDBRemoved, this);
1523         this._indexedDBModel.addEventListener(WebInspector.IndexedDBModel.EventTypes.DatabaseLoaded, this._indexedDBLoaded, this);
1524     },
1525
1526     refreshIndexedDB: function()
1527     {
1528         if (!this._indexedDBModel) {
1529             this._createIndexedDBModel();
1530             return;
1531         }
1532
1533         this._indexedDBModel.refreshDatabaseNames();
1534     },
1535
1536     /**
1537      * @param {WebInspector.Event} event
1538      */
1539     _indexedDBAdded: function(event)
1540     {
1541         var databaseId = /** @type {WebInspector.IndexedDBModel.DatabaseId} */ event.data;
1542
1543         var idbDatabaseTreeElement = new WebInspector.IDBDatabaseTreeElement(this._storagePanel, this._indexedDBModel, databaseId);
1544         this._idbDatabaseTreeElements.push(idbDatabaseTreeElement);
1545         this.appendChild(idbDatabaseTreeElement);
1546
1547         this._indexedDBModel.refreshDatabase(databaseId);
1548     },
1549
1550     /**
1551      * @param {WebInspector.Event} event
1552      */
1553     _indexedDBRemoved: function(event)
1554     {
1555         var databaseId = /** @type {WebInspector.IndexedDBModel.DatabaseId} */ event.data;
1556
1557         var idbDatabaseTreeElement = this._idbDatabaseTreeElement(databaseId)
1558         if (!idbDatabaseTreeElement)
1559             return;
1560
1561         idbDatabaseTreeElement.clear();
1562         this.removeChild(idbDatabaseTreeElement);
1563         this._idbDatabaseTreeElements.remove(idbDatabaseTreeElement);
1564     },
1565
1566     /**
1567      * @param {WebInspector.Event} event
1568      */
1569     _indexedDBLoaded: function(event)
1570     {
1571         var database = /** @type {WebInspector.IndexedDBModel.Database} */ event.data;
1572
1573         var idbDatabaseTreeElement = this._idbDatabaseTreeElement(database.databaseId)
1574         if (!idbDatabaseTreeElement)
1575             return;
1576
1577         idbDatabaseTreeElement.update(database);
1578     },
1579
1580     /**
1581      * @param {WebInspector.IndexedDBModel.DatabaseId} databaseId
1582      * @return {WebInspector.IDBDatabaseTreeElement}
1583      */
1584     _idbDatabaseTreeElement: function(databaseId)
1585     {
1586         var index = -1;
1587         for (var i = 0; i < this._idbDatabaseTreeElements.length; ++i) {
1588             if (this._idbDatabaseTreeElements[i]._databaseId.equals(databaseId)) {
1589                 index = i;
1590                 break;
1591             }
1592         }
1593         if (index !== -1)
1594             return this._idbDatabaseTreeElements[i];
1595         return null;
1596     }
1597 }
1598
1599 WebInspector.IndexedDBTreeElement.prototype.__proto__ = WebInspector.StorageCategoryTreeElement.prototype;
1600
1601 /**
1602  * @constructor
1603  * @extends {WebInspector.BaseStorageTreeElement}
1604  * @param {WebInspector.ResourcesPanel} storagePanel
1605  * @param {WebInspector.IndexedDBModel} model
1606  * @param {WebInspector.IndexedDBModel.DatabaseId} databaseId
1607  */
1608 WebInspector.IDBDatabaseTreeElement = function(storagePanel, model, databaseId)
1609 {
1610     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, databaseId.name + " - " + databaseId.securityOrigin, ["indexed-db-storage-tree-item"]);
1611     this._model = model;
1612     this._databaseId = databaseId;
1613     this._idbObjectStoreTreeElements = {};
1614 }
1615
1616 WebInspector.IDBDatabaseTreeElement.prototype = {
1617     get itemURL()
1618     {
1619         return "indexedDB://" + this._databaseId.securityOrigin + "/" + this._databaseId.name;
1620     },
1621
1622     onattach: function()
1623     {
1624         WebInspector.BaseStorageTreeElement.prototype.onattach.call(this);
1625         this.listItemElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true);
1626     },
1627
1628     _handleContextMenuEvent: function(event)
1629     {
1630         var contextMenu = new WebInspector.ContextMenu();
1631         contextMenu.appendItem(WebInspector.UIString("Refresh IndexedDB"), this._refreshIndexedDB.bind(this));
1632         contextMenu.show(event);
1633     },
1634
1635     _refreshIndexedDB: function(event)
1636     {
1637         this._model.refreshDatabaseNames();
1638     },
1639
1640     /**
1641      * @param {WebInspector.IndexedDBModel.Database} database
1642      */
1643     update: function(database)
1644     {
1645         this._database = database;
1646         var objectStoreNames = {};
1647         for (var objectStoreName in this._database.objectStores) {
1648             var objectStore = this._database.objectStores[objectStoreName];
1649             objectStoreNames[objectStore.name] = true;
1650             if (!this._idbObjectStoreTreeElements[objectStore.name]) {
1651                 var idbObjectStoreTreeElement = new WebInspector.IDBObjectStoreTreeElement(this._storagePanel, this._model, this._databaseId, objectStore);
1652                 this._idbObjectStoreTreeElements[objectStore.name] = idbObjectStoreTreeElement;
1653                 this.appendChild(idbObjectStoreTreeElement);
1654             }
1655             this._idbObjectStoreTreeElements[objectStore.name].update(objectStore);
1656         }
1657         for (var objectStoreName in this._idbObjectStoreTreeElements) {
1658             if (!objectStoreNames[objectStoreName])
1659                 this._objectStoreRemoved(objectStoreName);
1660         }
1661
1662         if (this.children.length) {
1663             this.hasChildren = true;
1664             this.expand();
1665         }
1666
1667         if (this._view)
1668             this._view.update(database);
1669         
1670         this._updateTooltip();
1671     },
1672
1673     _updateTooltip: function()
1674     {
1675         this.tooltip = WebInspector.UIString("Version") + ": " + this._database.version;
1676     },
1677
1678     onselect: function()
1679     {
1680         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1681         if (!this._view)
1682             this._view = new WebInspector.IDBDatabaseView(this._database);
1683
1684         this._storagePanel.showIndexedDB(this._view);
1685     },
1686
1687     /**
1688      * @param {string} objectStoreName
1689      */
1690     _objectStoreRemoved: function(objectStoreName)
1691     {
1692         var objectStoreTreeElement = this._idbObjectStoreTreeElements[objectStoreName];
1693         objectStoreTreeElement.clear();
1694         this.removeChild(objectStoreTreeElement);
1695         delete this._idbObjectStoreTreeElements[objectStoreName];
1696     },
1697
1698     clear: function()
1699     {
1700         for (var objectStoreName in this._idbObjectStoreTreeElements)
1701             this._objectStoreRemoved(objectStoreName);
1702     }
1703 }
1704
1705 WebInspector.IDBDatabaseTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1706
1707 /**
1708  * @constructor
1709  * @extends {WebInspector.BaseStorageTreeElement}
1710  * @param {WebInspector.ResourcesPanel} storagePanel
1711  * @param {WebInspector.IndexedDBModel} model
1712  * @param {WebInspector.IndexedDBModel.DatabaseId} databaseId
1713  * @param {WebInspector.IndexedDBModel.ObjectStore} objectStore
1714  */
1715 WebInspector.IDBObjectStoreTreeElement = function(storagePanel, model, databaseId, objectStore)
1716 {
1717     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, objectStore.name, ["indexed-db-object-store-storage-tree-item"]);
1718     this._model = model;
1719     this._databaseId = databaseId;
1720     this._idbIndexTreeElements = {};
1721 }
1722
1723 WebInspector.IDBObjectStoreTreeElement.prototype = {
1724     get itemURL()
1725     {
1726         return "indexedDB://" + this._databaseId.securityOrigin + "/" + this._databaseId.name + "/" + this._objectStore.name;
1727     },
1728
1729    /**
1730      * @param {WebInspector.IndexedDBModel.ObjectStore} objectStore
1731      */
1732     update: function(objectStore)
1733     {
1734         this._objectStore = objectStore;
1735
1736         var indexNames = {};
1737         for (var indexName in this._objectStore.indexes) {
1738             var index = this._objectStore.indexes[indexName];
1739             indexNames[index.name] = true;
1740             if (!this._idbIndexTreeElements[index.name]) {
1741                 var idbIndexTreeElement = new WebInspector.IDBIndexTreeElement(this._storagePanel, this._model, this._databaseId, this._objectStore, index);
1742                 this._idbIndexTreeElements[index.name] = idbIndexTreeElement;
1743                 this.appendChild(idbIndexTreeElement);
1744             }
1745             this._idbIndexTreeElements[index.name].update(index);
1746         }
1747         for (var indexName in this._idbIndexTreeElements) {
1748             if (!indexNames[indexName])
1749                 this._indexRemoved(indexName);
1750         }
1751         for (var indexName in this._idbIndexTreeElements) {
1752             if (!indexNames[indexName]) {
1753                 this.removeChild(this._idbIndexTreeElements[indexName]);
1754                 delete this._idbIndexTreeElements[indexName];
1755             }
1756         }
1757
1758         if (this.children.length) {
1759             this.hasChildren = true;
1760             this.expand();
1761         }
1762
1763         if (this._view)
1764             this._view.update(this._objectStore);
1765         
1766         this._updateTooltip();
1767     },
1768
1769     _updateTooltip: function()
1770     {
1771         this.tooltip = this._objectStore.keyPath ? (WebInspector.UIString("Key path") + ": " + this._objectStore.keyPath) : "";
1772     },
1773
1774     onselect: function()
1775     {
1776         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1777         if (!this._view)
1778             this._view = new WebInspector.IDBDataView(this._model, this._databaseId, this._objectStore, null);
1779
1780         this._storagePanel.showIndexedDB(this._view);
1781     },
1782
1783     /**
1784      * @param {string} indexName
1785      */
1786     _indexRemoved: function(indexName)
1787     {
1788         var indexTreeElement = this._idbIndexTreeElements[indexName];
1789         indexTreeElement.clear();
1790         this.removeChild(indexTreeElement);
1791         delete this._idbIndexTreeElements[indexName];
1792     },
1793
1794     clear: function()
1795     {
1796         for (var indexName in this._idbIndexTreeElements)
1797             this._indexRemoved(indexName);
1798         if (this._view)
1799             this._view.clear();
1800     }
1801 }
1802
1803 WebInspector.IDBObjectStoreTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1804
1805 /**
1806  * @constructor
1807  * @extends {WebInspector.BaseStorageTreeElement}
1808  * @param {WebInspector.ResourcesPanel} storagePanel
1809  * @param {WebInspector.IndexedDBModel} model
1810  * @param {WebInspector.IndexedDBModel.DatabaseId} databaseId
1811  * @param {WebInspector.IndexedDBModel.ObjectStore} objectStore
1812  * @param {WebInspector.IndexedDBModel.Index} index
1813  */
1814 WebInspector.IDBIndexTreeElement = function(storagePanel, model, databaseId, objectStore, index)
1815 {
1816     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, index.name, ["indexed-db-index-storage-tree-item"]);
1817     this._model = model;
1818     this._databaseId = databaseId;
1819     this._objectStore = objectStore;
1820     this._index = index;
1821 }
1822
1823 WebInspector.IDBIndexTreeElement.prototype = {
1824     get itemURL()
1825     {
1826         return "indexedDB://" + this._databaseId.securityOrigin + "/" + this._databaseId.name + "/" + this._objectStore.name + "/" + this._index.name;
1827     },
1828
1829     /**
1830      * @param {WebInspector.IndexedDBModel.Index} index
1831      */
1832     update: function(index)
1833     {
1834         this._index = index;
1835
1836         if (this._view)
1837             this._view.update(this._index);
1838         
1839         this._updateTooltip();
1840     },
1841
1842     _updateTooltip: function()
1843     {
1844         var tooltipLines = [];
1845         tooltipLines.push(WebInspector.UIString("Key path") + ": " + this._index.keyPath);
1846         if (this._index.unique)
1847             tooltipLines.push(WebInspector.UIString("unique"));
1848         if (this._index.multiEntry)
1849             tooltipLines.push(WebInspector.UIString("multiEntry"));
1850         this.tooltip = tooltipLines.join("\n");
1851     },
1852
1853     onselect: function()
1854     {
1855         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1856         if (!this._view)
1857             this._view = new WebInspector.IDBDataView(this._model, this._databaseId, this._objectStore, this._index);
1858
1859         this._storagePanel.showIndexedDB(this._view);
1860     },
1861
1862     clear: function()
1863     {
1864         if (this._view)
1865             this._view.clear();
1866     }
1867 }
1868
1869 WebInspector.IDBIndexTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1870
1871 /**
1872  * @constructor
1873  * @extends {WebInspector.BaseStorageTreeElement}
1874  */
1875 WebInspector.DOMStorageTreeElement = function(storagePanel, domStorage, className)
1876 {
1877     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, domStorage.domain ? domStorage.domain : WebInspector.UIString("Local Files"), ["domstorage-storage-tree-item", className]);
1878     this._domStorage = domStorage;
1879 }
1880
1881 WebInspector.DOMStorageTreeElement.prototype = {
1882     get itemURL()
1883     {
1884         return "storage://" + this._domStorage.domain + "/" + (this._domStorage.isLocalStorage ? "local" : "session");
1885     },
1886
1887     onselect: function()
1888     {
1889         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1890         this._storagePanel.showDOMStorage(this._domStorage);
1891     }
1892 }
1893 WebInspector.DOMStorageTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1894
1895 /**
1896  * @constructor
1897  * @extends {WebInspector.BaseStorageTreeElement}
1898  */
1899 WebInspector.CookieTreeElement = function(storagePanel, cookieDomain)
1900 {
1901     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, cookieDomain ? cookieDomain : WebInspector.UIString("Local Files"), ["cookie-storage-tree-item"]);
1902     this._cookieDomain = cookieDomain;
1903 }
1904
1905 WebInspector.CookieTreeElement.prototype = {
1906     get itemURL()
1907     {
1908         return "cookies://" + this._cookieDomain;
1909     },
1910
1911     onselect: function()
1912     {
1913         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1914         this._storagePanel.showCookies(this, this._cookieDomain);
1915     }
1916 }
1917 WebInspector.CookieTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1918
1919 /**
1920  * @constructor
1921  * @extends {WebInspector.BaseStorageTreeElement}
1922  */
1923 WebInspector.ApplicationCacheManifestTreeElement = function(storagePanel, manifestURL)
1924 {
1925     var title = new WebInspector.ParsedURL(manifestURL).displayName;
1926     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, title, ["application-cache-storage-tree-item"]);
1927     this.tooltip = manifestURL;
1928     this._manifestURL = manifestURL;
1929 }
1930
1931 WebInspector.ApplicationCacheManifestTreeElement.prototype = {
1932     get itemURL()
1933     {
1934         return "appcache://" + this._manifestURL;
1935     },
1936
1937     get manifestURL()
1938     {
1939         return this._manifestURL;
1940     },
1941
1942     onselect: function()
1943     {
1944         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1945         this._storagePanel.showCategoryView(this._manifestURL);
1946     }
1947 }
1948 WebInspector.ApplicationCacheManifestTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1949
1950 /**
1951  * @constructor
1952  * @extends {WebInspector.BaseStorageTreeElement}
1953  */
1954 WebInspector.ApplicationCacheFrameTreeElement = function(storagePanel, frameId, manifestURL)
1955 {
1956     WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, "", ["frame-storage-tree-item"]);
1957     this._frameId = frameId;
1958     this._manifestURL = manifestURL;
1959     this._refreshTitles();
1960 }
1961
1962 WebInspector.ApplicationCacheFrameTreeElement.prototype = {
1963     get itemURL()
1964     {
1965         return "appcache://" + this._manifestURL + "/" + encodeURI(this.displayName);
1966     },
1967
1968     get frameId()
1969     {
1970         return this._frameId;
1971     },
1972
1973     get manifestURL()
1974     {
1975         return this._manifestURL;
1976     },
1977
1978     _refreshTitles: function()
1979     {
1980         var frame = WebInspector.resourceTreeModel.frameForId(this._frameId);
1981         if (!frame) {
1982             this.subtitleText = WebInspector.UIString("new frame");
1983             return;
1984         }
1985         this.titleText = frame.name;
1986         this.subtitleText = new WebInspector.ParsedURL(frame.url).displayName;
1987     },
1988
1989     frameNavigated: function()
1990     {
1991         this._refreshTitles();
1992     },
1993
1994     onselect: function()
1995     {
1996         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1997         this._storagePanel.showApplicationCache(this._frameId);
1998     }
1999 }
2000 WebInspector.ApplicationCacheFrameTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
2001
2002 /**
2003  * @constructor
2004  * @extends {WebInspector.BaseStorageTreeElement}
2005  */
2006 WebInspector.ResourceRevisionTreeElement = function(storagePanel, revision)
2007 {
2008     var title = revision.timestamp ? revision.timestamp.toLocaleTimeString() : WebInspector.UIString("(original)");
2009     WebInspector.BaseStorageTreeElement.call(this, storagePanel, revision, title, ["resource-sidebar-tree-item", "resources-type-" + revision.resource.type.name()]);
2010     if (revision.timestamp)
2011         this.tooltip = revision.timestamp.toLocaleString();
2012     this._revision = revision;
2013 }
2014
2015 WebInspector.ResourceRevisionTreeElement.prototype = {
2016     get itemURL()
2017     {
2018         return this._revision.resource.url;
2019     },
2020
2021     onattach: function()
2022     {
2023         WebInspector.BaseStorageTreeElement.prototype.onattach.call(this);
2024         this.listItemElement.draggable = true;
2025         this.listItemElement.addEventListener("dragstart", this._ondragstart.bind(this), false);
2026         this.listItemElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true);
2027     },
2028
2029     onselect: function()
2030     {
2031         WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
2032         this._storagePanel._showRevisionView(this._revision);
2033     },
2034
2035     _ondragstart: function(event)
2036     {
2037         if (this._revision.content) {
2038             event.dataTransfer.setData("text/plain", this._revision.content);
2039             event.dataTransfer.effectAllowed = "copy";
2040             return true;
2041         }
2042     },
2043
2044     _handleContextMenuEvent: function(event)
2045     {
2046         var contextMenu = new WebInspector.ContextMenu();
2047         contextMenu.appendItem(WebInspector.UIString("Revert to this revision"), this._revision.revertToThis.bind(this._revision));
2048
2049         if (InspectorFrontendHost.canSave()) {
2050             function doSave(forceSaveAs, content)
2051             {
2052                 WebInspector.fileManager.save(this._revision.resource.url, content, forceSaveAs);
2053             }
2054
2055             function save(forceSaveAs)
2056             {
2057                 this._revision.requestContent(doSave.bind(this, forceSaveAs));
2058             }
2059
2060             contextMenu.appendSeparator();
2061             contextMenu.appendItem(WebInspector.UIString("Save"), save.bind(this, false));
2062             contextMenu.appendItem(WebInspector.UIString(WebInspector.useLowerCaseMenuTitles() ? "Save as..." : "Save As..."), save.bind(this, true));
2063         }
2064
2065         contextMenu.show(event);
2066     },
2067
2068     sourceView: function()
2069     {
2070         if (!this._sourceView)
2071             this._sourceView = new WebInspector.SourceFrame(this._revision);
2072         return this._sourceView;
2073     }
2074 }
2075
2076 WebInspector.ResourceRevisionTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
2077
2078 /**
2079  * @constructor
2080  * @extends {WebInspector.View}
2081  */
2082 WebInspector.StorageCategoryView = function()
2083 {
2084     WebInspector.View.call(this);
2085
2086     this.element.addStyleClass("storage-view");
2087     this._emptyView = new WebInspector.EmptyView("");
2088     this._emptyView.show(this.element);
2089 }
2090
2091 WebInspector.StorageCategoryView.prototype = {
2092     setText: function(text)
2093     {
2094         this._emptyView.text = text;
2095     }
2096 }
2097
2098 WebInspector.StorageCategoryView.prototype.__proto__ = WebInspector.View.prototype;
2099
2100 /**
2101  * @constructor
2102  * @param {WebInspector.BaseStorageTreeElement} rootElement
2103  * @param {number} matchesCount
2104  */
2105 WebInspector.ResourcesSearchController = function(rootElement, matchesCount)
2106 {
2107     this._root = rootElement;
2108     this._matchesCount = matchesCount;
2109     this._traverser = new WebInspector.SearchResultsTreeElementsTraverser(rootElement);
2110     this._lastTreeElement = null;
2111     this._lastIndex = -1;
2112 }
2113
2114 WebInspector.ResourcesSearchController.prototype = {
2115     /**
2116      * @param {WebInspector.BaseStorageTreeElement} currentTreeElement
2117      */
2118     nextSearchResult: function(currentTreeElement)
2119     {
2120         if (!currentTreeElement)
2121             return this._searchResult(this._traverser.first(), 0, 1);
2122
2123         if (!currentTreeElement.searchMatchesCount)
2124             return this._searchResult(this._traverser.next(currentTreeElement), 0);
2125
2126         if (this._lastTreeElement !== currentTreeElement || this._lastIndex === -1)
2127             return this._searchResult(currentTreeElement, 0);
2128
2129         if (this._lastIndex == currentTreeElement.searchMatchesCount - 1)
2130             return this._searchResult(this._traverser.next(currentTreeElement), 0, this._currentMatchIndex % this._matchesCount + 1);
2131
2132         return this._searchResult(currentTreeElement, this._lastIndex + 1, this._currentMatchIndex + 1);
2133     },
2134
2135     /**
2136      * @param {WebInspector.BaseStorageTreeElement} currentTreeElement
2137      */
2138     previousSearchResult: function(currentTreeElement)
2139     {
2140         if (!currentTreeElement) {
2141             var treeElement = this._traverser.last();
2142             return this._searchResult(treeElement, treeElement.searchMatchesCount - 1, this._matchesCount);
2143         }
2144
2145         if (currentTreeElement.searchMatchesCount && this._lastTreeElement === currentTreeElement) {
2146             if (this._lastIndex > 0)
2147                 return this._searchResult(currentTreeElement, this._lastIndex - 1, this._currentMatchIndex - 1);
2148             else {
2149                 var treeElement = this._traverser.previous(currentTreeElement);
2150                 var currentMatchIndex = this._currentMatchIndex - 1 ? this._currentMatchIndex - 1 : this._matchesCount;
2151                 return this._searchResult(treeElement, treeElement.searchMatchesCount - 1, currentMatchIndex);
2152             }
2153         }
2154
2155         var treeElement = this._traverser.previous(currentTreeElement)
2156         return this._searchResult(treeElement, treeElement.searchMatchesCount - 1);
2157     },
2158
2159     /**
2160      * @param {WebInspector.BaseStorageTreeElement} treeElement
2161      * @param {number} index
2162      * @param {number=} currentMatchIndex
2163      * @return {Object}
2164      */
2165     _searchResult: function(treeElement, index, currentMatchIndex)
2166     {
2167         this._lastTreeElement = treeElement;
2168         this._lastIndex = index;
2169         if (!currentMatchIndex)
2170             currentMatchIndex = this._traverser.matchIndex(treeElement, index);
2171         this._currentMatchIndex = currentMatchIndex;
2172         return {treeElement: treeElement, index: index, currentMatchIndex: currentMatchIndex};
2173     }
2174 }
2175
2176 /**
2177  * @constructor
2178  * @param {WebInspector.BaseStorageTreeElement} rootElement
2179  */
2180 WebInspector.SearchResultsTreeElementsTraverser = function(rootElement)
2181 {
2182     this._root = rootElement;
2183 }
2184
2185 WebInspector.SearchResultsTreeElementsTraverser.prototype = {
2186     /**
2187      * @return {WebInspector.BaseStorageTreeElement}
2188      */
2189     first: function()
2190     {
2191         return this.next(this._root);
2192     },
2193
2194     /**
2195      * @return {WebInspector.BaseStorageTreeElement}
2196      */
2197     last: function()
2198     {
2199         return this.previous(this._root);
2200     },
2201
2202     /**
2203      * @param {WebInspector.BaseStorageTreeElement} startTreeElement
2204      * @return {WebInspector.BaseStorageTreeElement}
2205      */
2206     next: function(startTreeElement)
2207     {
2208         var treeElement = startTreeElement;
2209         do {
2210             treeElement = this._traverseNext(treeElement) || this._root;
2211         } while (treeElement != startTreeElement && !this._elementSearchMatchesCount(treeElement));
2212         return treeElement;
2213     },
2214
2215     /**
2216      * @param {WebInspector.BaseStorageTreeElement} startTreeElement
2217      * @return {WebInspector.BaseStorageTreeElement}
2218      */
2219     previous: function(startTreeElement)
2220     {
2221         var treeElement = startTreeElement;
2222         do {
2223             treeElement = this._traversePrevious(treeElement) || this._lastTreeElement();
2224         } while (treeElement != startTreeElement && !this._elementSearchMatchesCount(treeElement));
2225         return treeElement;
2226     },
2227
2228     /**
2229      * @param {WebInspector.BaseStorageTreeElement} startTreeElement
2230      * @param {number} index
2231      * @return {number}
2232      */
2233     matchIndex: function(startTreeElement, index)
2234     {
2235         var matchIndex = 1;
2236         var treeElement = this._root;
2237         while (treeElement != startTreeElement) {
2238             matchIndex += this._elementSearchMatchesCount(treeElement);
2239             treeElement = this._traverseNext(treeElement) || this._root;
2240             if (treeElement === this._root)
2241                 return 0;
2242         }
2243         return matchIndex + index;
2244     },
2245
2246     /**
2247      * @param {WebInspector.BaseStorageTreeElement} treeElement
2248      * @return {number}
2249      */
2250     _elementSearchMatchesCount: function(treeElement)
2251     {
2252         return treeElement.searchMatchesCount;
2253     },
2254
2255     /**
2256      * @param {WebInspector.BaseStorageTreeElement} treeElement
2257      * @return {WebInspector.BaseStorageTreeElement}
2258      */
2259     _traverseNext: function(treeElement)
2260     {
2261         return /** @type {WebInspector.BaseStorageTreeElement} */ treeElement.traverseNextTreeElement(false, this._root, true);
2262     },
2263
2264     /**
2265      * @param {WebInspector.BaseStorageTreeElement} treeElement
2266      * @return {WebInspector.BaseStorageTreeElement}
2267      */
2268     _traversePrevious: function(treeElement)
2269     {
2270         return /** @type {WebInspector.BaseStorageTreeElement} */ treeElement.traversePreviousTreeElement(false, true);
2271     },
2272
2273     /**
2274      * @return {WebInspector.BaseStorageTreeElement}
2275      */
2276     _lastTreeElement: function()
2277     {
2278         var treeElement = this._root;
2279         var nextTreeElement;
2280         while (nextTreeElement = this._traverseNext(treeElement))
2281             treeElement = nextTreeElement;
2282         return treeElement;
2283     }
2284 }