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