| /* |
| * Copyright (C) 2007, 2008, 2010 Apple Inc. All rights reserved. |
| * Copyright (C) 2009 Joseph Pecoraro |
| * Copyright (C) 2013 Samsung Electronics. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of |
| * its contributors may be used to endorse or promote products derived |
| * from this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| // @ts-nocheck |
| // TODO(crbug.com/1011811): Enable TypeScript compiler checks |
| |
| import * as Common from '../common/common.js'; |
| import * as Host from '../host/host.js'; |
| import * as Platform from '../platform/platform.js'; |
| import * as SDK from '../sdk/sdk.js'; |
| import * as SourceFrame from '../source_frame/source_frame.js'; |
| import * as UI from '../ui/ui.js'; |
| |
| import {ApplicationCacheItemsView} from './ApplicationCacheItemsView.js'; |
| import {ApplicationCacheModel, Events as ApplicationCacheModelEvents} from './ApplicationCacheModel.js'; |
| import {AppManifestView} from './AppManifestView.js'; |
| import {BackgroundServiceModel} from './BackgroundServiceModel.js'; |
| import {BackgroundServiceView} from './BackgroundServiceView.js'; |
| import {ClearStorageView} from './ClearStorageView.js'; |
| import {Database as DatabaseModelDatabase, DatabaseModel, Events as DatabaseModelEvents} from './DatabaseModel.js'; // eslint-disable-line no-unused-vars |
| import {DatabaseQueryView, Events as DatabaseQueryViewEvents} from './DatabaseQueryView.js'; |
| import {DatabaseTableView} from './DatabaseTableView.js'; |
| import {DOMStorage, DOMStorageModel, Events as DOMStorageModelEvents} from './DOMStorageModel.js'; // eslint-disable-line no-unused-vars |
| import {FrameDetailsView, OpenedWindowDetailsView, WorkerDetailsView} from './FrameDetailsView.js'; |
| import {Database as IndexedDBModelDatabase, DatabaseId, Events as IndexedDBModelEvents, Index, IndexedDBModel, ObjectStore} from './IndexedDBModel.js'; // eslint-disable-line no-unused-vars |
| import {IDBDatabaseView, IDBDataView} from './IndexedDBViews.js'; |
| import {ServiceWorkerCacheView} from './ServiceWorkerCacheViews.js'; |
| import {ServiceWorkersView} from './ServiceWorkersView.js'; |
| |
| /** |
| * @implements {SDK.SDKModel.Observer} |
| * @unrestricted |
| */ |
| export class ApplicationPanelSidebar extends UI.Widget.VBox { |
| /** |
| * @param {!UI.Panel.PanelWithSidebar} panel |
| */ |
| constructor(panel) { |
| super(); |
| |
| this._panel = panel; |
| |
| this._sidebarTree = new UI.TreeOutline.TreeOutlineInShadow(); |
| this._sidebarTree.element.classList.add('resources-sidebar'); |
| this._sidebarTree.registerRequiredCSS('resources/resourcesSidebar.css', {enableLegacyPatching: true}); |
| this._sidebarTree.element.classList.add('filter-all'); |
| // Listener needs to have been set up before the elements are added |
| this._sidebarTree.addEventListener(UI.TreeOutline.Events.ElementAttached, this._treeElementAdded, this); |
| |
| this.contentElement.appendChild(this._sidebarTree.element); |
| this._applicationTreeElement = this._addSidebarSection(Common.UIString.UIString('Application')); |
| const manifestTreeElement = new AppManifestTreeElement(panel); |
| this._applicationTreeElement.appendChild(manifestTreeElement); |
| this.serviceWorkersTreeElement = new ServiceWorkersTreeElement(panel); |
| this._applicationTreeElement.appendChild(this.serviceWorkersTreeElement); |
| const clearStorageTreeElement = new ClearStorageTreeElement(panel); |
| this._applicationTreeElement.appendChild(clearStorageTreeElement); |
| |
| const storageTreeElement = this._addSidebarSection(Common.UIString.UIString('Storage')); |
| this.localStorageListTreeElement = |
| new StorageCategoryTreeElement(panel, Common.UIString.UIString('Local Storage'), 'LocalStorage'); |
| this.localStorageListTreeElement.setLink( |
| 'https://developers.google.com/web/tools/chrome-devtools/storage/localstorage?utm_source=devtools'); |
| const localStorageIcon = UI.Icon.Icon.create('mediumicon-table', 'resource-tree-item'); |
| this.localStorageListTreeElement.setLeadingIcons([localStorageIcon]); |
| |
| storageTreeElement.appendChild(this.localStorageListTreeElement); |
| this.sessionStorageListTreeElement = |
| new StorageCategoryTreeElement(panel, Common.UIString.UIString('Session Storage'), 'SessionStorage'); |
| this.sessionStorageListTreeElement.setLink( |
| 'https://developers.google.com/web/tools/chrome-devtools/storage/sessionstorage?utm_source=devtools'); |
| const sessionStorageIcon = UI.Icon.Icon.create('mediumicon-table', 'resource-tree-item'); |
| this.sessionStorageListTreeElement.setLeadingIcons([sessionStorageIcon]); |
| |
| storageTreeElement.appendChild(this.sessionStorageListTreeElement); |
| this.indexedDBListTreeElement = new IndexedDBTreeElement(panel); |
| this.indexedDBListTreeElement.setLink( |
| 'https://developers.google.com/web/tools/chrome-devtools/storage/indexeddb?utm_source=devtools'); |
| storageTreeElement.appendChild(this.indexedDBListTreeElement); |
| this.databasesListTreeElement = |
| new StorageCategoryTreeElement(panel, Common.UIString.UIString('Web SQL'), 'Databases'); |
| this.databasesListTreeElement.setLink( |
| 'https://developers.google.com/web/tools/chrome-devtools/storage/websql?utm_source=devtools'); |
| const databaseIcon = UI.Icon.Icon.create('mediumicon-database', 'resource-tree-item'); |
| this.databasesListTreeElement.setLeadingIcons([databaseIcon]); |
| |
| storageTreeElement.appendChild(this.databasesListTreeElement); |
| this.cookieListTreeElement = new StorageCategoryTreeElement(panel, Common.UIString.UIString('Cookies'), 'Cookies'); |
| this.cookieListTreeElement.setLink( |
| 'https://developers.google.com/web/tools/chrome-devtools/storage/cookies?utm_source=devtools'); |
| const cookieIcon = UI.Icon.Icon.create('mediumicon-cookie', 'resource-tree-item'); |
| this.cookieListTreeElement.setLeadingIcons([cookieIcon]); |
| storageTreeElement.appendChild(this.cookieListTreeElement); |
| |
| const cacheTreeElement = this._addSidebarSection(Common.UIString.UIString('Cache')); |
| this.cacheStorageListTreeElement = new ServiceWorkerCacheTreeElement(panel); |
| cacheTreeElement.appendChild(this.cacheStorageListTreeElement); |
| this.applicationCacheListTreeElement = |
| new StorageCategoryTreeElement(panel, Common.UIString.UIString('Application Cache'), 'ApplicationCache'); |
| this.applicationCacheListTreeElement.setLink( |
| 'https://developers.google.com/web/tools/chrome-devtools/storage/applicationcache?utm_source=devtools'); |
| const applicationCacheIcon = UI.Icon.Icon.create('mediumicon-table', 'resource-tree-item'); |
| this.applicationCacheListTreeElement.setLeadingIcons([applicationCacheIcon]); |
| |
| cacheTreeElement.appendChild(this.applicationCacheListTreeElement); |
| |
| if (Root.Runtime.experiments.isEnabled('backgroundServices')) { |
| const backgroundServiceTreeElement = this._addSidebarSection(ls`Background Services`); |
| |
| this.backgroundFetchTreeElement = |
| new BackgroundServiceTreeElement(panel, Protocol.BackgroundService.ServiceName.BackgroundFetch); |
| backgroundServiceTreeElement.appendChild(this.backgroundFetchTreeElement); |
| this.backgroundSyncTreeElement = |
| new BackgroundServiceTreeElement(panel, Protocol.BackgroundService.ServiceName.BackgroundSync); |
| backgroundServiceTreeElement.appendChild(this.backgroundSyncTreeElement); |
| |
| if (Root.Runtime.experiments.isEnabled('backgroundServicesNotifications')) { |
| this.notificationsTreeElement = |
| new BackgroundServiceTreeElement(panel, Protocol.BackgroundService.ServiceName.Notifications); |
| backgroundServiceTreeElement.appendChild(this.notificationsTreeElement); |
| } |
| if (Root.Runtime.experiments.isEnabled('backgroundServicesPaymentHandler')) { |
| this.paymentHandlerTreeElement = |
| new BackgroundServiceTreeElement(panel, Protocol.BackgroundService.ServiceName.PaymentHandler); |
| backgroundServiceTreeElement.appendChild(this.paymentHandlerTreeElement); |
| } |
| this.periodicBackgroundSyncTreeElement = |
| new BackgroundServiceTreeElement(panel, Protocol.BackgroundService.ServiceName.PeriodicBackgroundSync); |
| backgroundServiceTreeElement.appendChild(this.periodicBackgroundSyncTreeElement); |
| if (Root.Runtime.experiments.isEnabled('backgroundServicesPushMessaging')) { |
| this.pushMessagingTreeElement = |
| new BackgroundServiceTreeElement(panel, Protocol.BackgroundService.ServiceName.PushMessaging); |
| backgroundServiceTreeElement.appendChild(this.pushMessagingTreeElement); |
| } |
| } |
| |
| this._resourcesSection = new ResourcesSection(panel, this._addSidebarSection(Common.UIString.UIString('Frames'))); |
| |
| /** @type {!Map.<!DatabaseModelDatabase, !Object.<string, !DatabaseTableView>>} */ |
| this._databaseTableViews = new Map(); |
| /** @type {!Map.<!DatabaseModelDatabase, !DatabaseQueryView>} */ |
| this._databaseQueryViews = new Map(); |
| /** @type {!Map.<!DatabaseModelDatabase, !DatabaseTreeElement>} */ |
| this._databaseTreeElements = new Map(); |
| /** @type {!Map.<!DOMStorage, !DOMStorageTreeElement>} */ |
| this._domStorageTreeElements = new Map(); |
| /** @type {!Object.<string, boolean>} */ |
| this._domains = {}; |
| |
| this._sidebarTree.contentElement.addEventListener('mousemove', this._onmousemove.bind(this), false); |
| this._sidebarTree.contentElement.addEventListener('mouseleave', this._onmouseleave.bind(this), false); |
| |
| SDK.SDKModel.TargetManager.instance().observeTargets(this); |
| SDK.SDKModel.TargetManager.instance().addModelListener( |
| SDK.ResourceTreeModel.ResourceTreeModel, SDK.ResourceTreeModel.Events.FrameNavigated, this._frameNavigated, |
| this); |
| |
| const selection = this._panel.lastSelectedItemPath(); |
| if (!selection.length) { |
| manifestTreeElement.select(); |
| } |
| } |
| |
| /** |
| * @param {string} title |
| * @return {!UI.TreeOutline.TreeElement} |
| */ |
| _addSidebarSection(title) { |
| const treeElement = new UI.TreeOutline.TreeElement(title, true); |
| treeElement.listItemElement.classList.add('storage-group-list-item'); |
| treeElement.setCollapsible(false); |
| treeElement.selectable = false; |
| this._sidebarTree.appendChild(treeElement); |
| return treeElement; |
| } |
| |
| /** |
| * @override |
| * @param {!SDK.SDKModel.Target} target |
| */ |
| targetAdded(target) { |
| if (this._target) { |
| return; |
| } |
| this._target = target; |
| this._databaseModel = target.model(DatabaseModel); |
| if (this._databaseModel) { |
| this._databaseModel.addEventListener(DatabaseModelEvents.DatabaseAdded, this._databaseAdded, this); |
| this._databaseModel.addEventListener(DatabaseModelEvents.DatabasesRemoved, this._resetWebSQL, this); |
| } |
| |
| const resourceTreeModel = target.model(SDK.ResourceTreeModel.ResourceTreeModel); |
| if (!resourceTreeModel) { |
| return; |
| } |
| |
| if (resourceTreeModel.cachedResourcesLoaded()) { |
| this._initialize(); |
| } |
| |
| resourceTreeModel.addEventListener(SDK.ResourceTreeModel.Events.CachedResourcesLoaded, this._initialize, this); |
| resourceTreeModel.addEventListener( |
| SDK.ResourceTreeModel.Events.WillLoadCachedResources, this._resetWithFrames, this); |
| } |
| |
| /** |
| * @override |
| * @param {!SDK.SDKModel.Target} target |
| */ |
| targetRemoved(target) { |
| if (target !== this._target) { |
| return; |
| } |
| delete this._target; |
| |
| const resourceTreeModel = target.model(SDK.ResourceTreeModel.ResourceTreeModel); |
| if (resourceTreeModel) { |
| resourceTreeModel.removeEventListener(SDK.ResourceTreeModel.Events.CachedResourcesLoaded, this._initialize, this); |
| resourceTreeModel.removeEventListener( |
| SDK.ResourceTreeModel.Events.WillLoadCachedResources, this._resetWithFrames, this); |
| } |
| if (this._databaseModel) { |
| this._databaseModel.removeEventListener(DatabaseModelEvents.DatabaseAdded, this._databaseAdded, this); |
| this._databaseModel.removeEventListener(DatabaseModelEvents.DatabasesRemoved, this._resetWebSQL, this); |
| this._databaseModel = null; |
| } |
| |
| this._resetWithFrames(); |
| } |
| |
| /** |
| * @override |
| */ |
| focus() { |
| this._sidebarTree.focus(); |
| } |
| |
| _initialize() { |
| for (const frame of SDK.ResourceTreeModel.ResourceTreeModel.frames()) { |
| this._addCookieDocument(frame); |
| } |
| if (this._databaseModel) { |
| this._databaseModel.enable(); |
| } |
| |
| const cacheStorageModel = this._target.model(SDK.ServiceWorkerCacheModel.ServiceWorkerCacheModel); |
| if (cacheStorageModel) { |
| cacheStorageModel.enable(); |
| } |
| const resourceTreeModel = this._target.model(SDK.ResourceTreeModel.ResourceTreeModel); |
| if (resourceTreeModel) { |
| this._populateApplicationCacheTree(resourceTreeModel); |
| } |
| SDK.SDKModel.TargetManager.instance().observeModels( |
| DOMStorageModel, /** @type {!SDK.SDKModel.SDKModelObserver} */ ({ |
| modelAdded: model => this._domStorageModelAdded(model), |
| modelRemoved: model => this._domStorageModelRemoved(model) |
| })); |
| this.indexedDBListTreeElement._initialize(); |
| SDK.SDKModel.TargetManager.instance().observeModels( |
| IndexedDBModel, /** @type {!SDK.SDKModel.SDKModelObserver} */ ({ |
| modelAdded: model => model.enable(), |
| modelRemoved: model => this.indexedDBListTreeElement.removeIndexedDBForModel(model) |
| })); |
| const serviceWorkerCacheModel = this._target.model(SDK.ServiceWorkerCacheModel.ServiceWorkerCacheModel); |
| this.cacheStorageListTreeElement._initialize(serviceWorkerCacheModel); |
| const backgroundServiceModel = this._target.model(BackgroundServiceModel); |
| if (Root.Runtime.experiments.isEnabled('backgroundServices')) { |
| this.backgroundFetchTreeElement._initialize(backgroundServiceModel); |
| this.backgroundSyncTreeElement._initialize(backgroundServiceModel); |
| if (Root.Runtime.experiments.isEnabled('backgroundServicesNotifications')) { |
| this.notificationsTreeElement._initialize(backgroundServiceModel); |
| } |
| if (Root.Runtime.experiments.isEnabled('backgroundServicesPaymentHandler')) { |
| this.paymentHandlerTreeElement._initialize(backgroundServiceModel); |
| } |
| this.periodicBackgroundSyncTreeElement._initialize(backgroundServiceModel); |
| if (Root.Runtime.experiments.isEnabled('backgroundServicesPushMessaging')) { |
| this.pushMessagingTreeElement._initialize(backgroundServiceModel); |
| } |
| } |
| } |
| |
| /** |
| * @param {!DOMStorageModel} model |
| */ |
| _domStorageModelAdded(model) { |
| model.enable(); |
| model.storages().forEach(this._addDOMStorage.bind(this)); |
| model.addEventListener(DOMStorageModelEvents.DOMStorageAdded, this._domStorageAdded, this); |
| model.addEventListener(DOMStorageModelEvents.DOMStorageRemoved, this._domStorageRemoved, this); |
| } |
| |
| /** |
| * @param {!DOMStorageModel} model |
| */ |
| _domStorageModelRemoved(model) { |
| model.storages().forEach(this._removeDOMStorage.bind(this)); |
| model.removeEventListener(DOMStorageModelEvents.DOMStorageAdded, this._domStorageAdded, this); |
| model.removeEventListener(DOMStorageModelEvents.DOMStorageRemoved, this._domStorageRemoved, this); |
| } |
| |
| _resetWithFrames() { |
| this._resourcesSection.reset(); |
| this._reset(); |
| } |
| |
| _resetWebSQL() { |
| for (const queryView of this._databaseQueryViews.values()) { |
| queryView.removeEventListener(DatabaseQueryViewEvents.SchemaUpdated, event => { |
| this._updateDatabaseTables(event); |
| }, this); |
| } |
| this._databaseTableViews.clear(); |
| this._databaseQueryViews.clear(); |
| this._databaseTreeElements.clear(); |
| this.databasesListTreeElement.removeChildren(); |
| this.databasesListTreeElement.setExpandable(false); |
| } |
| |
| _resetAppCache() { |
| for (const frameId of Object.keys(this._applicationCacheFrameElements)) { |
| this._applicationCacheFrameManifestRemoved({data: frameId}); |
| } |
| this.applicationCacheListTreeElement.setExpandable(false); |
| } |
| |
| /** |
| * @param {!Common.EventTarget.EventTargetEvent} event |
| */ |
| _treeElementAdded(event) { |
| // On tree item selection its itemURL and those of its parents are persisted. |
| // On reload/navigation we check for matches starting from the root on the |
| // path to the current element. Matching nodes are expanded until we hit a |
| // mismatch. This way we ensure that the longest matching path starting from |
| // the root is expanded, even if we cannot match the whole path. |
| const selection = this._panel.lastSelectedItemPath(); |
| if (!selection.length) { |
| return; |
| } |
| const element = event.data; |
| const elementPath = [element]; |
| for (let parent = element.parent; parent && parent.itemURL; parent = parent.parent) { |
| elementPath.push(parent); |
| } |
| |
| let i = selection.length - 1; |
| let j = elementPath.length - 1; |
| while (i >= 0 && j >= 0 && selection[i] === elementPath[j].itemURL) { |
| if (!elementPath[j].expanded) { |
| if (i > 0) { |
| elementPath[j].expand(); |
| } |
| if (!elementPath[j].selected) { |
| elementPath[j].select(); |
| } |
| } |
| i--; |
| j--; |
| } |
| } |
| |
| _reset() { |
| this._domains = {}; |
| this._resetWebSQL(); |
| this.cookieListTreeElement.removeChildren(); |
| } |
| |
| /** |
| * @param {!Common.EventTarget.EventTargetEvent} event |
| */ |
| _frameNavigated(event) { |
| const frame = /** @type {!SDK.ResourceTreeModel.ResourceTreeFrame} */ (event.data); |
| |
| if (frame.isTopFrame()) { |
| this._reset(); |
| } |
| |
| const applicationCacheFrameTreeElement = this._applicationCacheFrameElements[frame.id]; |
| if (applicationCacheFrameTreeElement) { |
| applicationCacheFrameTreeElement.frameNavigated(frame); |
| } |
| this._addCookieDocument(frame); |
| } |
| |
| /** |
| * @param {!Common.EventTarget.EventTargetEvent} event |
| */ |
| _databaseAdded(event) { |
| const database = /** @type {!DatabaseModelDatabase} */ (event.data); |
| const databaseTreeElement = new DatabaseTreeElement(this, database); |
| this._databaseTreeElements.set(database, databaseTreeElement); |
| this.databasesListTreeElement.appendChild(databaseTreeElement); |
| } |
| |
| /** |
| * @param {!SDK.ResourceTreeModel.ResourceTreeFrame} frame |
| */ |
| _addCookieDocument(frame) { |
| // In case the current frame was unreachable, show it's cookies |
| // instead of the error interstitials because they might help to |
| // debug why the frame was unreachable. |
| const urlToParse = frame.unreachableUrl() || frame.url; |
| const parsedURL = Common.ParsedURL.ParsedURL.fromString(urlToParse); |
| if (!parsedURL || (parsedURL.scheme !== 'http' && parsedURL.scheme !== 'https' && parsedURL.scheme !== 'file')) { |
| return; |
| } |
| |
| const domain = parsedURL.securityOrigin(); |
| if (!this._domains[domain]) { |
| this._domains[domain] = true; |
| const cookieDomainTreeElement = new CookieTreeElement(this._panel, frame, domain); |
| this.cookieListTreeElement.appendChild(cookieDomainTreeElement); |
| } |
| } |
| |
| /** |
| * @param {!Common.EventTarget.EventTargetEvent} event |
| */ |
| _domStorageAdded(event) { |
| const domStorage = /** @type {!DOMStorage} */ (event.data); |
| this._addDOMStorage(domStorage); |
| } |
| |
| /** |
| * @param {!DOMStorage} domStorage |
| */ |
| _addDOMStorage(domStorage) { |
| console.assert(!this._domStorageTreeElements.get(domStorage)); |
| |
| const domStorageTreeElement = new DOMStorageTreeElement(this._panel, domStorage); |
| this._domStorageTreeElements.set(domStorage, domStorageTreeElement); |
| if (domStorage.isLocalStorage) { |
| this.localStorageListTreeElement.appendChild(domStorageTreeElement); |
| } else { |
| this.sessionStorageListTreeElement.appendChild(domStorageTreeElement); |
| } |
| } |
| |
| /** |
| * @param {!Common.EventTarget.EventTargetEvent} event |
| */ |
| _domStorageRemoved(event) { |
| const domStorage = /** @type {!DOMStorage} */ (event.data); |
| this._removeDOMStorage(domStorage); |
| } |
| |
| /** |
| * @param {!DOMStorage} domStorage |
| */ |
| _removeDOMStorage(domStorage) { |
| const treeElement = this._domStorageTreeElements.get(domStorage); |
| if (!treeElement) { |
| return; |
| } |
| const wasSelected = treeElement.selected; |
| const parentListTreeElement = treeElement.parent; |
| parentListTreeElement.removeChild(treeElement); |
| if (wasSelected) { |
| parentListTreeElement.select(); |
| } |
| this._domStorageTreeElements.delete(domStorage); |
| } |
| |
| /** |
| * @param {!DatabaseModelDatabase} database |
| */ |
| selectDatabase(database) { |
| if (database) { |
| this._showDatabase(database); |
| this._databaseTreeElements.get(database).select(); |
| } |
| } |
| |
| /** |
| * @param {!SDK.Resource.Resource} resource |
| * @param {number=} line |
| * @param {number=} column |
| * @return {!Promise} |
| */ |
| async showResource(resource, line, column) { |
| await this._resourcesSection.revealResource(resource, line, column); |
| } |
| |
| /** |
| * @param {!DatabaseModelDatabase} database |
| * @param {string=} tableName |
| */ |
| _showDatabase(database, tableName) { |
| if (!database) { |
| return; |
| } |
| |
| let view; |
| if (tableName) { |
| let tableViews = this._databaseTableViews.get(database); |
| if (!tableViews) { |
| tableViews = /** @type {!Object.<string, !DatabaseTableView>} */ ({}); |
| this._databaseTableViews.set(database, tableViews); |
| } |
| view = tableViews[tableName]; |
| if (!view) { |
| view = new DatabaseTableView(database, tableName); |
| tableViews[tableName] = view; |
| } |
| } else { |
| view = this._databaseQueryViews.get(database); |
| if (!view) { |
| view = new DatabaseQueryView(database); |
| this._databaseQueryViews.set(database, view); |
| view.addEventListener(DatabaseQueryViewEvents.SchemaUpdated, event => { |
| this._updateDatabaseTables(event); |
| }, this); |
| } |
| } |
| |
| this._innerShowView(view); |
| } |
| |
| _showApplicationCache(frameId) { |
| if (!this._applicationCacheViews[frameId]) { |
| this._applicationCacheViews[frameId] = new ApplicationCacheItemsView(this._applicationCacheModel, frameId); |
| } |
| |
| this._innerShowView(this._applicationCacheViews[frameId]); |
| } |
| |
| /** |
| * @param {!UI.Widget.Widget} view |
| */ |
| showFileSystem(view) { |
| this._innerShowView(view); |
| } |
| |
| _innerShowView(view) { |
| this._panel.showView(view); |
| } |
| |
| /** |
| * @param {!Common.EventTarget.EventTargetEvent} event |
| */ |
| async _updateDatabaseTables(event) { |
| const database = /** @type {!DatabaseModelDatabase} */ (event.data); |
| |
| if (!database) { |
| return; |
| } |
| |
| const databasesTreeElement = this._databaseTreeElements.get(database); |
| if (!databasesTreeElement) { |
| return; |
| } |
| |
| databasesTreeElement.invalidateChildren(); |
| const tableViews = this._databaseTableViews.get(database); |
| |
| if (!tableViews) { |
| return; |
| } |
| |
| const tableNamesHash = {}; |
| const panel = this._panel; |
| const tableNames = await database.tableNames(); |
| const tableNamesLength = tableNames.length; |
| |
| for (let i = 0; i < tableNamesLength; ++i) { |
| tableNamesHash[tableNames[i]] = true; |
| } |
| |
| for (const tableName in tableViews) { |
| if (!(tableName in tableNamesHash)) { |
| if (panel.visibleView === tableViews[tableName]) { |
| panel.showView(null); |
| } |
| delete tableViews[tableName]; |
| } |
| } |
| |
| await databasesTreeElement.updateChildren(); |
| } |
| |
| /** |
| * @param {!SDK.ResourceTreeModel.ResourceTreeModel} resourceTreeModel |
| */ |
| _populateApplicationCacheTree(resourceTreeModel) { |
| this._applicationCacheModel = this._target.model(ApplicationCacheModel); |
| |
| this._applicationCacheViews = {}; |
| this._applicationCacheFrameElements = {}; |
| this._applicationCacheManifestElements = {}; |
| |
| this._applicationCacheModel.addEventListener( |
| ApplicationCacheModelEvents.FrameManifestAdded, this._applicationCacheFrameManifestAdded, this); |
| this._applicationCacheModel.addEventListener( |
| ApplicationCacheModelEvents.FrameManifestRemoved, this._applicationCacheFrameManifestRemoved, this); |
| this._applicationCacheModel.addEventListener( |
| ApplicationCacheModelEvents.FrameManifestsReset, this._resetAppCache, this); |
| |
| this._applicationCacheModel.addEventListener( |
| ApplicationCacheModelEvents.FrameManifestStatusUpdated, this._applicationCacheFrameManifestStatusChanged, this); |
| this._applicationCacheModel.addEventListener( |
| ApplicationCacheModelEvents.NetworkStateChanged, this._applicationCacheNetworkStateChanged, this); |
| } |
| |
| _applicationCacheFrameManifestAdded(event) { |
| const frameId = event.data; |
| const manifestURL = this._applicationCacheModel.frameManifestURL(frameId); |
| |
| let manifestTreeElement = this._applicationCacheManifestElements[manifestURL]; |
| if (!manifestTreeElement) { |
| manifestTreeElement = new ApplicationCacheManifestTreeElement(this._panel, manifestURL); |
| this.applicationCacheListTreeElement.appendChild(manifestTreeElement); |
| this._applicationCacheManifestElements[manifestURL] = manifestTreeElement; |
| } |
| |
| const model = this._target.model(SDK.ResourceTreeModel.ResourceTreeModel); |
| const frameTreeElement = new ApplicationCacheFrameTreeElement(this, model.frameForId(frameId), manifestURL); |
| manifestTreeElement.appendChild(frameTreeElement); |
| manifestTreeElement.expand(); |
| this._applicationCacheFrameElements[frameId] = frameTreeElement; |
| } |
| |
| _applicationCacheFrameManifestRemoved(event) { |
| const frameId = event.data; |
| const frameTreeElement = this._applicationCacheFrameElements[frameId]; |
| if (!frameTreeElement) { |
| return; |
| } |
| |
| const manifestURL = frameTreeElement.manifestURL; |
| delete this._applicationCacheFrameElements[frameId]; |
| delete this._applicationCacheViews[frameId]; |
| frameTreeElement.parent.removeChild(frameTreeElement); |
| |
| const manifestTreeElement = this._applicationCacheManifestElements[manifestURL]; |
| if (manifestTreeElement.childCount()) { |
| return; |
| } |
| |
| delete this._applicationCacheManifestElements[manifestURL]; |
| manifestTreeElement.parent.removeChild(manifestTreeElement); |
| } |
| |
| _applicationCacheFrameManifestStatusChanged(event) { |
| const frameId = event.data; |
| const status = this._applicationCacheModel.frameManifestStatus(frameId); |
| |
| if (this._applicationCacheViews[frameId]) { |
| this._applicationCacheViews[frameId].updateStatus(status); |
| } |
| } |
| |
| _applicationCacheNetworkStateChanged(event) { |
| const isNowOnline = event.data; |
| |
| for (const manifestURL in this._applicationCacheViews) { |
| this._applicationCacheViews[manifestURL].updateNetworkState(isNowOnline); |
| } |
| } |
| |
| showView(view) { |
| if (view) { |
| this.showResource(view.resource); |
| } |
| } |
| |
| _onmousemove(event) { |
| const nodeUnderMouse = event.target; |
| if (!nodeUnderMouse) { |
| return; |
| } |
| |
| const listNode = nodeUnderMouse.enclosingNodeOrSelfWithNodeName('li'); |
| if (!listNode) { |
| return; |
| } |
| |
| const element = listNode.treeElement; |
| if (this._previousHoveredElement === element) { |
| return; |
| } |
| |
| if (this._previousHoveredElement) { |
| this._previousHoveredElement.hovered = false; |
| delete this._previousHoveredElement; |
| } |
| |
| if (element instanceof FrameTreeElement) { |
| this._previousHoveredElement = element; |
| element.hovered = true; |
| } |
| } |
| |
| _onmouseleave(event) { |
| if (this._previousHoveredElement) { |
| this._previousHoveredElement.hovered = false; |
| delete this._previousHoveredElement; |
| } |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class BaseStorageTreeElement extends UI.TreeOutline.TreeElement { |
| /** |
| * @param {!UI.Panel.PanelWithSidebar} storagePanel |
| * @param {string} title |
| * @param {boolean} expandable |
| */ |
| constructor(storagePanel, title, expandable) { |
| super(title, expandable); |
| this._storagePanel = storagePanel; |
| UI.ARIAUtils.setAccessibleName(this.listItemElement, title); |
| } |
| |
| /** |
| * @override |
| * @param {boolean=} selectedByUser |
| * @return {boolean} |
| */ |
| onselect(selectedByUser) { |
| if (!selectedByUser) { |
| return false; |
| } |
| |
| const path = []; |
| for (let el = this; el; el = el.parent) { |
| const url = el.itemURL; |
| if (!url) { |
| break; |
| } |
| path.push(url); |
| } |
| this._storagePanel.setLastSelectedItemPath(path); |
| |
| return false; |
| } |
| |
| /** |
| * @protected |
| * @param {?UI.Widget.Widget} view |
| */ |
| showView(view) { |
| this._storagePanel.showView(view); |
| } |
| } |
| |
| export class StorageCategoryTreeElement extends BaseStorageTreeElement { |
| /** |
| * @param {!UI.Panel.PanelWithSidebar} storagePanel |
| * @param {string} categoryName |
| * @param {string} settingsKey |
| */ |
| constructor(storagePanel, categoryName, settingsKey) { |
| super(storagePanel, categoryName, false); |
| this._expandedSetting = Common.Settings.Settings.instance().createSetting( |
| 'resources' + settingsKey + 'Expanded', settingsKey === 'Frames'); |
| this._categoryName = categoryName; |
| this._categoryLink = null; |
| } |
| |
| get itemURL() { |
| return 'category://' + this._categoryName; |
| } |
| |
| /** |
| * @param {string} link |
| */ |
| setLink(link) { |
| this._categoryLink = link; |
| } |
| |
| /** |
| * @override |
| * @param {boolean=} selectedByUser |
| * @return {boolean} |
| */ |
| onselect(selectedByUser) { |
| super.onselect(selectedByUser); |
| this._storagePanel.showCategoryView(this._categoryName, this._categoryLink); |
| return false; |
| } |
| |
| /** |
| * @override |
| */ |
| onattach() { |
| super.onattach(); |
| if (this._expandedSetting.get()) { |
| this.expand(); |
| } |
| } |
| |
| /** |
| * @override |
| */ |
| onexpand() { |
| this._expandedSetting.set(true); |
| } |
| |
| /** |
| * @override |
| */ |
| oncollapse() { |
| this._expandedSetting.set(false); |
| } |
| } |
| |
| export class BackgroundServiceTreeElement extends BaseStorageTreeElement { |
| /** |
| * @param {!UI.Panel.PanelWithSidebar} storagePanel |
| * @param {!Protocol.BackgroundService.ServiceName} serviceName |
| */ |
| constructor(storagePanel, serviceName) { |
| super(storagePanel, BackgroundServiceView.getUIString(serviceName), false); |
| |
| /** @const {!Protocol.BackgroundService.ServiceName} */ |
| this._serviceName = serviceName; |
| |
| /** @type {boolean} Whether the element has been selected. */ |
| this._selected = false; |
| |
| /** @type {?BackgroundServiceView} */ |
| this._view = null; |
| |
| /** @private {?BackgroundServiceModel} */ |
| this._model = null; |
| |
| const backgroundServiceIcon = UI.Icon.Icon.create(this._getIconType(), 'resource-tree-item'); |
| this.setLeadingIcons([backgroundServiceIcon]); |
| } |
| |
| /** |
| * @return {string} |
| */ |
| _getIconType() { |
| switch (this._serviceName) { |
| case Protocol.BackgroundService.ServiceName.BackgroundFetch: |
| return 'mediumicon-fetch'; |
| case Protocol.BackgroundService.ServiceName.BackgroundSync: |
| return 'mediumicon-sync'; |
| case Protocol.BackgroundService.ServiceName.PushMessaging: |
| return 'mediumicon-cloud'; |
| case Protocol.BackgroundService.ServiceName.Notifications: |
| return 'mediumicon-bell'; |
| case Protocol.BackgroundService.ServiceName.PaymentHandler: |
| return 'mediumicon-payment'; |
| case Protocol.BackgroundService.ServiceName.PeriodicBackgroundSync: |
| return 'mediumicon-schedule'; |
| default: |
| console.error(`Service ${this._serviceName} does not have a dedicated icon`); |
| return 'mediumicon-table'; |
| } |
| } |
| |
| /** |
| * @param {?BackgroundServiceModel} model |
| */ |
| _initialize(model) { |
| this._model = model; |
| // Show the view if the model was initialized after selection. |
| if (this._selected && !this._view) { |
| this.onselect(false); |
| } |
| } |
| |
| /** |
| * @return {string} |
| */ |
| get itemURL() { |
| return `background-service://${this._serviceName}`; |
| } |
| |
| /** |
| * @override |
| * @param {boolean=} selectedByUser |
| * @return {boolean} |
| */ |
| onselect(selectedByUser) { |
| super.onselect(selectedByUser); |
| this._selected = true; |
| |
| if (!this._model) { |
| return false; |
| } |
| |
| if (!this._view) { |
| this._view = new BackgroundServiceView(this._serviceName, this._model); |
| } |
| this.showView(this._view); |
| UI.Context.Context.instance().setFlavor(BackgroundServiceView, this._view); |
| return false; |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class DatabaseTreeElement extends BaseStorageTreeElement { |
| /** |
| * @param {!ApplicationPanelSidebar} sidebar |
| * @param {!DatabaseModelDatabase} database |
| */ |
| constructor(sidebar, database) { |
| super(sidebar._panel, database.name, true); |
| this._sidebar = sidebar; |
| this._database = database; |
| |
| const icon = UI.Icon.Icon.create('mediumicon-database', 'resource-tree-item'); |
| this.setLeadingIcons([icon]); |
| } |
| |
| get itemURL() { |
| return 'database://' + encodeURI(this._database.name); |
| } |
| |
| /** |
| * @override |
| * @param {boolean=} selectedByUser |
| * @return {boolean} |
| */ |
| onselect(selectedByUser) { |
| super.onselect(selectedByUser); |
| this._sidebar._showDatabase(this._database); |
| return false; |
| } |
| |
| /** |
| * @override |
| */ |
| onexpand() { |
| this.updateChildren(); |
| } |
| |
| async updateChildren() { |
| this.removeChildren(); |
| const tableNames = await this._database.tableNames(); |
| for (const tableName of tableNames) { |
| this.appendChild(new DatabaseTableTreeElement(this._sidebar, this._database, tableName)); |
| } |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class DatabaseTableTreeElement extends BaseStorageTreeElement { |
| /** |
| * @param {!ApplicationPanelSidebar} sidebar |
| * @param {!DatabaseModelDatabase} database |
| * @param {string} tableName |
| */ |
| constructor(sidebar, database, tableName) { |
| super(sidebar._panel, tableName, false); |
| this._sidebar = sidebar; |
| this._database = database; |
| this._tableName = tableName; |
| const icon = UI.Icon.Icon.create('mediumicon-table', 'resource-tree-item'); |
| this.setLeadingIcons([icon]); |
| } |
| |
| get itemURL() { |
| return 'database://' + encodeURI(this._database.name) + '/' + encodeURI(this._tableName); |
| } |
| |
| /** |
| * @override |
| * @param {boolean=} selectedByUser |
| * @return {boolean} |
| */ |
| onselect(selectedByUser) { |
| super.onselect(selectedByUser); |
| this._sidebar._showDatabase(this._database, this._tableName); |
| return false; |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class ServiceWorkerCacheTreeElement extends StorageCategoryTreeElement { |
| /** |
| * @param {!UI.Panel.PanelWithSidebar} storagePanel |
| */ |
| constructor(storagePanel) { |
| super(storagePanel, Common.UIString.UIString('Cache Storage'), 'CacheStorage'); |
| const icon = UI.Icon.Icon.create('mediumicon-database', 'resource-tree-item'); |
| this.setLeadingIcons([icon]); |
| /** @type {?SDK.ServiceWorkerCacheModel.ServiceWorkerCacheModel} */ |
| this._swCacheModel = null; |
| } |
| |
| /** |
| * @param {?SDK.ServiceWorkerCacheModel.ServiceWorkerCacheModel} model |
| */ |
| _initialize(model) { |
| /** @type {!Set.<!SWCacheTreeElement>} */ |
| this._swCacheTreeElements = new Set(); |
| this._swCacheModel = model; |
| if (model) { |
| for (const cache of model.caches()) { |
| this._addCache(model, cache); |
| } |
| } |
| SDK.SDKModel.TargetManager.instance().addModelListener( |
| SDK.ServiceWorkerCacheModel.ServiceWorkerCacheModel, SDK.ServiceWorkerCacheModel.Events.CacheAdded, |
| this._cacheAdded, this); |
| SDK.SDKModel.TargetManager.instance().addModelListener( |
| SDK.ServiceWorkerCacheModel.ServiceWorkerCacheModel, SDK.ServiceWorkerCacheModel.Events.CacheRemoved, |
| this._cacheRemoved, this); |
| } |
| |
| /** |
| * @override |
| */ |
| onattach() { |
| super.onattach(); |
| this.listItemElement.addEventListener('contextmenu', this._handleContextMenuEvent.bind(this), true); |
| } |
| |
| _handleContextMenuEvent(event) { |
| const contextMenu = new UI.ContextMenu.ContextMenu(event); |
| contextMenu.defaultSection().appendItem(Common.UIString.UIString('Refresh Caches'), this._refreshCaches.bind(this)); |
| contextMenu.show(); |
| } |
| |
| _refreshCaches() { |
| if (this._swCacheModel) { |
| this._swCacheModel.refreshCacheNames(); |
| } |
| } |
| |
| /** |
| * @param {!Common.EventTarget.EventTargetEvent} event |
| */ |
| _cacheAdded(event) { |
| const cache = /** @type {!SDK.ServiceWorkerCacheModel.Cache} */ (event.data.cache); |
| const model = /** @type {!SDK.ServiceWorkerCacheModel.ServiceWorkerCacheModel} */ (event.data.model); |
| this._addCache(model, cache); |
| } |
| |
| /** |
| * @param {!SDK.ServiceWorkerCacheModel.ServiceWorkerCacheModel} model |
| * @param {!SDK.ServiceWorkerCacheModel.Cache} cache |
| */ |
| _addCache(model, cache) { |
| const swCacheTreeElement = new SWCacheTreeElement(this._storagePanel, model, cache); |
| this._swCacheTreeElements.add(swCacheTreeElement); |
| this.appendChild(swCacheTreeElement); |
| } |
| |
| /** |
| * @param {!Common.EventTarget.EventTargetEvent} event |
| */ |
| _cacheRemoved(event) { |
| const cache = /** @type {!SDK.ServiceWorkerCacheModel.Cache} */ (event.data.cache); |
| const model = /** @type {!SDK.ServiceWorkerCacheModel.ServiceWorkerCacheModel} */ (event.data.model); |
| |
| const swCacheTreeElement = this._cacheTreeElement(model, cache); |
| if (!swCacheTreeElement) { |
| return; |
| } |
| |
| this.removeChild(swCacheTreeElement); |
| this._swCacheTreeElements.delete(swCacheTreeElement); |
| this.setExpandable(this.childCount() > 0); |
| } |
| |
| /** |
| * @param {!SDK.ServiceWorkerCacheModel.ServiceWorkerCacheModel} model |
| * @param {!SDK.ServiceWorkerCacheModel.Cache} cache |
| * @return {?SWCacheTreeElement} |
| */ |
| _cacheTreeElement(model, cache) { |
| for (const cacheTreeElement of this._swCacheTreeElements) { |
| if (cacheTreeElement._cache.equals(cache) && cacheTreeElement._model === model) { |
| return cacheTreeElement; |
| } |
| } |
| return null; |
| } |
| } |
| |
| export class SWCacheTreeElement extends BaseStorageTreeElement { |
| /** |
| * @param {!UI.Panel.PanelWithSidebar} storagePanel |
| * @param {!SDK.ServiceWorkerCacheModel.ServiceWorkerCacheModel} model |
| * @param {!SDK.ServiceWorkerCacheModel.Cache} cache |
| */ |
| constructor(storagePanel, model, cache) { |
| super(storagePanel, cache.cacheName + ' - ' + cache.securityOrigin, false); |
| this._model = model; |
| this._cache = cache; |
| /** @type {?ServiceWorkerCacheView} */ |
| this._view = null; |
| const icon = UI.Icon.Icon.create('mediumicon-table', 'resource-tree-item'); |
| this.setLeadingIcons([icon]); |
| } |
| |
| get itemURL() { |
| // I don't think this will work at all. |
| return 'cache://' + this._cache.cacheId; |
| } |
| |
| /** |
| * @override |
| */ |
| onattach() { |
| super.onattach(); |
| this.listItemElement.addEventListener('contextmenu', this._handleContextMenuEvent.bind(this), true); |
| } |
| |
| _handleContextMenuEvent(event) { |
| const contextMenu = new UI.ContextMenu.ContextMenu(event); |
| contextMenu.defaultSection().appendItem(Common.UIString.UIString('Delete'), this._clearCache.bind(this)); |
| contextMenu.show(); |
| } |
| |
| _clearCache() { |
| this._model.deleteCache(this._cache); |
| } |
| |
| /** |
| * @param {!SDK.ServiceWorkerCacheModel.Cache} cache |
| */ |
| update(cache) { |
| this._cache = cache; |
| if (this._view) { |
| this._view.update(cache); |
| } |
| } |
| |
| /** |
| * @override |
| * @param {boolean=} selectedByUser |
| * @return {boolean} |
| */ |
| onselect(selectedByUser) { |
| super.onselect(selectedByUser); |
| if (!this._view) { |
| this._view = new ServiceWorkerCacheView(this._model, this._cache); |
| } |
| |
| this.showView(this._view); |
| return false; |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class ServiceWorkersTreeElement extends BaseStorageTreeElement { |
| /** |
| * @param {!UI.Panel.PanelWithSidebar} storagePanel |
| */ |
| constructor(storagePanel) { |
| super(storagePanel, Common.UIString.UIString('Service Workers'), false); |
| const icon = UI.Icon.Icon.create('mediumicon-service-worker', 'resource-tree-item'); |
| this.setLeadingIcons([icon]); |
| } |
| |
| /** |
| * @return {string} |
| */ |
| get itemURL() { |
| return 'service-workers://'; |
| } |
| |
| /** |
| * @override |
| * @param {boolean=} selectedByUser |
| * @return {boolean} |
| */ |
| onselect(selectedByUser) { |
| super.onselect(selectedByUser); |
| if (!this._view) { |
| this._view = new ServiceWorkersView(); |
| } |
| this.showView(this._view); |
| return false; |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class AppManifestTreeElement extends BaseStorageTreeElement { |
| /** |
| * @param {!UI.Panel.PanelWithSidebar} storagePanel |
| */ |
| constructor(storagePanel) { |
| super(storagePanel, Common.UIString.UIString('Manifest'), false); |
| const icon = UI.Icon.Icon.create('mediumicon-manifest', 'resource-tree-item'); |
| this.setLeadingIcons([icon]); |
| } |
| |
| /** |
| * @return {string} |
| */ |
| get itemURL() { |
| return 'manifest://'; |
| } |
| |
| /** |
| * @override |
| * @param {boolean=} selectedByUser |
| * @return {boolean} |
| */ |
| onselect(selectedByUser) { |
| super.onselect(selectedByUser); |
| if (!this._view) { |
| this._view = new AppManifestView(); |
| } |
| this.showView(this._view); |
| return false; |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class ClearStorageTreeElement extends BaseStorageTreeElement { |
| /** |
| * @param {!UI.Panel.PanelWithSidebar} storagePanel |
| */ |
| constructor(storagePanel) { |
| super(storagePanel, Common.UIString.UIString('Clear storage'), false); |
| const icon = UI.Icon.Icon.create('mediumicon-clear-storage', 'resource-tree-item'); |
| this.setLeadingIcons([icon]); |
| } |
| |
| /** |
| * @return {string} |
| */ |
| get itemURL() { |
| return 'clear-storage://'; |
| } |
| |
| /** |
| * @override |
| * @param {boolean=} selectedByUser |
| * @return {boolean} |
| */ |
| onselect(selectedByUser) { |
| super.onselect(selectedByUser); |
| if (!this._view) { |
| this._view = new ClearStorageView(); |
| } |
| this.showView(this._view); |
| return false; |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class IndexedDBTreeElement extends StorageCategoryTreeElement { |
| /** |
| * @param {!UI.Panel.PanelWithSidebar} storagePanel |
| */ |
| constructor(storagePanel) { |
| super(storagePanel, Common.UIString.UIString('IndexedDB'), 'IndexedDB'); |
| const icon = UI.Icon.Icon.create('mediumicon-database', 'resource-tree-item'); |
| this.setLeadingIcons([icon]); |
| } |
| |
| _initialize() { |
| SDK.SDKModel.TargetManager.instance().addModelListener( |
| IndexedDBModel, IndexedDBModelEvents.DatabaseAdded, this._indexedDBAdded, this); |
| SDK.SDKModel.TargetManager.instance().addModelListener( |
| IndexedDBModel, IndexedDBModelEvents.DatabaseRemoved, this._indexedDBRemoved, this); |
| SDK.SDKModel.TargetManager.instance().addModelListener( |
| IndexedDBModel, IndexedDBModelEvents.DatabaseLoaded, this._indexedDBLoaded, this); |
| SDK.SDKModel.TargetManager.instance().addModelListener( |
| IndexedDBModel, IndexedDBModelEvents.IndexedDBContentUpdated, this._indexedDBContentUpdated, this); |
| // TODO(szuend): Replace with a Set once two web tests no longer directly access this private |
| // variable (indexeddb/live-update-indexeddb-content.js, indexeddb/delete-entry.js). |
| /** @type {!Array.<!IDBDatabaseTreeElement>} */ |
| this._idbDatabaseTreeElements = []; |
| |
| for (const indexedDBModel of SDK.SDKModel.TargetManager.instance().models(IndexedDBModel)) { |
| const databases = indexedDBModel.databases(); |
| for (let j = 0; j < databases.length; ++j) { |
| this._addIndexedDB(indexedDBModel, databases[j]); |
| } |
| } |
| } |
| |
| /** |
| * @param {!IndexedDBModel} model |
| */ |
| removeIndexedDBForModel(model) { |
| const idbDatabaseTreeElements = this._idbDatabaseTreeElements.filter(element => element._model === model); |
| for (const idbDatabaseTreeElement of idbDatabaseTreeElements) { |
| this._removeIDBDatabaseTreeElement(idbDatabaseTreeElement); |
| } |
| } |
| |
| /** |
| * @override |
| */ |
| onattach() { |
| super.onattach(); |
| this.listItemElement.addEventListener('contextmenu', this._handleContextMenuEvent.bind(this), true); |
| } |
| |
| _handleContextMenuEvent(event) { |
| const contextMenu = new UI.ContextMenu.ContextMenu(event); |
| contextMenu.defaultSection().appendItem( |
| Common.UIString.UIString('Refresh IndexedDB'), this.refreshIndexedDB.bind(this)); |
| contextMenu.show(); |
| } |
| |
| refreshIndexedDB() { |
| for (const indexedDBModel of SDK.SDKModel.TargetManager.instance().models(IndexedDBModel)) { |
| indexedDBModel.refreshDatabaseNames(); |
| } |
| } |
| |
| /** |
| * @param {!Common.EventTarget.EventTargetEvent} event |
| */ |
| _indexedDBAdded(event) { |
| const databaseId = /** @type {!DatabaseId} */ (event.data.databaseId); |
| const model = /** @type {!IndexedDBModel} */ (event.data.model); |
| this._addIndexedDB(model, databaseId); |
| } |
| |
| /** |
| * @param {!IndexedDBModel} model |
| * @param {!DatabaseId} databaseId |
| */ |
| _addIndexedDB(model, databaseId) { |
| const idbDatabaseTreeElement = new IDBDatabaseTreeElement(this._storagePanel, model, databaseId); |
| this._idbDatabaseTreeElements.push(idbDatabaseTreeElement); |
| this.appendChild(idbDatabaseTreeElement); |
| model.refreshDatabase(databaseId); |
| } |
| |
| /** |
| * @param {!Common.EventTarget.EventTargetEvent} event |
| */ |
| _indexedDBRemoved(event) { |
| const databaseId = /** @type {!DatabaseId} */ (event.data.databaseId); |
| const model = /** @type {!IndexedDBModel} */ (event.data.model); |
| |
| const idbDatabaseTreeElement = this._idbDatabaseTreeElement(model, databaseId); |
| if (!idbDatabaseTreeElement) { |
| return; |
| } |
| this._removeIDBDatabaseTreeElement(idbDatabaseTreeElement); |
| } |
| |
| /** |
| * @param {!IDBDatabaseTreeElement} idbDatabaseTreeElement |
| */ |
| _removeIDBDatabaseTreeElement(idbDatabaseTreeElement) { |
| idbDatabaseTreeElement.clear(); |
| this.removeChild(idbDatabaseTreeElement); |
| Platform.ArrayUtilities.removeElement(this._idbDatabaseTreeElements, idbDatabaseTreeElement); |
| this.setExpandable(this.childCount() > 0); |
| } |
| |
| /** |
| * @param {!Common.EventTarget.EventTargetEvent} event |
| */ |
| _indexedDBLoaded(event) { |
| const database = /** @type {!IndexedDBModelDatabase} */ (event.data.database); |
| const model = /** @type {!IndexedDBModel} */ (event.data.model); |
| const entriesUpdated = /** @type {boolean} */ (event.data.entriesUpdated); |
| |
| const idbDatabaseTreeElement = this._idbDatabaseTreeElement(model, database.databaseId); |
| if (!idbDatabaseTreeElement) { |
| return; |
| } |
| idbDatabaseTreeElement.update(database, entriesUpdated); |
| this._indexedDBLoadedForTest(); |
| } |
| |
| _indexedDBLoadedForTest() { |
| // For sniffing in tests. |
| } |
| |
| /** |
| * @param {!Common.EventTarget.EventTargetEvent} event |
| */ |
| _indexedDBContentUpdated(event) { |
| const databaseId = /** @type {!DatabaseId} */ (event.data.databaseId); |
| const objectStoreName = /** @type {string} */ (event.data.objectStoreName); |
| const model = /** @type {!IndexedDBModel} */ (event.data.model); |
| |
| const idbDatabaseTreeElement = this._idbDatabaseTreeElement(model, databaseId); |
| if (!idbDatabaseTreeElement) { |
| return; |
| } |
| idbDatabaseTreeElement.indexedDBContentUpdated(objectStoreName); |
| } |
| |
| /** |
| * @param {!IndexedDBModel} model |
| * @param {!DatabaseId} databaseId |
| * @return {?IDBDatabaseTreeElement} |
| */ |
| _idbDatabaseTreeElement(model, databaseId) { |
| return this._idbDatabaseTreeElements.find(x => x._databaseId.equals(databaseId) && x._model === model) || null; |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class IDBDatabaseTreeElement extends BaseStorageTreeElement { |
| /** |
| * @param {!UI.Panel.PanelWithSidebar} storagePanel |
| * @param {!IndexedDBModel} model |
| * @param {!DatabaseId} databaseId |
| */ |
| constructor(storagePanel, model, databaseId) { |
| super(storagePanel, databaseId.name + ' - ' + databaseId.securityOrigin, false); |
| this._model = model; |
| this._databaseId = databaseId; |
| this._idbObjectStoreTreeElements = {}; |
| const icon = UI.Icon.Icon.create('mediumicon-database', 'resource-tree-item'); |
| this.setLeadingIcons([icon]); |
| this._model.addEventListener(IndexedDBModelEvents.DatabaseNamesRefreshed, this._refreshIndexedDB, this); |
| } |
| |
| get itemURL() { |
| return 'indexedDB://' + this._databaseId.securityOrigin + '/' + this._databaseId.name; |
| } |
| |
| /** |
| * @override |
| */ |
| onattach() { |
| super.onattach(); |
| this.listItemElement.addEventListener('contextmenu', this._handleContextMenuEvent.bind(this), true); |
| } |
| |
| _handleContextMenuEvent(event) { |
| const contextMenu = new UI.ContextMenu.ContextMenu(event); |
| contextMenu.defaultSection().appendItem( |
| Common.UIString.UIString('Refresh IndexedDB'), this._refreshIndexedDB.bind(this)); |
| contextMenu.show(); |
| } |
| |
| _refreshIndexedDB() { |
| this._model.refreshDatabase(this._databaseId); |
| } |
| |
| /** |
| * @param {string} objectStoreName |
| */ |
| indexedDBContentUpdated(objectStoreName) { |
| if (this._idbObjectStoreTreeElements[objectStoreName]) { |
| this._idbObjectStoreTreeElements[objectStoreName].markNeedsRefresh(); |
| } |
| } |
| |
| /** |
| * @param {!IndexedDBModelDatabase} database |
| * @param {boolean} entriesUpdated |
| */ |
| update(database, entriesUpdated) { |
| this._database = database; |
| const objectStoreNames = {}; |
| for (const objectStoreName of [...this._database.objectStores.keys()].sort()) { |
| const objectStore = this._database.objectStores.get(objectStoreName); |
| objectStoreNames[objectStore.name] = true; |
| if (!this._idbObjectStoreTreeElements[objectStore.name]) { |
| const idbObjectStoreTreeElement = |
| new IDBObjectStoreTreeElement(this._storagePanel, this._model, this._databaseId, objectStore); |
| this._idbObjectStoreTreeElements[objectStore.name] = idbObjectStoreTreeElement; |
| this.appendChild(idbObjectStoreTreeElement); |
| } |
| this._idbObjectStoreTreeElements[objectStore.name].update(objectStore, entriesUpdated); |
| } |
| for (const objectStoreName in this._idbObjectStoreTreeElements) { |
| if (!objectStoreNames[objectStoreName]) { |
| this._objectStoreRemoved(objectStoreName); |
| } |
| } |
| |
| if (this._view) { |
| this._view.update(database); |
| } |
| |
| this._updateTooltip(); |
| } |
| |
| _updateTooltip() { |
| if (Object.keys(this._idbObjectStoreTreeElements).length === 0) { |
| this.tooltip = ls`Version: ${this._database.version} (empty)`; |
| } else { |
| this.tooltip = ls`Version: ${this._database.version}`; |
| } |
| } |
| |
| /** |
| * @override |
| * @param {boolean=} selectedByUser |
| * @return {boolean} |
| */ |
| onselect(selectedByUser) { |
| super.onselect(selectedByUser); |
| if (!this._view) { |
| this._view = new IDBDatabaseView(this._model, this._database); |
| } |
| |
| this.showView(this._view); |
| return false; |
| } |
| |
| /** |
| * @param {string} objectStoreName |
| */ |
| _objectStoreRemoved(objectStoreName) { |
| const objectStoreTreeElement = this._idbObjectStoreTreeElements[objectStoreName]; |
| objectStoreTreeElement.clear(); |
| this.removeChild(objectStoreTreeElement); |
| delete this._idbObjectStoreTreeElements[objectStoreName]; |
| this._updateTooltip(); |
| } |
| |
| clear() { |
| for (const objectStoreName in this._idbObjectStoreTreeElements) { |
| this._objectStoreRemoved(objectStoreName); |
| } |
| } |
| } |
| |
| export class IDBObjectStoreTreeElement extends BaseStorageTreeElement { |
| /** |
| * @param {!UI.Panel.PanelWithSidebar} storagePanel |
| * @param {!IndexedDBModel} model |
| * @param {!DatabaseId} databaseId |
| * @param {!ObjectStore} objectStore |
| */ |
| constructor(storagePanel, model, databaseId, objectStore) { |
| super(storagePanel, objectStore.name, false); |
| this._model = model; |
| this._databaseId = databaseId; |
| this._idbIndexTreeElements = {}; |
| this._objectStore = objectStore; |
| /** @type {?IDBDataView} */ |
| this._view = null; |
| const icon = UI.Icon.Icon.create('mediumicon-table', 'resource-tree-item'); |
| this.setLeadingIcons([icon]); |
| } |
| |
| get itemURL() { |
| return 'indexedDB://' + this._databaseId.securityOrigin + '/' + this._databaseId.name + '/' + |
| this._objectStore.name; |
| } |
| |
| /** |
| * @override |
| */ |
| onattach() { |
| super.onattach(); |
| this.listItemElement.addEventListener('contextmenu', this._handleContextMenuEvent.bind(this), true); |
| } |
| |
| markNeedsRefresh() { |
| if (this._view) { |
| this._view.markNeedsRefresh(); |
| } |
| for (const indexName in this._idbIndexTreeElements) { |
| this._idbIndexTreeElements[indexName].markNeedsRefresh(); |
| } |
| } |
| |
| _handleContextMenuEvent(event) { |
| const contextMenu = new UI.ContextMenu.ContextMenu(event); |
| contextMenu.defaultSection().appendItem(Common.UIString.UIString('Clear'), this._clearObjectStore.bind(this)); |
| contextMenu.show(); |
| } |
| |
| _refreshObjectStore() { |
| if (this._view) { |
| this._view.refreshData(); |
| } |
| for (const indexName in this._idbIndexTreeElements) { |
| this._idbIndexTreeElements[indexName].refreshIndex(); |
| } |
| } |
| |
| async _clearObjectStore() { |
| await this._model.clearObjectStore(this._databaseId, this._objectStore.name); |
| this.update(this._objectStore, true); |
| } |
| |
| /** |
| * @param {!ObjectStore} objectStore |
| * @param {boolean} entriesUpdated |
| */ |
| update(objectStore, entriesUpdated) { |
| this._objectStore = objectStore; |
| |
| const indexNames = {}; |
| for (const index of this._objectStore.indexes.values()) { |
| indexNames[index.name] = true; |
| if (!this._idbIndexTreeElements[index.name]) { |
| const idbIndexTreeElement = new IDBIndexTreeElement( |
| this._storagePanel, this._model, this._databaseId, this._objectStore, index, |
| this._refreshObjectStore.bind(this)); |
| this._idbIndexTreeElements[index.name] = idbIndexTreeElement; |
| this.appendChild(idbIndexTreeElement); |
| } |
| this._idbIndexTreeElements[index.name].update(this._objectStore, index, entriesUpdated); |
| } |
| for (const indexName in this._idbIndexTreeElements) { |
| if (!indexNames[indexName]) { |
| this._indexRemoved(indexName); |
| } |
| } |
| for (const indexName in this._idbIndexTreeElements) { |
| if (!indexNames[indexName]) { |
| this.removeChild(this._idbIndexTreeElements[indexName]); |
| delete this._idbIndexTreeElements[indexName]; |
| } |
| } |
| |
| if (this.childCount()) { |
| this.expand(); |
| } |
| |
| if (this._view && entriesUpdated) { |
| this._view.update(this._objectStore, null); |
| } |
| |
| this._updateTooltip(); |
| } |
| |
| _updateTooltip() { |
| const keyPathString = this._objectStore.keyPathString; |
| let tooltipString = keyPathString !== null ? ls`Key path: ${keyPathString}` : ''; |
| if (this._objectStore.autoIncrement) { |
| tooltipString += '\n' + Common.UIString.UIString('autoIncrement'); |
| } |
| this.tooltip = tooltipString; |
| } |
| |
| /** |
| * @override |
| * @param {boolean=} selectedByUser |
| * @return {boolean} |
| */ |
| onselect(selectedByUser) { |
| super.onselect(selectedByUser); |
| if (!this._view) { |
| this._view = |
| new IDBDataView(this._model, this._databaseId, this._objectStore, null, this._refreshObjectStore.bind(this)); |
| } |
| |
| this.showView(this._view); |
| return false; |
| } |
| |
| /** |
| * @param {string} indexName |
| */ |
| _indexRemoved(indexName) { |
| const indexTreeElement = this._idbIndexTreeElements[indexName]; |
| indexTreeElement.clear(); |
| this.removeChild(indexTreeElement); |
| delete this._idbIndexTreeElements[indexName]; |
| } |
| |
| clear() { |
| for (const indexName in this._idbIndexTreeElements) { |
| this._indexRemoved(indexName); |
| } |
| if (this._view) { |
| this._view.clear(); |
| } |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class IDBIndexTreeElement extends BaseStorageTreeElement { |
| /** |
| * @param {!UI.Panel.PanelWithSidebar} storagePanel |
| * @param {!IndexedDBModel} model |
| * @param {!DatabaseId} databaseId |
| * @param {!ObjectStore} objectStore |
| * @param {!Index} index |
| * @param {function()} refreshObjectStore |
| */ |
| constructor(storagePanel, model, databaseId, objectStore, index, refreshObjectStore) { |
| super(storagePanel, index.name, false); |
| this._model = model; |
| this._databaseId = databaseId; |
| this._objectStore = objectStore; |
| this._index = index; |
| this._refreshObjectStore = refreshObjectStore; |
| } |
| |
| get itemURL() { |
| return 'indexedDB://' + this._databaseId.securityOrigin + '/' + this._databaseId.name + '/' + |
| this._objectStore.name + '/' + this._index.name; |
| } |
| |
| markNeedsRefresh() { |
| if (this._view) { |
| this._view.markNeedsRefresh(); |
| } |
| } |
| |
| refreshIndex() { |
| if (this._view) { |
| this._view.refreshData(); |
| } |
| } |
| |
| /** |
| * @param {!ObjectStore} objectStore |
| * @param {!Index} index |
| * @param {boolean} entriesUpdated |
| */ |
| update(objectStore, index, entriesUpdated) { |
| this._objectStore = objectStore; |
| this._index = index; |
| |
| if (this._view && entriesUpdated) { |
| this._view.update(this._objectStore, this._index); |
| } |
| |
| this._updateTooltip(); |
| } |
| |
| _updateTooltip() { |
| const tooltipLines = []; |
| const keyPathString = this._index.keyPathString; |
| tooltipLines.push(ls`Key path: ${keyPathString}`); |
| if (this._index.unique) { |
| tooltipLines.push(Common.UIString.UIString('unique')); |
| } |
| if (this._index.multiEntry) { |
| tooltipLines.push(Common.UIString.UIString('multiEntry')); |
| } |
| this.tooltip = tooltipLines.join('\n'); |
| } |
| |
| /** |
| * @override |
| * @param {boolean=} selectedByUser |
| * @return {boolean} |
| */ |
| onselect(selectedByUser) { |
| super.onselect(selectedByUser); |
| if (!this._view) { |
| this._view = |
| new IDBDataView(this._model, this._databaseId, this._objectStore, this._index, this._refreshObjectStore); |
| } |
| |
| this.showView(this._view); |
| return false; |
| } |
| |
| clear() { |
| if (this._view) { |
| this._view.clear(); |
| } |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class DOMStorageTreeElement extends BaseStorageTreeElement { |
| /** |
| * @param {!UI.Panel.PanelWithSidebar} storagePanel |
| * @param {!DOMStorage} domStorage |
| */ |
| constructor(storagePanel, domStorage) { |
| super( |
| storagePanel, domStorage.securityOrigin ? domStorage.securityOrigin : Common.UIString.UIString('Local Files'), |
| false); |
| this._domStorage = domStorage; |
| const icon = UI.Icon.Icon.create('mediumicon-table', 'resource-tree-item'); |
| this.setLeadingIcons([icon]); |
| } |
| |
| get itemURL() { |
| return 'storage://' + this._domStorage.securityOrigin + '/' + |
| (this._domStorage.isLocalStorage ? 'local' : 'session'); |
| } |
| |
| /** |
| * @override |
| * @param {boolean=} selectedByUser |
| * @return {boolean} |
| */ |
| onselect(selectedByUser) { |
| super.onselect(selectedByUser); |
| this._storagePanel.showDOMStorage(this._domStorage); |
| return false; |
| } |
| |
| /** |
| * @override |
| */ |
| onattach() { |
| super.onattach(); |
| this.listItemElement.addEventListener('contextmenu', this._handleContextMenuEvent.bind(this), true); |
| } |
| |
| _handleContextMenuEvent(event) { |
| const contextMenu = new UI.ContextMenu.ContextMenu(event); |
| contextMenu.defaultSection().appendItem(Common.UIString.UIString('Clear'), () => this._domStorage.clear()); |
| contextMenu.show(); |
| } |
| } |
| |
| export class CookieTreeElement extends BaseStorageTreeElement { |
| /** |
| * @param {!UI.Panel.PanelWithSidebar} storagePanel |
| * @param {!SDK.ResourceTreeModel.ResourceTreeFrame} frame |
| * @param {string} cookieDomain |
| */ |
| constructor(storagePanel, frame, cookieDomain) { |
| super(storagePanel, cookieDomain ? cookieDomain : Common.UIString.UIString('Local Files'), false); |
| this._target = frame.resourceTreeModel().target(); |
| this._cookieDomain = cookieDomain; |
| this.tooltip = ls`Cookies used by frames from ${cookieDomain}`; |
| const icon = UI.Icon.Icon.create('mediumicon-cookie', 'resource-tree-item'); |
| this.setLeadingIcons([icon]); |
| } |
| |
| get itemURL() { |
| return 'cookies://' + this._cookieDomain; |
| } |
| |
| cookieDomain() { |
| return this._cookieDomain; |
| } |
| |
| /** |
| * @override |
| */ |
| onattach() { |
| super.onattach(); |
| this.listItemElement.addEventListener('contextmenu', this._handleContextMenuEvent.bind(this), true); |
| } |
| |
| /** |
| * @param {!Event} event |
| */ |
| _handleContextMenuEvent(event) { |
| const contextMenu = new UI.ContextMenu.ContextMenu(event); |
| contextMenu.defaultSection().appendItem( |
| Common.UIString.UIString('Clear'), () => this._storagePanel.clearCookies(this._target, this._cookieDomain)); |
| contextMenu.show(); |
| } |
| |
| /** |
| * @override |
| * @param {boolean=} selectedByUser |
| * @return {boolean} |
| */ |
| onselect(selectedByUser) { |
| super.onselect(selectedByUser); |
| this._storagePanel.showCookies(this._target, this._cookieDomain); |
| return false; |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class ApplicationCacheManifestTreeElement extends BaseStorageTreeElement { |
| constructor(storagePanel, manifestURL) { |
| const title = new Common.ParsedURL.ParsedURL(manifestURL).displayName; |
| super(storagePanel, title, false); |
| this.tooltip = manifestURL; |
| this._manifestURL = manifestURL; |
| } |
| |
| get itemURL() { |
| return 'appcache://' + this._manifestURL; |
| } |
| |
| get manifestURL() { |
| return this._manifestURL; |
| } |
| |
| /** |
| * @override |
| * @param {boolean=} selectedByUser |
| * @return {boolean} |
| */ |
| onselect(selectedByUser) { |
| super.onselect(selectedByUser); |
| this._storagePanel.showCategoryView(this._manifestURL, null); |
| return false; |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class ApplicationCacheFrameTreeElement extends BaseStorageTreeElement { |
| /** |
| * @param {!ApplicationPanelSidebar} sidebar |
| * @param {!SDK.ResourceTreeModel.ResourceTreeFrame} frame |
| * @param {string} manifestURL |
| */ |
| constructor(sidebar, frame, manifestURL) { |
| super(sidebar._panel, '', false); |
| this._sidebar = sidebar; |
| this._frameId = frame.id; |
| this._manifestURL = manifestURL; |
| this._refreshTitles(frame); |
| |
| const icon = UI.Icon.Icon.create('mediumicon-frame-top', 'navigator-folder-tree-item'); |
| this.setLeadingIcons([icon]); |
| } |
| |
| get itemURL() { |
| return 'appcache://' + this._manifestURL + '/' + encodeURI(this.titleAsText()); |
| } |
| |
| get frameId() { |
| return this._frameId; |
| } |
| |
| get manifestURL() { |
| return this._manifestURL; |
| } |
| |
| /** |
| * @param {!SDK.ResourceTreeModel.ResourceTreeFrame} frame |
| */ |
| _refreshTitles(frame) { |
| this.title = frame.displayName(); |
| } |
| |
| /** |
| * @param {!SDK.ResourceTreeModel.ResourceTreeFrame} frame |
| */ |
| frameNavigated(frame) { |
| this._refreshTitles(frame); |
| } |
| |
| /** |
| * @override |
| * @param {boolean=} selectedByUser |
| * @return {boolean} |
| */ |
| onselect(selectedByUser) { |
| super.onselect(selectedByUser); |
| this._sidebar._showApplicationCache(this._frameId); |
| return false; |
| } |
| } |
| |
| /** |
| * @unrestricted |
| */ |
| export class StorageCategoryView extends UI.Widget.VBox { |
| constructor() { |
| super(); |
| |
| this.element.classList.add('storage-view'); |
| this._emptyWidget = new UI.EmptyWidget.EmptyWidget(''); |
| this._linkElement = null; |
| this._emptyWidget.show(this.element); |
| } |
| |
| /** |
| * @param {string} text |
| */ |
| setText(text) { |
| this._emptyWidget.text = text; |
| } |
| |
| /** |
| * @param {?string} link |
| */ |
| setLink(link) { |
| if (link && !this._linkElement) { |
| this._linkElement = this._emptyWidget.appendLink(link); |
| } |
| if (!link && this._linkElement) { |
| this._linkElement.classList.add('hidden'); |
| } |
| if (link && this._linkElement) { |
| this._linkElement.setAttribute('href', link); |
| this._linkElement.classList.remove('hidden'); |
| } |
| } |
| } |
| |
| /** |
| * @implements {SDK.SDKModel.Observer} |
| */ |
| export class ResourcesSection { |
| /** |
| * @param {!UI.Panel.PanelWithSidebar} storagePanel |
| * @param {!UI.TreeOutline.TreeElement} treeElement |
| */ |
| constructor(storagePanel, treeElement) { |
| this._panel = storagePanel; |
| this._treeElement = treeElement; |
| /** @type {!Map<string, !FrameTreeElement>} */ |
| this._treeElementForFrameId = new Map(); |
| /** @type {!Map<string, !FrameTreeElement>} */ |
| this._treeElementForTargetId = new Map(); |
| |
| const frameManager = SDK.FrameManager.FrameManager.instance(); |
| frameManager.addEventListener( |
| SDK.FrameManager.Events.FrameAddedToTarget, event => this._frameAdded(event.data.frame), this); |
| frameManager.addEventListener( |
| SDK.FrameManager.Events.FrameRemoved, event => this._frameDetached(event.data.frameId), this); |
| frameManager.addEventListener( |
| SDK.FrameManager.Events.FrameNavigated, event => this._frameNavigated(event.data.frame), this); |
| frameManager.addEventListener( |
| SDK.FrameManager.Events.ResourceAdded, event => this._resourceAdded(event.data.resource), this); |
| |
| SDK.SDKModel.TargetManager.instance().addModelListener( |
| SDK.ChildTargetManager.ChildTargetManager, SDK.ChildTargetManager.Events.TargetCreated, this._windowOpened, |
| this); |
| SDK.SDKModel.TargetManager.instance().addModelListener( |
| SDK.ChildTargetManager.ChildTargetManager, SDK.ChildTargetManager.Events.TargetInfoChanged, this._windowChanged, |
| this); |
| SDK.SDKModel.TargetManager.instance().addModelListener( |
| SDK.ChildTargetManager.ChildTargetManager, SDK.ChildTargetManager.Events.TargetDestroyed, this._windowDestroyed, |
| this); |
| |
| SDK.SDKModel.TargetManager.instance().observeTargets(this); |
| |
| for (const frame of frameManager.getAllFrames()) { |
| if (!this._treeElementForFrameId.get(frame.id)) { |
| this._addFrameAndParents(frame); |
| } |
| const childTargetManager = frame.resourceTreeModel().target().model(SDK.ChildTargetManager.ChildTargetManager); |
| for (const targetInfo of childTargetManager.targetInfos()) { |
| this._windowOpened({data: {targetInfo}}); |
| } |
| } |
| } |
| |
| /** |
| * @override |
| * @param {!SDK.SDKModel.Target} target |
| */ |
| targetAdded(target) { |
| if (target.type() === 'worker') { |
| this._workerAdded(target); |
| } |
| } |
| |
| /** |
| * @param {!SDK.SDKModel.Target} target |
| * @return {!Promise<void>} |
| */ |
| async _workerAdded(target) { |
| const parentTargetId = target.parentTarget().id(); |
| const frameTreeElement = this._treeElementForTargetId.get(parentTargetId); |
| const targetInfo = |
| (await target.parentTarget().targetAgent().invoke_getTargetInfo({targetId: target.id()})).targetInfo; |
| if (frameTreeElement && targetInfo) { |
| frameTreeElement.workerCreated(targetInfo); |
| } |
| } |
| |
| /** |
| * @override |
| * @param {!SDK.SDKModel.Target} target |
| */ |
| targetRemoved(target) { |
| } |
| |
| /** |
| * @param {!SDK.ResourceTreeModel.ResourceTreeFrame} frame |
| */ |
| _addFrameAndParents(frame) { |
| const parentFrame = frame.parentFrame(); |
| if (parentFrame && !this._treeElementForFrameId.get(parentFrame.id)) { |
| this._addFrameAndParents(parentFrame); |
| } |
| this._frameAdded(frame); |
| } |
| |
| /** |
| * @param {?SDK.ResourceTreeModel.ResourceTreeFrame} frame |
| * @return {boolean} |
| */ |
| _expandFrame(frame) { |
| if (!frame) { |
| return false; |
| } |
| let treeElement = this._treeElementForFrameId.get(frame.id); |
| if (!treeElement && !this._expandFrame(frame.parentFrame())) { |
| return false; |
| } |
| treeElement = this._treeElementForFrameId.get(frame.id); |
| if (!treeElement) { |
| return false; |
| } |
| treeElement.expand(); |
| return true; |
| } |
| |
| /** |
| * @param {!SDK.Resource.Resource} resource |
| * @param {number=} line |
| * @param {number=} column |
| * @return {!Promise} |
| */ |
| async revealResource(resource, line, column) { |
| if (!this._expandFrame(resource.frame())) { |
| return; |
| } |
| const resourceTreeElement = FrameResourceTreeElement.forResource(resource); |
| if (resourceTreeElement) { |
| await resourceTreeElement.revealResource(line, column); |
| } |
| } |
| |
| /** |
| * @param {!SDK.ResourceTreeModel.ResourceTreeFrame} frame |
| */ |
| _frameAdded(frame) { |
| const parentFrame = frame.parentFrame(); |
| const parentTreeElement = parentFrame ? this._treeElementForFrameId.get(parentFrame.id) : this._treeElement; |
| if (!parentTreeElement) { |
| return; |
| } |
| |
| const existingElement = this._treeElementForFrameId.get(frame.id); |
| if (existingElement) { |
| this._treeElementForFrameId.delete(frame.id); |
| if (existingElement.parent) { |
| existingElement.parent.removeChild(existingElement); |
| } |
| } |
| |
| const frameTreeElement = new FrameTreeElement(this, frame); |
| this._treeElementForFrameId.set(frame.id, frameTreeElement); |
| const targetId = frame.resourceTreeModel().target().id(); |
| if (!this._treeElementForTargetId.get(targetId)) { |
| this._treeElementForTargetId.set(targetId, frameTreeElement); |
| } |
| parentTreeElement.appendChild(frameTreeElement); |
| |
| for (const resource of frame.resources()) { |
| this._resourceAdded(resource); |
| } |
| } |
| |
| /** |
| * @param {string} frameId |
| */ |
| _frameDetached(frameId) { |
| const frameTreeElement = this._treeElementForFrameId.get(frameId); |
| if (!frameTreeElement) { |
| return; |
| } |
| |
| this._treeElementForFrameId.delete(frameId); |
| if (frameTreeElement.parent) { |
| frameTreeElement.parent.removeChild(frameTreeElement); |
| } |
| } |
| |
| /** |
| * @param {!SDK.ResourceTreeModel.ResourceTreeFrame} frame |
| */ |
| _frameNavigated(frame) { |
| const frameTreeElement = this._treeElementForFrameId.get(frame.id); |
| if (frameTreeElement) { |
| frameTreeElement.frameNavigated(frame); |
| } |
| } |
| |
| /** |
| * @param {!SDK.Resource.Resource} resource |
| */ |
| _resourceAdded(resource) { |
| const frameTreeElement = this._treeElementForFrameId.get(resource.frameId); |
| if (!frameTreeElement) { |
| // This is a frame's main resource, it will be retained |
| // and re-added by the resource manager; |
| return; |
| } |
| frameTreeElement.appendResource(resource); |
| } |
| |
| /** |
| * @param {!Common.EventTarget.EventTargetEvent} event |
| */ |
| _windowOpened(event) { |
| const targetInfo = /** @type {!Protocol.Target.TargetInfo} */ (event.data); |
| // Events for DevTools windows are ignored because they do not have an openerId |
| if (targetInfo.openerId && targetInfo.type === 'page') { |
| const frameTreeElement = this._treeElementForFrameId.get(targetInfo.openerId); |
| if (frameTreeElement) { |
| this._treeElementForTargetId.set(targetInfo.targetId, frameTreeElement); |
| frameTreeElement.windowOpened(targetInfo); |
| } |
| } |
| } |
| |
| /** |
| * @param {!Common.EventTarget.EventTargetEvent} event |
| */ |
| _windowDestroyed(event) { |
| const targetId = /** @type {string} */ (event.data); |
| const frameTreeElement = this._treeElementForTargetId.get(targetId); |
| if (frameTreeElement) { |
| frameTreeElement.windowDestroyed(targetId); |
| this._treeElementForTargetId.delete(targetId); |
| } |
| } |
| |
| /** |
| * @param {!Common.EventTarget.EventTargetEvent} event |
| */ |
| _windowChanged(event) { |
| const targetInfo = /** @type {!Protocol.Target.TargetInfo} */ (event.data); |
| // Events for DevTools windows are ignored because they do not have an openerId |
| if (targetInfo.openerId && targetInfo.type === 'page') { |
| const frameTreeElement = this._treeElementForFrameId.get(targetInfo.openerId); |
| if (frameTreeElement) { |
| frameTreeElement.windowChanged(targetInfo); |
| } |
| } |
| } |
| |
| reset() { |
| this._treeElement.removeChildren(); |
| this._treeElementForFrameId.clear(); |
| this._treeElementForTargetId.clear(); |
| } |
| } |
| |
| export class FrameTreeElement extends BaseStorageTreeElement { |
| /** |
| * @param {!ResourcesSection} section |
| * @param {!SDK.ResourceTreeModel.ResourceTreeFrame} frame |
| */ |
| constructor(section, frame) { |
| super(section._panel, '', false); |
| this._section = section; |
| this._frame = frame; |
| this._frameId = frame.id; |
| /** @type {!Map<string, !StorageCategoryTreeElement>} */ |
| this._categoryElements = new Map(); |
| /** @type {!Map<string, !FrameResourceTreeElement>} */ |
| this._treeElementForResource = new Map(); |
| /** @type {!Map<string, !BaseStorageTreeElement>} */ |
| this._treeElementForWindow = new Map(); |
| this.setExpandable(true); |
| this.frameNavigated(frame); |
| /** @type {?FrameDetailsView} */ |
| this._view = null; |
| } |
| |
| /** |
| * @param {!SDK.ResourceTreeModel.ResourceTreeFrame} frame |
| */ |
| getIconTypeForFrame(frame) { |
| if (frame.isTopFrame()) { |
| return frame.unreachableUrl() ? 'mediumicon-frame-blocked' : 'mediumicon-frame'; |
| } |
| return frame.unreachableUrl() ? 'mediumicon-frame-embedded-blocked' : 'mediumicon-frame-embedded'; |
| } |
| |
| /** |
| * @param {!SDK.ResourceTreeModel.ResourceTreeFrame} frame |
| */ |
| frameNavigated(frame) { |
| const icon = UI.Icon.Icon.create(this.getIconTypeForFrame(frame)); |
| if (frame.unreachableUrl()) { |
| icon.classList.add('red-icon'); |
| } |
| this.setLeadingIcons([icon]); |
| this.invalidateChildren(); |
| |
| this._frameId = frame.id; |
| if (this.title !== frame.displayName()) { |
| this.title = frame.displayName(); |
| UI.ARIAUtils.setAccessibleName(this.listItemElement, this.title); |
| if (this.parent) { |
| const parent = this.parent; |
| // Insert frame at new position to preserve correct alphabetical order |
| parent.removeChild(this); |
| parent.appendChild(this); |
| } |
| } |
| this._categoryElements.clear(); |
| this._treeElementForResource.clear(); |
| |
| if (this.selected) { |
| this._view = new FrameDetailsView(this._frame); |
| this.showView(this._view); |
| } else { |
| this._view = null; |
| } |
| } |
| |
| get itemURL() { |
| // This is used to persist over reloads/navigation which frame was selected. |
| // A frame's title can change on DevTools refresh, so we resort to using |
| // the URL instead (even though it is not guaranteed to be unique). |
| return 'frame://' + encodeURI(this._frame.url); |
| } |
| |
| /** |
| * @override |
| * @param {boolean=} selectedByUser |
| * @return {boolean} |
| */ |
| onselect(selectedByUser) { |
| super.onselect(selectedByUser); |
| if (!this._view) { |
| this._view = new FrameDetailsView(this._frame); |
| } else { |
| this._view.update(); |
| } |
| this.showView(this._view); |
| |
| this.listItemElement.classList.remove('hovered'); |
| SDK.OverlayModel.OverlayModel.hideDOMNodeHighlight(); |
| return false; |
| } |
| |
| /** |
| * @param {boolean} hovered |
| */ |
| set hovered(hovered) { |
| if (hovered) { |
| this.listItemElement.classList.add('hovered'); |
| this._frame.highlight(); |
| } else { |
| this.listItemElement.classList.remove('hovered'); |
| SDK.OverlayModel.OverlayModel.hideDOMNodeHighlight(); |
| } |
| } |
| |
| /** |
| * @param {!SDK.Resource.Resource} resource |
| */ |
| appendResource(resource) { |
| const statusCode = resource['statusCode']; |
| if (statusCode >= 301 && statusCode <= 303) { |
| return; |
| } |
| |
| const resourceType = resource.resourceType(); |
| const categoryName = resourceType.name(); |
| let categoryElement = |
| resourceType === Common.ResourceType.resourceTypes.Document ? this : this._categoryElements.get(categoryName); |
| if (!categoryElement) { |
| categoryElement = |
| new StorageCategoryTreeElement(this._section._panel, resource.resourceType().category().title, categoryName); |
| this._categoryElements.set(resourceType.name(), categoryElement); |
| this.appendChild(categoryElement, FrameTreeElement._presentationOrderCompare); |
| } |
| const resourceTreeElement = new FrameResourceTreeElement(this._section._panel, resource); |
| categoryElement.appendChild(resourceTreeElement, FrameTreeElement._presentationOrderCompare); |
| this._treeElementForResource.set(resource.url, resourceTreeElement); |
| |
| if (this._view) { |
| this._view.update(); |
| } |
| } |
| |
| /** |
| * @param {!Protocol.Target.TargetInfo} targetInfo |
| */ |
| windowOpened(targetInfo) { |
| const categoryKey = 'OpenedWindows'; |
| let categoryElement = this._categoryElements.get(categoryKey); |
| if (!categoryElement) { |
| categoryElement = new StorageCategoryTreeElement(this._section._panel, ls`Opened Windows`, categoryKey); |
| this._categoryElements.set(categoryKey, categoryElement); |
| this.appendChild(categoryElement, FrameTreeElement._presentationOrderCompare); |
| } |
| if (!this._treeElementForWindow.get(targetInfo.targetId)) { |
| const windowTreeElement = new FrameWindowTreeElement(this._section._panel, targetInfo); |
| categoryElement.appendChild(windowTreeElement); |
| this._treeElementForWindow.set(targetInfo.targetId, windowTreeElement); |
| } |
| } |
| |
| /** |
| * @param {!Protocol.Target.TargetInfo} targetInfo |
| */ |
| workerCreated(targetInfo) { |
| const categoryKey = 'Workers'; |
| let categoryElement = this._categoryElements.get(categoryKey); |
| if (!categoryElement) { |
| categoryElement = new StorageCategoryTreeElement(this._section._panel, ls`Workers`, categoryKey); |
| this._categoryElements.set(categoryKey, categoryElement); |
| this.appendChild(categoryElement, FrameTreeElement._presentationOrderCompare); |
| } |
| if (!this._treeElementForWindow.get(targetInfo.targetId)) { |
| const workerTreeElement = new WorkerTreeElement(this._section._panel, targetInfo); |
| categoryElement.appendChild(workerTreeElement); |
| this._treeElementForWindow.set(targetInfo.targetId, workerTreeElement); |
| } |
| } |
| |
| /** |
| * @param {!Protocol.Target.TargetInfo} targetInfo |
| */ |
| windowChanged(targetInfo) { |
| const windowTreeElement = this._treeElementForWindow.get(targetInfo.targetId); |
| if (!windowTreeElement) { |
| return; |
| } |
| if (windowTreeElement.title !== targetInfo.title) { |
| windowTreeElement.title = targetInfo.title; |
| } |
| windowTreeElement.update(targetInfo); |
| } |
| |
| /** |
| * @param {string} targetId |
| */ |
| windowDestroyed(targetId) { |
| const windowTreeElement = this._treeElementForWindow.get(targetId); |
| if (windowTreeElement) { |
| windowTreeElement.windowClosed(); |
| } |
| } |
| |
| /** |
| * @param {string} url |
| * @return {?SDK.Resource.Resource} |
| */ |
| resourceByURL(url) { |
| const treeElement = this._treeElementForResource.get(url); |
| return treeElement ? treeElement._resource : null; |
| } |
| |
| /** |
| * @override |
| * @param {!UI.TreeOutline.TreeElement} treeElement |
| */ |
| appendChild(treeElement) { |
| super.appendChild(treeElement, FrameTreeElement._presentationOrderCompare); |
| } |
| |
| /** |
| * Order elements by type (first frames, then resources, last Document resources) |
| * and then each of these groups in the alphabetical order. |
| * @param {!UI.TreeOutline.TreeElement} treeElement1 |
| * @param {!UI.TreeOutline.TreeElement} treeElement2 |
| */ |
| static _presentationOrderCompare(treeElement1, treeElement2) { |
| /** |
| * @param {*} treeElement |
| */ |
| function typeWeight(treeElement) { |
| if (treeElement instanceof StorageCategoryTreeElement) { |
| return 2; |
| } |
| if (treeElement instanceof FrameTreeElement) { |
| return 1; |
| } |
| return 3; |
| } |
| |
| const typeWeight1 = typeWeight(treeElement1); |
| const typeWeight2 = typeWeight(treeElement2); |
| return typeWeight1 - typeWeight2 || treeElement1.titleAsText().localeCompare(treeElement2.titleAsText()); |
| } |
| } |
| |
| export class FrameResourceTreeElement extends BaseStorageTreeElement { |
| /** |
| * @param {!UI.Panel.PanelWithSidebar} storagePanel |
| * @param {!SDK.Resource.Resource} resource |
| */ |
| constructor(storagePanel, resource) { |
| super(storagePanel, resource.displayName, false); |
| this._panel = storagePanel; |
| /** @type {!SDK.Resource.Resource} */ |
| this._resource = resource; |
| /** @type {?Promise<!UI.Widget.Widget>} */ |
| this._previewPromise = null; |
| this.tooltip = resource.url; |
| this._resource[FrameResourceTreeElement._symbol] = this; |
| |
| const icon = UI.Icon.Icon.create('mediumicon-manifest', 'navigator-file-tree-item'); |
| icon.classList.add('navigator-' + resource.resourceType().name() + '-tree-item'); |
| this.setLeadingIcons([icon]); |
| } |
| |
| /** |
| * @param {!SDK.Resource.Resource} resource |
| */ |
| static forResource(resource) { |
| return resource[FrameResourceTreeElement._symbol]; |
| } |
| |
| get itemURL() { |
| return this._resource.url; |
| } |
| |
| /** |
| * @return {!Promise<!UI.Widget.Widget>} |
| */ |
| _preparePreview() { |
| if (this._previewPromise) { |
| return this._previewPromise; |
| } |
| const viewPromise = |
| SourceFrame.PreviewFactory.PreviewFactory.createPreview(this._resource, this._resource.mimeType); |
| this._previewPromise = viewPromise.then(view => { |
| if (view) { |
| return view; |
| } |
| return new UI.EmptyWidget.EmptyWidget(this._resource.url); |
| }); |
| return this._previewPromise; |
| } |
| |
| /** |
| * @override |
| * @param {boolean=} selectedByUser |
| * @return {boolean} |
| */ |
| onselect(selectedByUser) { |
| super.onselect(selectedByUser); |
| this._panel.scheduleShowView(this._preparePreview()); |
| return false; |
| } |
| |
| /** |
| * @override |
| * @return {boolean} |
| */ |
| ondblclick(event) { |
| Host.InspectorFrontendHost.InspectorFrontendHostInstance.openInNewTab(this._resource.url); |
| return false; |
| } |
| |
| /** |
| * @override |
| */ |
| onattach() { |
| super.onattach(); |
| this.listItemElement.draggable = true; |
| this.listItemElement.addEventListener('dragstart', this._ondragstart.bind(this), false); |
| this.listItemElement.addEventListener('contextmenu', this._handleContextMenuEvent.bind(this), true); |
| } |
| |
| /** |
| * @param {!MouseEvent} event |
| * @return {boolean} |
| */ |
| _ondragstart(event) { |
| event.dataTransfer.setData('text/plain', this._resource.content || ''); |
| event.dataTransfer.effectAllowed = 'copy'; |
| return true; |
| } |
| |
| _handleContextMenuEvent(event) { |
| const contextMenu = new UI.ContextMenu.ContextMenu(event); |
| contextMenu.appendApplicableItems(this._resource); |
| contextMenu.show(); |
| } |
| |
| /** |
| * @param {number=} line |
| * @param {number=} column |
| */ |
| async revealResource(line, column) { |
| this.revealAndSelect(true); |
| const view = await this._panel.scheduleShowView(this._preparePreview()); |
| if (!(view instanceof SourceFrame.ResourceSourceFrame.ResourceSourceFrame) || typeof line !== 'number') { |
| return; |
| } |
| view.revealPosition(line, column, true); |
| } |
| } |
| |
| class FrameWindowTreeElement extends BaseStorageTreeElement { |
| /** |
| * @param {!UI.Panel.PanelWithSidebar} storagePanel |
| * @param {!Protocol.Target.TargetInfo} targetInfo |
| */ |
| constructor(storagePanel, targetInfo) { |
| super(storagePanel, targetInfo.title || ls`Window without title`, false); |
| this._targetInfo = targetInfo; |
| this._isWindowClosed = false; |
| this._view = null; |
| this.updateIcon(targetInfo.canAccessOpener); |
| } |
| |
| /** |
| * @param {boolean} canAccessOpener |
| */ |
| updateIcon(canAccessOpener) { |
| const iconType = canAccessOpener ? 'mediumicon-frame-opened' : 'mediumicon-frame'; |
| const icon = UI.Icon.Icon.create(iconType); |
| this.setLeadingIcons([icon]); |
| } |
| |
| /** |
| * @param {!Protocol.Target.TargetInfo} targetInfo |
| */ |
| update(targetInfo) { |
| if (targetInfo.canAccessOpener !== this._targetInfo.canAccessOpener) { |
| this.updateIcon(targetInfo.canAccessOpener); |
| } |
| this._targetInfo = targetInfo; |
| if (this._view) { |
| this._view.setTargetInfo(targetInfo); |
| this._view.update(); |
| } |
| } |
| |
| windowClosed() { |
| this.listItemElement.classList.add('window-closed'); |
| this._isWindowClosed = true; |
| if (this._view) { |
| this._view.setIsWindowClosed(true); |
| this._view.update(); |
| } |
| } |
| |
| /** |
| * @override |
| * @param {boolean=} selectedByUser |
| * @return {boolean} |
| */ |
| onselect(selectedByUser) { |
| super.onselect(selectedByUser); |
| if (!this._view) { |
| this._view = new OpenedWindowDetailsView(this._targetInfo, this._isWindowClosed); |
| } else { |
| this._view.update(); |
| } |
| this.showView(this._view); |
| return false; |
| } |
| } |
| |
| class WorkerTreeElement extends BaseStorageTreeElement { |
| /** |
| * @param {!UI.Panel.PanelWithSidebar} storagePanel |
| * @param {!Protocol.Target.TargetInfo} targetInfo |
| */ |
| constructor(storagePanel, targetInfo) { |
| super(storagePanel, targetInfo.title || targetInfo.url || ls`worker`, false); |
| this._targetInfo = targetInfo; |
| this._view = null; |
| const icon = UI.Icon.Icon.create('mediumicon-service-worker', 'navigator-file-tree-item'); |
| this.setLeadingIcons([icon]); |
| } |
| |
| /** |
| * @override |
| * @param {boolean=} selectedByUser |
| * @return {boolean} |
| */ |
| onselect(selectedByUser) { |
| super.onselect(selectedByUser); |
| if (!this._view) { |
| this._view = new WorkerDetailsView(this._targetInfo); |
| } else { |
| this._view.update(); |
| } |
| this.showView(this._view); |
| return false; |
| } |
| } |
| |
| FrameResourceTreeElement._symbol = Symbol('treeElement'); |