1/*
2 * Copyright (C) 2007, 2008, 2010 Apple Inc.  All rights reserved.
3 * Copyright (C) 2009 Joseph Pecoraro
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 *
9 * 1.  Redistributions of source code must retain the above copyright
10 *     notice, this list of conditions and the following disclaimer.
11 * 2.  Redistributions in binary form must reproduce the above copyright
12 *     notice, this list of conditions and the following disclaimer in the
13 *     documentation and/or other materials provided with the distribution.
14 * 3.  Neither the name of Apple Computer, Inc. ("Apple") nor the names of
15 *     its contributors may be used to endorse or promote products derived
16 *     from this software without specific prior written permission.
17 *
18 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
19 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
20 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
21 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
22 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
23 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
24 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
25 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29
30WebInspector.ResourcesPanel = function(database)
31{
32    WebInspector.Panel.call(this, "resources");
33
34    WebInspector.settings.installApplicationSetting("resourcesLastSelectedItem", {});
35
36    this.createSidebar();
37    this.sidebarElement.addStyleClass("outline-disclosure");
38    this.sidebarElement.addStyleClass("filter-all");
39    this.sidebarElement.addStyleClass("children");
40    this.sidebarElement.addStyleClass("small");
41    this.sidebarTreeElement.removeStyleClass("sidebar-tree");
42
43    this.resourcesListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Frames"), "Frames", ["frame-storage-tree-item"]);
44    this.sidebarTree.appendChild(this.resourcesListTreeElement);
45
46    this.databasesListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Databases"), "Databases", ["database-storage-tree-item"]);
47    this.sidebarTree.appendChild(this.databasesListTreeElement);
48
49    this.localStorageListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Local Storage"), "LocalStorage", ["domstorage-storage-tree-item", "local-storage"]);
50    this.sidebarTree.appendChild(this.localStorageListTreeElement);
51
52    this.sessionStorageListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Session Storage"), "SessionStorage", ["domstorage-storage-tree-item", "session-storage"]);
53    this.sidebarTree.appendChild(this.sessionStorageListTreeElement);
54
55    this.cookieListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Cookies"), "Cookies", ["cookie-storage-tree-item"]);
56    this.sidebarTree.appendChild(this.cookieListTreeElement);
57
58    this.applicationCacheListTreeElement = new WebInspector.StorageCategoryTreeElement(this, WebInspector.UIString("Application Cache"), "ApplicationCache", ["application-cache-storage-tree-item"]);
59    this.sidebarTree.appendChild(this.applicationCacheListTreeElement);
60
61    this.storageViews = document.createElement("div");
62    this.storageViews.id = "storage-views";
63    this.storageViews.className = "diff-container";
64    this.element.appendChild(this.storageViews);
65
66    this.storageViewStatusBarItemsContainer = document.createElement("div");
67    this.storageViewStatusBarItemsContainer.className = "status-bar-items";
68
69    this._databases = [];
70    this._domStorage = [];
71    this._cookieViews = {};
72    this._origins = {};
73    this._domains = {};
74
75    this.sidebarElement.addEventListener("mousemove", this._onmousemove.bind(this), false);
76    this.sidebarElement.addEventListener("mouseout", this._onmouseout.bind(this), false);
77
78    WebInspector.networkManager.addEventListener(WebInspector.NetworkManager.EventTypes.ResourceUpdated, this._refreshResource, this);
79}
80
81WebInspector.ResourcesPanel.prototype = {
82    get toolbarItemLabel()
83    {
84        return WebInspector.UIString("Resources");
85    },
86
87    get statusBarItems()
88    {
89        return [this.storageViewStatusBarItemsContainer];
90    },
91
92    elementsToRestoreScrollPositionsFor: function()
93    {
94        return [this.sidebarElement];
95    },
96
97    show: function()
98    {
99        WebInspector.Panel.prototype.show.call(this);
100
101        this._populateResourceTree();
102
103        if (this.visibleView && this.visibleView.resource)
104            this._showResourceView(this.visibleView.resource);
105    },
106
107    loadEventFired: function()
108    {
109        this._initDefaultSelection();
110    },
111
112    _initDefaultSelection: function()
113    {
114        var itemURL = WebInspector.settings.resourcesLastSelectedItem;
115        if (itemURL) {
116            for (var treeElement = this.sidebarTree.children[0]; treeElement; treeElement = treeElement.traverseNextTreeElement(false, this.sidebarTree, true)) {
117                if (treeElement.itemURL === itemURL) {
118                    treeElement.select();
119                    treeElement.reveal();
120                    return;
121                }
122            }
123        }
124
125        if (WebInspector.mainResource && this.resourcesListTreeElement && this.resourcesListTreeElement.expanded)
126            this.showResource(WebInspector.mainResource);
127    },
128
129    reset: function()
130    {
131        delete this._initializedDefaultSelection;
132        this._origins = {};
133        this._domains = {};
134        for (var i = 0; i < this._databases.length; ++i) {
135            var database = this._databases[i];
136            delete database._tableViews;
137            delete database._queryView;
138        }
139        this._databases = [];
140
141        var domStorageLength = this._domStorage.length;
142        for (var i = 0; i < this._domStorage.length; ++i) {
143            var domStorage = this._domStorage[i];
144            delete domStorage._domStorageView;
145        }
146        this._domStorage = [];
147
148        this._cookieViews = {};
149
150        this._applicationCacheView = null;
151        delete this._cachedApplicationCacheViewStatus;
152
153        this.databasesListTreeElement.removeChildren();
154        this.localStorageListTreeElement.removeChildren();
155        this.sessionStorageListTreeElement.removeChildren();
156        this.cookieListTreeElement.removeChildren();
157        this.applicationCacheListTreeElement.removeChildren();
158        this.storageViews.removeChildren();
159
160        this.storageViewStatusBarItemsContainer.removeChildren();
161
162        if (this.sidebarTree.selectedTreeElement)
163            this.sidebarTree.selectedTreeElement.deselect();
164    },
165
166    _populateResourceTree: function()
167    {
168        if (this._treeElementForFrameId)
169            return;
170
171        this._treeElementForFrameId = {};
172        WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameAdded, this._frameAdded, this);
173        WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameNavigated, this._frameNavigated, this);
174        WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.FrameDetached, this._frameDetached, this);
175        WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.ResourceAdded, this._resourceAdded, this);
176        WebInspector.resourceTreeModel.addEventListener(WebInspector.ResourceTreeModel.EventTypes.CachedResourcesLoaded, this._cachedResourcesLoaded, this);
177
178        function populateFrame(frameId)
179        {
180            var subframes = WebInspector.resourceTreeModel.subframes(frameId);
181            for (var i = 0; i < subframes.length; ++i) {
182                this._frameAdded({data:subframes[i]});
183                populateFrame.call(this, subframes[i].id);
184            }
185
186            var resources = WebInspector.resourceTreeModel.resources(frameId);
187            for (var i = 0; i < resources.length; ++i)
188                this._resourceAdded({data:resources[i]});
189        }
190        populateFrame.call(this, 0);
191    },
192
193    _frameAdded: function(event)
194    {
195        var frame = event.data;
196        var frameId = frame.id;
197        var parentFrameId = frame.parentId;
198        var title = frame.name;
199        var subtitle = new WebInspector.Resource(null, frame.url).displayName;
200        this.addDocumentURL(frame.url);
201
202        var frameTreeElement = this._treeElementForFrameId[frameId];
203        if (frameTreeElement) {
204            // Maintain sorted order.
205            var parent = frameTreeElement.parent;
206            parent.removeChild(frameTreeElement);
207            frameTreeElement.setTitles(title, subtitle);
208            parent.appendChild(frameTreeElement);
209            return;
210        }
211
212        var parentTreeElement = parentFrameId ? this._treeElementForFrameId[parentFrameId] : this.resourcesListTreeElement;
213        if (!parentTreeElement) {
214            console.warn("No frame with id:" + parentFrameId + " to route " + title + "/" + subtitle + " to.")
215            return;
216        }
217
218        var frameTreeElement = new WebInspector.FrameTreeElement(this, frameId, title, subtitle);
219        this._treeElementForFrameId[frameId] = frameTreeElement;
220        parentTreeElement.appendChild(frameTreeElement);
221    },
222
223    _frameDetached: function(event)
224    {
225        var frameId = event.data;
226        var frameTreeElement = this._treeElementForFrameId[frameId];
227        if (!frameTreeElement)
228            return;
229        delete this._treeElementForFrameId[frameId];
230        if (frameTreeElement.parent)
231            frameTreeElement.parent.removeChild(frameTreeElement);
232    },
233
234    _resourceAdded: function(event)
235    {
236        var resource = event.data;
237        var frameId = resource.frameId;
238
239        if (resource.statusCode >= 301 && resource.statusCode <= 303)
240            return;
241
242        var frameTreeElement = this._treeElementForFrameId[frameId];
243        if (!frameTreeElement) {
244            // This is a frame's main resource, it will be retained
245            // and re-added by the resource manager;
246            return;
247        }
248
249        frameTreeElement.appendResource(resource);
250    },
251
252    _frameNavigated: function(event)
253    {
254        var frameId = event.data;
255        if (!frameId) {
256            // Total update.
257            this.resourcesListTreeElement.removeChildren();
258            this._treeElementForFrameId = {};
259            this.reset();
260            return;
261        }
262
263        var frameTreeElement = this._treeElementForFrameId[frameId];
264        if (frameTreeElement)
265            frameTreeElement.removeChildren();
266    },
267
268    _cachedResourcesLoaded: function()
269    {
270        this._initDefaultSelection();
271    },
272
273    _refreshResource: function(event)
274    {
275        var resource = event.data;
276        // FIXME: do not add XHR in the first place based on the native instrumentation.
277        if (resource.type === WebInspector.Resource.Type.XHR) {
278            var resourceTreeElement = this._findTreeElementForResource(resource);
279            if (resourceTreeElement)
280                resourceTreeElement.parent.removeChild(resourceTreeElement);
281        }
282    },
283
284    addDatabase: function(database)
285    {
286        this._databases.push(database);
287
288        var databaseTreeElement = new WebInspector.DatabaseTreeElement(this, database);
289        database._databasesTreeElement = databaseTreeElement;
290        this.databasesListTreeElement.appendChild(databaseTreeElement);
291    },
292
293    addDocumentURL: function(url)
294    {
295        var parsedURL = url.asParsedURL();
296        if (!parsedURL)
297            return;
298
299        var domain = parsedURL.host;
300        if (!this._domains[domain]) {
301            this._domains[domain] = true;
302
303            var cookieDomainTreeElement = new WebInspector.CookieTreeElement(this, domain);
304            this.cookieListTreeElement.appendChild(cookieDomainTreeElement);
305
306            var applicationCacheTreeElement = new WebInspector.ApplicationCacheTreeElement(this, domain);
307            this.applicationCacheListTreeElement.appendChild(applicationCacheTreeElement);
308        }
309    },
310
311    addDOMStorage: function(domStorage)
312    {
313        this._domStorage.push(domStorage);
314        var domStorageTreeElement = new WebInspector.DOMStorageTreeElement(this, domStorage, (domStorage.isLocalStorage ? "local-storage" : "session-storage"));
315        domStorage._domStorageTreeElement = domStorageTreeElement;
316        if (domStorage.isLocalStorage)
317            this.localStorageListTreeElement.appendChild(domStorageTreeElement);
318        else
319            this.sessionStorageListTreeElement.appendChild(domStorageTreeElement);
320    },
321
322    selectDatabase: function(databaseId)
323    {
324        var database;
325        for (var i = 0, len = this._databases.length; i < len; ++i) {
326            database = this._databases[i];
327            if (database.id === databaseId) {
328                this.showDatabase(database);
329                database._databasesTreeElement.select();
330                return;
331            }
332        }
333    },
334
335    selectDOMStorage: function(storageId)
336    {
337        var domStorage = this._domStorageForId(storageId);
338        if (domStorage) {
339            this.showDOMStorage(domStorage);
340            domStorage._domStorageTreeElement.select();
341        }
342    },
343
344    canShowAnchorLocation: function(anchor)
345    {
346        return !!WebInspector.resourceForURL(anchor.href);
347    },
348
349    showAnchorLocation: function(anchor)
350    {
351        var resource = WebInspector.resourceForURL(anchor.href);
352        if (resource.type === WebInspector.Resource.Type.XHR) {
353            // Show XHRs in the network panel only.
354            if (WebInspector.panels.network && WebInspector.panels.network.canShowAnchorLocation(anchor)) {
355                WebInspector.currentPanel = WebInspector.panels.network;
356                WebInspector.panels.network.showAnchorLocation(anchor);
357            }
358            return;
359        }
360        this.showResource(resource, anchor.getAttribute("line_number") - 1);
361    },
362
363    showResource: function(resource, line)
364    {
365        var resourceTreeElement = this._findTreeElementForResource(resource);
366        if (resourceTreeElement) {
367            resourceTreeElement.reveal();
368            resourceTreeElement.select();
369        }
370
371        if (line !== undefined) {
372            var view = WebInspector.ResourceView.resourceViewForResource(resource);
373            if (view.highlightLine)
374                view.highlightLine(line);
375        }
376        return true;
377    },
378
379    _showResourceView: function(resource)
380    {
381        var view = WebInspector.ResourceView.resourceViewForResource(resource);
382        this._fetchAndApplyDiffMarkup(view, resource);
383        this._innerShowView(view);
384    },
385
386    _showRevisionView: function(revision)
387    {
388        if (!revision._view)
389            revision._view = new WebInspector.RevisionSourceFrame(revision);
390        var view = revision._view;
391        this._fetchAndApplyDiffMarkup(view, revision.resource, revision);
392        this._innerShowView(view);
393    },
394
395    _fetchAndApplyDiffMarkup: function(view, resource, revision)
396    {
397        var baseRevision = resource.history[0];
398        if (!baseRevision)
399            return;
400        if (!(view instanceof WebInspector.SourceFrame))
401            return;
402
403        baseRevision.requestContent(step1.bind(this));
404
405        function step1(baseContent)
406        {
407            (revision ? revision : resource).requestContent(step2.bind(this, baseContent));
408        }
409
410        function step2(baseContent, revisionContent)
411        {
412            this._applyDiffMarkup(view, baseContent, revisionContent);
413        }
414    },
415
416    _applyDiffMarkup: function(view, baseContent, newContent) {
417        var oldLines = baseContent.split(/\r?\n/);
418        var newLines = newContent.split(/\r?\n/);
419
420        var diff = Array.diff(oldLines, newLines);
421
422        var diffData = {};
423        diffData.added = [];
424        diffData.removed = [];
425        diffData.changed = [];
426
427        var offset = 0;
428        var right = diff.right;
429        for (var i = 0; i < right.length; ++i) {
430            if (typeof right[i] === "string") {
431                if (right.length > i + 1 && right[i + 1].row === i + 1 - offset)
432                    diffData.changed.push(i);
433                else {
434                    diffData.added.push(i);
435                    offset++;
436                }
437            } else
438                offset = i - right[i].row;
439        }
440        view.markDiff(diffData);
441    },
442
443    showDatabase: function(database, tableName)
444    {
445        if (!database)
446            return;
447
448        var view;
449        if (tableName) {
450            if (!("_tableViews" in database))
451                database._tableViews = {};
452            view = database._tableViews[tableName];
453            if (!view) {
454                view = new WebInspector.DatabaseTableView(database, tableName);
455                database._tableViews[tableName] = view;
456            }
457        } else {
458            view = database._queryView;
459            if (!view) {
460                view = new WebInspector.DatabaseQueryView(database);
461                database._queryView = view;
462            }
463        }
464
465        this._innerShowView(view);
466    },
467
468    showDOMStorage: function(domStorage)
469    {
470        if (!domStorage)
471            return;
472
473        var view;
474        view = domStorage._domStorageView;
475        if (!view) {
476            view = new WebInspector.DOMStorageItemsView(domStorage);
477            domStorage._domStorageView = view;
478        }
479
480        this._innerShowView(view);
481    },
482
483    showCookies: function(treeElement, cookieDomain)
484    {
485        var view = this._cookieViews[cookieDomain];
486        if (!view) {
487            view = new WebInspector.CookieItemsView(treeElement, cookieDomain);
488            this._cookieViews[cookieDomain] = view;
489        }
490
491        this._innerShowView(view);
492    },
493
494    showApplicationCache: function(treeElement, appcacheDomain)
495    {
496        var view = this._applicationCacheView;
497        if (!view) {
498            view = new WebInspector.ApplicationCacheItemsView(treeElement, appcacheDomain);
499            this._applicationCacheView = view;
500        }
501
502        this._innerShowView(view);
503
504        if ("_cachedApplicationCacheViewStatus" in this)
505            this._applicationCacheView.updateStatus(this._cachedApplicationCacheViewStatus);
506    },
507
508    showCategoryView: function(categoryName)
509    {
510        if (!this._categoryView)
511            this._categoryView = new WebInspector.StorageCategoryView();
512        this._categoryView.setText(categoryName);
513        this._innerShowView(this._categoryView);
514    },
515
516    _innerShowView: function(view)
517    {
518        if (this.visibleView)
519            this.visibleView.hide();
520
521        view.show(this.storageViews);
522        this.visibleView = view;
523
524        this.storageViewStatusBarItemsContainer.removeChildren();
525        var statusBarItems = view.statusBarItems || [];
526        for (var i = 0; i < statusBarItems.length; ++i)
527            this.storageViewStatusBarItemsContainer.appendChild(statusBarItems[i]);
528    },
529
530    closeVisibleView: function()
531    {
532        if (this.visibleView)
533            this.visibleView.hide();
534        delete this.visibleView;
535    },
536
537    updateDatabaseTables: function(database)
538    {
539        if (!database || !database._databasesTreeElement)
540            return;
541
542        database._databasesTreeElement.shouldRefreshChildren = true;
543
544        if (!("_tableViews" in database))
545            return;
546
547        var tableNamesHash = {};
548        var self = this;
549        function tableNamesCallback(tableNames)
550        {
551            var tableNamesLength = tableNames.length;
552            for (var i = 0; i < tableNamesLength; ++i)
553                tableNamesHash[tableNames[i]] = true;
554
555            for (var tableName in database._tableViews) {
556                if (!(tableName in tableNamesHash)) {
557                    if (self.visibleView === database._tableViews[tableName])
558                        self.closeVisibleView();
559                    delete database._tableViews[tableName];
560                }
561            }
562        }
563        database.getTableNames(tableNamesCallback);
564    },
565
566    dataGridForResult: function(columnNames, values)
567    {
568        var numColumns = columnNames.length;
569        if (!numColumns)
570            return null;
571
572        var columns = {};
573
574        for (var i = 0; i < columnNames.length; ++i) {
575            var column = {};
576            column.width = columnNames[i].length;
577            column.title = columnNames[i];
578            column.sortable = true;
579
580            columns[columnNames[i]] = column;
581        }
582
583        var nodes = [];
584        for (var i = 0; i < values.length / numColumns; ++i) {
585            var data = {};
586            for (var j = 0; j < columnNames.length; ++j)
587                data[columnNames[j]] = values[numColumns * i + j];
588
589            var node = new WebInspector.DataGridNode(data, false);
590            node.selectable = false;
591            nodes.push(node);
592        }
593
594        var dataGrid = new WebInspector.DataGrid(columns);
595        var length = nodes.length;
596        for (var i = 0; i < length; ++i)
597            dataGrid.appendChild(nodes[i]);
598
599        dataGrid.addEventListener("sorting changed", this._sortDataGrid.bind(this, dataGrid), this);
600        return dataGrid;
601    },
602
603    _sortDataGrid: function(dataGrid)
604    {
605        var nodes = dataGrid.children.slice();
606        var sortColumnIdentifier = dataGrid.sortColumnIdentifier;
607        var sortDirection = dataGrid.sortOrder === "ascending" ? 1 : -1;
608        var columnIsNumeric = true;
609
610        for (var i = 0; i < nodes.length; i++) {
611            if (isNaN(Number(nodes[i].data[sortColumnIdentifier])))
612                columnIsNumeric = false;
613        }
614
615        function comparator(dataGridNode1, dataGridNode2)
616        {
617            var item1 = dataGridNode1.data[sortColumnIdentifier];
618            var item2 = dataGridNode2.data[sortColumnIdentifier];
619
620            var comparison;
621            if (columnIsNumeric) {
622                // Sort numbers based on comparing their values rather than a lexicographical comparison.
623                var number1 = parseFloat(item1);
624                var number2 = parseFloat(item2);
625                comparison = number1 < number2 ? -1 : (number1 > number2 ? 1 : 0);
626            } else
627                comparison = item1 < item2 ? -1 : (item1 > item2 ? 1 : 0);
628
629            return sortDirection * comparison;
630        }
631
632        nodes.sort(comparator);
633        dataGrid.removeChildren();
634        for (var i = 0; i < nodes.length; i++)
635            dataGrid.appendChild(nodes[i]);
636    },
637
638    updateDOMStorage: function(storageId)
639    {
640        var domStorage = this._domStorageForId(storageId);
641        if (!domStorage)
642            return;
643
644        var view = domStorage._domStorageView;
645        if (this.visibleView && view === this.visibleView)
646            domStorage._domStorageView.update();
647    },
648
649    updateApplicationCacheStatus: function(status)
650    {
651        this._cachedApplicationCacheViewStatus = status;
652        if (this._applicationCacheView && this._applicationCacheView === this.visibleView)
653            this._applicationCacheView.updateStatus(status);
654    },
655
656    updateNetworkState: function(isNowOnline)
657    {
658        if (this._applicationCacheView && this._applicationCacheView === this.visibleView)
659            this._applicationCacheView.updateNetworkState(isNowOnline);
660    },
661
662    updateManifest: function(manifest)
663    {
664        if (this._applicationCacheView && this._applicationCacheView === this.visibleView)
665            this._applicationCacheView.updateManifest(manifest);
666    },
667
668    _domStorageForId: function(storageId)
669    {
670        if (!this._domStorage)
671            return null;
672        var domStorageLength = this._domStorage.length;
673        for (var i = 0; i < domStorageLength; ++i) {
674            var domStorage = this._domStorage[i];
675            if (domStorage.id == storageId)
676                return domStorage;
677        }
678        return null;
679    },
680
681    updateMainViewWidth: function(width)
682    {
683        this.storageViews.style.left = width + "px";
684        this.storageViewStatusBarItemsContainer.style.left = width + "px";
685        this.resize();
686    },
687
688    get searchableViews()
689    {
690        var views = [];
691
692        const visibleView = this.visibleView;
693        if (visibleView && visibleView.performSearch)
694            views.push(visibleView);
695
696        function callback(resourceTreeElement)
697        {
698            var resource = resourceTreeElement._resource;
699            var resourceView = WebInspector.ResourceView.resourceViewForResource(resource);
700            if (resourceView.performSearch && resourceView !== visibleView)
701                views.push(resourceView);
702        }
703        this._forAllResourceTreeElements(callback);
704        return views;
705    },
706
707    _forAllResourceTreeElements: function(callback)
708    {
709        var stop = false;
710        for (var treeElement = this.resourcesListTreeElement; !stop && treeElement; treeElement = treeElement.traverseNextTreeElement(false, this.resourcesListTreeElement, true)) {
711            if (treeElement instanceof WebInspector.FrameResourceTreeElement)
712                stop = callback(treeElement);
713        }
714    },
715
716    searchMatchFound: function(view, matches)
717    {
718        if (!view.resource)
719            return;
720        var treeElement = this._findTreeElementForResource(view.resource);
721        if (treeElement)
722            treeElement.searchMatchFound(matches);
723    },
724
725    _findTreeElementForResource: function(resource)
726    {
727        function isAncestor(ancestor, object)
728        {
729            // Redirects, XHRs do not belong to the tree, it is fine to silently return false here.
730            return false;
731        }
732
733        function getParent(object)
734        {
735            // Redirects, XHRs do not belong to the tree, it is fine to silently return false here.
736            return null;
737        }
738
739        return this.sidebarTree.findTreeElement(resource, isAncestor, getParent);
740    },
741
742    searchCanceled: function(startingNewSearch)
743    {
744        WebInspector.Panel.prototype.searchCanceled.call(this, startingNewSearch);
745
746        if (startingNewSearch)
747            return;
748
749        function callback(resourceTreeElement)
750        {
751            resourceTreeElement._errorsWarningsUpdated();
752        }
753        this._forAllResourceTreeElements(callback);
754    },
755
756    performSearch: function(query)
757    {
758        function callback(resourceTreeElement)
759        {
760            resourceTreeElement._resetBubble();
761        }
762        this._forAllResourceTreeElements(callback);
763        WebInspector.Panel.prototype.performSearch.call(this, query);
764    },
765
766    showView: function(view)
767    {
768        if (view)
769            this.showResource(view.resource);
770    },
771
772    _onmousemove: function(event)
773    {
774        var nodeUnderMouse = document.elementFromPoint(event.pageX, event.pageY);
775        if (!nodeUnderMouse)
776            return;
777
778        var listNode = nodeUnderMouse.enclosingNodeOrSelfWithNodeName("li");
779        if (!listNode)
780            return;
781
782        var element = listNode.treeElement;
783        if (this._previousHoveredElement === element)
784            return;
785
786        if (this._previousHoveredElement) {
787            this._previousHoveredElement.hovered = false;
788            delete this._previousHoveredElement;
789        }
790
791        if (element instanceof WebInspector.FrameTreeElement) {
792            this._previousHoveredElement = element;
793            element.hovered = true;
794        }
795    },
796
797    _onmouseout: function(event)
798    {
799        if (this._previousHoveredElement) {
800            this._previousHoveredElement.hovered = false;
801            delete this._previousHoveredElement;
802        }
803    }
804}
805
806WebInspector.ResourcesPanel.prototype.__proto__ = WebInspector.Panel.prototype;
807
808WebInspector.BaseStorageTreeElement = function(storagePanel, representedObject, title, iconClasses, hasChildren, noIcon)
809{
810    TreeElement.call(this, "", representedObject, hasChildren);
811    this._storagePanel = storagePanel;
812    this._titleText = title;
813    this._iconClasses = iconClasses;
814    this._noIcon = noIcon;
815}
816
817WebInspector.BaseStorageTreeElement.prototype = {
818    onattach: function()
819    {
820        this.listItemElement.removeChildren();
821        if (this._iconClasses) {
822            for (var i = 0; i < this._iconClasses.length; ++i)
823                this.listItemElement.addStyleClass(this._iconClasses[i]);
824        }
825
826        var selectionElement = document.createElement("div");
827        selectionElement.className = "selection";
828        this.listItemElement.appendChild(selectionElement);
829
830        if (!this._noIcon) {
831            this.imageElement = document.createElement("img");
832            this.imageElement.className = "icon";
833            this.listItemElement.appendChild(this.imageElement);
834        }
835
836        this.titleElement = document.createElement("div");
837        this.titleElement.className = "base-storage-tree-element-title";
838        this.titleElement.textContent = this._titleText;
839        this.listItemElement.appendChild(this.titleElement);
840    },
841
842    onselect: function()
843    {
844        var itemURL = this.itemURL;
845        if (itemURL)
846            WebInspector.settings.resourcesLastSelectedItem = itemURL;
847    },
848
849    onreveal: function()
850    {
851        if (this.listItemElement)
852            this.listItemElement.scrollIntoViewIfNeeded(false);
853    },
854
855    get titleText()
856    {
857        return this._titleText;
858    },
859
860    set titleText(titleText)
861    {
862        this._titleText = titleText;
863        if (this.titleElement)
864            this.titleElement.textContent = this._titleText;
865    },
866
867    isEventWithinDisclosureTriangle: function()
868    {
869        // Override it since we use margin-left in place of treeoutline's text-indent.
870        // Hence we need to take padding into consideration. This all is needed for leading
871        // icons in the tree.
872        const paddingLeft = 14;
873        var left = this.listItemElement.totalOffsetLeft + paddingLeft;
874        return event.pageX >= left && event.pageX <= left + this.arrowToggleWidth && this.hasChildren;
875    }
876}
877
878WebInspector.BaseStorageTreeElement.prototype.__proto__ = TreeElement.prototype;
879
880WebInspector.StorageCategoryTreeElement = function(storagePanel, categoryName, settingsKey, iconClasses, noIcon)
881{
882    WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, categoryName, iconClasses, true, noIcon);
883    this._expandedSettingKey = "resources" + settingsKey + "Expanded";
884    WebInspector.settings.installApplicationSetting(this._expandedSettingKey, settingsKey === "Frames");
885    this._categoryName = categoryName;
886}
887
888WebInspector.StorageCategoryTreeElement.prototype = {
889    get itemURL()
890    {
891        return "category://" + this._categoryName;
892    },
893
894    onselect: function()
895    {
896        WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
897        this._storagePanel.showCategoryView(this._categoryName);
898    },
899
900    onattach: function()
901    {
902        WebInspector.BaseStorageTreeElement.prototype.onattach.call(this);
903        if (WebInspector.settings[this._expandedSettingKey])
904            this.expand();
905    },
906
907    onexpand: function()
908    {
909        WebInspector.settings[this._expandedSettingKey] = true;
910    },
911
912    oncollapse: function()
913    {
914        WebInspector.settings[this._expandedSettingKey] = false;
915    }
916}
917WebInspector.StorageCategoryTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
918
919WebInspector.FrameTreeElement = function(storagePanel, frameId, title, subtitle)
920{
921    WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, "", ["frame-storage-tree-item"]);
922    this._frameId = frameId;
923    this.setTitles(title, subtitle);
924    this._categoryElements = {};
925}
926
927WebInspector.FrameTreeElement.prototype = {
928    get itemURL()
929    {
930        return "frame://" + encodeURI(this._displayName);
931    },
932
933    onattach: function()
934    {
935        WebInspector.BaseStorageTreeElement.prototype.onattach.call(this);
936        if (this._titleToSetOnAttach || this._subtitleToSetOnAttach) {
937            this.setTitles(this._titleToSetOnAttach, this._subtitleToSetOnAttach);
938            delete this._titleToSetOnAttach;
939            delete this._subtitleToSetOnAttach;
940        }
941    },
942
943    onselect: function()
944    {
945        WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
946        this._storagePanel.showCategoryView(this._displayName);
947
948        this.listItemElement.removeStyleClass("hovered");
949        DOMAgent.hideFrameHighlight();
950    },
951
952    get displayName()
953    {
954        return this._displayName;
955    },
956
957    setTitles: function(title, subtitle)
958    {
959        this._displayName = title || "";
960        if (subtitle)
961            this._displayName += " (" + subtitle + ")";
962
963        if (this.parent) {
964            this.titleElement.textContent = title || "";
965            if (subtitle) {
966                var subtitleElement = document.createElement("span");
967                subtitleElement.className = "base-storage-tree-element-subtitle";
968                subtitleElement.textContent = "(" + subtitle + ")";
969                this.titleElement.appendChild(subtitleElement);
970            }
971        } else {
972            this._titleToSetOnAttach = title;
973            this._subtitleToSetOnAttach = subtitle;
974        }
975    },
976
977    set hovered(hovered)
978    {
979        if (hovered) {
980            this.listItemElement.addStyleClass("hovered");
981            DOMAgent.highlightFrame(this._frameId);
982        } else {
983            this.listItemElement.removeStyleClass("hovered");
984            DOMAgent.hideFrameHighlight();
985        }
986    },
987
988    appendResource: function(resource)
989    {
990        var categoryName = resource.category.name;
991        var categoryElement = resource.category === WebInspector.resourceCategories.documents ? this : this._categoryElements[categoryName];
992        if (!categoryElement) {
993            categoryElement = new WebInspector.StorageCategoryTreeElement(this._storagePanel, resource.category.title, categoryName, null, true);
994            this._categoryElements[resource.category.name] = categoryElement;
995            this._insertInPresentationOrder(this, categoryElement);
996        }
997        var resourceTreeElement = new WebInspector.FrameResourceTreeElement(this._storagePanel, resource);
998        this._insertInPresentationOrder(categoryElement, resourceTreeElement);
999        resourceTreeElement._populateRevisions();
1000    },
1001
1002    appendChild: function(treeElement)
1003    {
1004        this._insertInPresentationOrder(this, treeElement);
1005    },
1006
1007    _insertInPresentationOrder: function(parentTreeElement, childTreeElement)
1008    {
1009        // Insert in the alphabetical order, first frames, then resources. Document resource goes last.
1010        function typeWeight(treeElement)
1011        {
1012            if (treeElement instanceof WebInspector.StorageCategoryTreeElement)
1013                return 2;
1014            if (treeElement instanceof WebInspector.FrameTreeElement)
1015                return 1;
1016            return 3;
1017        }
1018
1019        function compare(treeElement1, treeElement2)
1020        {
1021            var typeWeight1 = typeWeight(treeElement1);
1022            var typeWeight2 = typeWeight(treeElement2);
1023
1024            var result;
1025            if (typeWeight1 > typeWeight2)
1026                result = 1;
1027            else if (typeWeight1 < typeWeight2)
1028                result = -1;
1029            else {
1030                var title1 = treeElement1.displayName || treeElement1.titleText;
1031                var title2 = treeElement2.displayName || treeElement2.titleText;
1032                result = title1.localeCompare(title2);
1033            }
1034            return result;
1035        }
1036
1037        var children = parentTreeElement.children;
1038        var i;
1039        for (i = 0; i < children.length; ++i) {
1040            if (compare(childTreeElement, children[i]) < 0)
1041                break;
1042        }
1043        parentTreeElement.insertChild(childTreeElement, i);
1044    }
1045}
1046
1047WebInspector.FrameTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1048
1049WebInspector.FrameResourceTreeElement = function(storagePanel, resource)
1050{
1051    WebInspector.BaseStorageTreeElement.call(this, storagePanel, resource, resource.displayName, ["resource-sidebar-tree-item", "resources-category-" + resource.category.name]);
1052    this._resource = resource;
1053    this._resource.addEventListener("errors-warnings-updated", this._errorsWarningsUpdated, this);
1054    this._resource.addEventListener(WebInspector.Resource.Events.RevisionAdded, this._revisionAdded, this);
1055    this.tooltip = resource.url;
1056}
1057
1058WebInspector.FrameResourceTreeElement.prototype = {
1059    get itemURL()
1060    {
1061        return this._resource.url;
1062    },
1063
1064    onselect: function()
1065    {
1066        WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1067        this._storagePanel._showResourceView(this._resource);
1068    },
1069
1070    ondblclick: function(event)
1071    {
1072        PageAgent.openInInspectedWindow(this._resource.url);
1073    },
1074
1075    onattach: function()
1076    {
1077        WebInspector.BaseStorageTreeElement.prototype.onattach.call(this);
1078
1079        if (this._resource.category === WebInspector.resourceCategories.images) {
1080            var previewImage = document.createElement("img");
1081            previewImage.className = "image-resource-icon-preview";
1082            this._resource.populateImageSource(previewImage);
1083
1084            var iconElement = document.createElement("div");
1085            iconElement.className = "icon";
1086            iconElement.appendChild(previewImage);
1087            this.listItemElement.replaceChild(iconElement, this.imageElement);
1088        }
1089
1090        this._statusElement = document.createElement("div");
1091        this._statusElement.className = "status";
1092        this.listItemElement.insertBefore(this._statusElement, this.titleElement);
1093
1094        this.listItemElement.draggable = true;
1095        this.listItemElement.addEventListener("dragstart", this._ondragstart.bind(this), false);
1096    },
1097
1098    _ondragstart: function(event)
1099    {
1100        event.dataTransfer.setData("text/plain", this._resource.content);
1101        event.dataTransfer.effectAllowed = "copy";
1102        return true;
1103    },
1104
1105    _setBubbleText: function(x)
1106    {
1107        if (!this._bubbleElement) {
1108            this._bubbleElement = document.createElement("div");
1109            this._bubbleElement.className = "bubble";
1110            this._statusElement.appendChild(this._bubbleElement);
1111        }
1112
1113        this._bubbleElement.textContent = x;
1114    },
1115
1116    _resetBubble: function()
1117    {
1118        if (this._bubbleElement) {
1119            this._bubbleElement.textContent = "";
1120            this._bubbleElement.removeStyleClass("search-matches");
1121            this._bubbleElement.removeStyleClass("warning");
1122            this._bubbleElement.removeStyleClass("error");
1123        }
1124    },
1125
1126    searchMatchFound: function(matches)
1127    {
1128        this._resetBubble();
1129
1130        this._setBubbleText(matches);
1131        this._bubbleElement.addStyleClass("search-matches");
1132
1133        // Expand, do not scroll into view.
1134        var currentAncestor = this.parent;
1135        while (currentAncestor && !currentAncestor.root) {
1136            if (!currentAncestor.expanded)
1137                currentAncestor.expand();
1138            currentAncestor = currentAncestor.parent;
1139        }
1140    },
1141
1142    _errorsWarningsUpdated: function()
1143    {
1144        // FIXME: move to the SourceFrame.
1145        if (!this._resource.warnings && !this._resource.errors) {
1146            var view = WebInspector.ResourceView.existingResourceViewForResource(this._resource);
1147            if (view && view.clearMessages)
1148                view.clearMessages();
1149        }
1150
1151        if (this._storagePanel.currentQuery)
1152            return;
1153
1154        this._resetBubble();
1155
1156        if (this._resource.warnings || this._resource.errors)
1157            this._setBubbleText(this._resource.warnings + this._resource.errors);
1158
1159        if (this._resource.warnings)
1160            this._bubbleElement.addStyleClass("warning");
1161
1162        if (this._resource.errors)
1163            this._bubbleElement.addStyleClass("error");
1164    },
1165
1166    _populateRevisions: function()
1167    {
1168        for (var i = 0; i < this._resource.history.length; ++i)
1169            this._appendRevision(this._resource.history[i]);
1170    },
1171
1172    _revisionAdded: function(event)
1173    {
1174        this._appendRevision(event.data);
1175    },
1176
1177    _appendRevision: function(revision)
1178    {
1179        this.insertChild(new WebInspector.ResourceRevisionTreeElement(this._storagePanel, revision), 0);
1180        var oldView = WebInspector.ResourceView.existingResourceViewForResource(this._resource);
1181        if (oldView) {
1182            var newView = WebInspector.ResourceView.recreateResourceView(this._resource);
1183            if (oldView === this._storagePanel.visibleView)
1184                this._storagePanel._showResourceView(this._resource);
1185        }
1186    }
1187}
1188
1189WebInspector.FrameResourceTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1190
1191WebInspector.DatabaseTreeElement = function(storagePanel, database)
1192{
1193    WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, database.name, ["database-storage-tree-item"], true);
1194    this._database = database;
1195}
1196
1197WebInspector.DatabaseTreeElement.prototype = {
1198    get itemURL()
1199    {
1200        return "database://" + encodeURI(this._database.name);
1201    },
1202
1203    onselect: function()
1204    {
1205        WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1206        this._storagePanel.showDatabase(this._database);
1207    },
1208
1209    oncollapse: function()
1210    {
1211        // Request a refresh after every collapse so the next
1212        // expand will have an updated table list.
1213        this.shouldRefreshChildren = true;
1214    },
1215
1216    onpopulate: function()
1217    {
1218        this.removeChildren();
1219
1220        function tableNamesCallback(tableNames)
1221        {
1222            var tableNamesLength = tableNames.length;
1223            for (var i = 0; i < tableNamesLength; ++i)
1224                this.appendChild(new WebInspector.DatabaseTableTreeElement(this._storagePanel, this._database, tableNames[i]));
1225        }
1226        this._database.getTableNames(tableNamesCallback.bind(this));
1227    }
1228
1229}
1230WebInspector.DatabaseTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1231
1232WebInspector.DatabaseTableTreeElement = function(storagePanel, database, tableName)
1233{
1234    WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, tableName, ["database-storage-tree-item"]);
1235    this._database = database;
1236    this._tableName = tableName;
1237}
1238
1239WebInspector.DatabaseTableTreeElement.prototype = {
1240    get itemURL()
1241    {
1242        return "database://" + encodeURI(this._database.name) + "/" + encodeURI(this._tableName);
1243    },
1244
1245    onselect: function()
1246    {
1247        WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1248        this._storagePanel.showDatabase(this._database, this._tableName);
1249    }
1250}
1251WebInspector.DatabaseTableTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1252
1253WebInspector.DOMStorageTreeElement = function(storagePanel, domStorage, className)
1254{
1255    WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, domStorage.domain ? domStorage.domain : WebInspector.UIString("Local Files"), ["domstorage-storage-tree-item", className]);
1256    this._domStorage = domStorage;
1257}
1258
1259WebInspector.DOMStorageTreeElement.prototype = {
1260    get itemURL()
1261    {
1262        return "storage://" + this._domStorage.domain + "/" + (this._domStorage.isLocalStorage ? "local" : "session");
1263    },
1264
1265    onselect: function()
1266    {
1267        WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1268        this._storagePanel.showDOMStorage(this._domStorage);
1269    }
1270}
1271WebInspector.DOMStorageTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1272
1273WebInspector.CookieTreeElement = function(storagePanel, cookieDomain)
1274{
1275    WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, cookieDomain ? cookieDomain : WebInspector.UIString("Local Files"), ["cookie-storage-tree-item"]);
1276    this._cookieDomain = cookieDomain;
1277}
1278
1279WebInspector.CookieTreeElement.prototype = {
1280    get itemURL()
1281    {
1282        return "cookies://" + this._cookieDomain;
1283    },
1284
1285    onselect: function()
1286    {
1287        WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1288        this._storagePanel.showCookies(this, this._cookieDomain);
1289    }
1290}
1291WebInspector.CookieTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1292
1293WebInspector.ApplicationCacheTreeElement = function(storagePanel, appcacheDomain)
1294{
1295    WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, appcacheDomain ? appcacheDomain : WebInspector.UIString("Local Files"), ["application-cache-storage-tree-item"]);
1296    this._appcacheDomain = appcacheDomain;
1297}
1298
1299WebInspector.ApplicationCacheTreeElement.prototype = {
1300    get itemURL()
1301    {
1302        return "appcache://" + this._appcacheDomain;
1303    },
1304
1305    onselect: function()
1306    {
1307        WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1308        this._storagePanel.showApplicationCache(this, this._appcacheDomain);
1309    }
1310}
1311WebInspector.ApplicationCacheTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1312
1313WebInspector.ResourceRevisionTreeElement = function(storagePanel, revision)
1314{
1315    var title = revision.timestamp ? revision.timestamp.toLocaleTimeString() : WebInspector.UIString("(original)");
1316    WebInspector.BaseStorageTreeElement.call(this, storagePanel, null, title, ["resource-sidebar-tree-item", "resources-category-" + revision.resource.category.name]);
1317    if (revision.timestamp)
1318        this.tooltip = revision.timestamp.toLocaleString();
1319    this._revision = revision;
1320}
1321
1322WebInspector.ResourceRevisionTreeElement.prototype = {
1323    get itemURL()
1324    {
1325        return this._revision.resource.url;
1326    },
1327
1328    onattach: function()
1329    {
1330        WebInspector.BaseStorageTreeElement.prototype.onattach.call(this);
1331        this.listItemElement.draggable = true;
1332        this.listItemElement.addEventListener("dragstart", this._ondragstart.bind(this), false);
1333        this.listItemElement.addEventListener("contextmenu", this._handleContextMenuEvent.bind(this), true);
1334    },
1335
1336    onselect: function()
1337    {
1338        WebInspector.BaseStorageTreeElement.prototype.onselect.call(this);
1339        this._storagePanel._showRevisionView(this._revision);
1340    },
1341
1342    _ondragstart: function(event)
1343    {
1344        if (this._revision.content) {
1345            event.dataTransfer.setData("text/plain", this._revision.content);
1346            event.dataTransfer.effectAllowed = "copy";
1347            return true;
1348        }
1349    },
1350
1351    _handleContextMenuEvent: function(event)
1352    {
1353        var contextMenu = new WebInspector.ContextMenu();
1354        contextMenu.appendItem(WebInspector.UIString("Revert to this revision"), this._revision.revertToThis.bind(this._revision));
1355        contextMenu.show(event);
1356    }
1357}
1358
1359WebInspector.ResourceRevisionTreeElement.prototype.__proto__ = WebInspector.BaseStorageTreeElement.prototype;
1360
1361WebInspector.StorageCategoryView = function()
1362{
1363    WebInspector.View.call(this);
1364
1365    this.element.addStyleClass("storage-view");
1366
1367    this._emptyMsgElement = document.createElement("div");
1368    this._emptyMsgElement.className = "storage-empty-view";
1369    this.element.appendChild(this._emptyMsgElement);
1370}
1371
1372WebInspector.StorageCategoryView.prototype = {
1373    setText: function(text)
1374    {
1375        this._emptyMsgElement.textContent = text;
1376    }
1377}
1378
1379WebInspector.StorageCategoryView.prototype.__proto__ = WebInspector.View.prototype;
1380