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