UNPKG

chrome-devtools-frontend

Version:
1,421 lines (1,278 loc) • 85.7 kB
/* * 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. */ import * as Common from '../common/common.js'; import * as Host from '../host/host.js'; import * as i18n from '../i18n/i18n.js'; import * as Platform from '../platform/platform.js'; import * as Root from '../root/root.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 {ApplicationCacheFrameTreeElement, ApplicationCacheManifestTreeElement, ServiceWorkerCacheTreeElement} from './ApplicationPanelCacheSection.js'; import {ApplicationPanelTreeElement, ExpandableApplicationPanelTreeElement} from './ApplicationPanelTreeElement.js'; import {AppManifestView} from './AppManifestView.js'; import {BackgroundServiceModel} from './BackgroundServiceModel.js'; import {BackgroundServiceView} from './BackgroundServiceView.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} 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 {OpenedWindowDetailsView, WorkerDetailsView} from './OpenedWindowDetailsView.js'; import {ResourcesPanel} from './ResourcesPanel.js'; // eslint-disable-line no-unused-vars import {ServiceWorkersView} from './ServiceWorkersView.js'; import {StorageView} from './StorageView.js'; import {TrustTokensTreeElement} from './TrustTokensView.js'; export const UIStrings = { /** *@description Text in Application Panel Sidebar of the Application panel */ application: 'Application', /** *@description Text in Application Panel Sidebar of the Application panel */ storage: 'Storage', /** *@description Text in Application Panel Sidebar of the Application panel */ localStorage: 'Local Storage', /** *@description Text in Application Panel Sidebar of the Application panel */ sessionStorage: 'Session Storage', /** *@description Text in Application Panel Sidebar of the Application panel */ webSql: 'Web SQL', /** *@description Text for web cookies */ cookies: 'Cookies', /** *@description Text in Application Panel Sidebar of the Application panel */ cache: 'Cache', /** *@description Text in Application Panel Sidebar of the Application panel */ applicationCache: 'Application Cache', /** *@description Text in Application Panel Sidebar of the Application panel */ backgroundServices: 'Background Services', /** *@description Text for rendering frames */ frames: 'Frames', /** *@description Text in Application Panel Sidebar of the Application panel */ serviceWorkers: '`Service Workers`', /** *@description Text that appears on a button for the manifest resource type filter. */ manifest: 'Manifest', /** *@description Text in Application Panel Sidebar of the Application panel */ indexeddb: 'IndexedDB', /** *@description A context menu item in the Application Panel Sidebar of the Application panel */ refreshIndexeddb: 'Refresh IndexedDB', /** *@description Tooltip in Application Panel Sidebar of the Application panel *@example {1.0} PH1 */ versionSEmpty: 'Version: {PH1} (empty)', /** *@description Tooltip in Application Panel Sidebar of the Application panel *@example {1.0} PH1 */ versionS: 'Version: {PH1}', /** *@description Text to clear content */ clear: 'Clear', /** *@description Text in Application Panel Sidebar of the Application panel *@example {"key path"} PH1 */ keyPathS: 'Key path: {PH1}', /** *@description Text in Application Panel Sidebar of the Application panel */ autoincrement: 'autoIncrement', /** * @description Shown in a tooltip for the Application Panel Sidebar of the Application panel. * Indicates that an IndexedDB Index is multiEntry. Not to be translated as it is a property name * used in code. */ unique: '`unique`', /** * @description Shown in a tooltip for the Application Panel Sidebar of the Application panel. * Indicates that an IndexedDB Index is unique. Not to be translated as it is a property name * used in code. */ multientry: '`multiEntry`', /** *@description Text in Application Panel Sidebar of the Application panel */ localFiles: 'Local Files', /** *@description Tooltip in Application Panel Sidebar of the Application panel *@example {https://example.com} PH1 */ cookiesUsedByFramesFromS: 'Cookies used by frames from {PH1}', /** *@description Text in Frames View of the Application panel */ openedWindows: 'Opened Windows', /** *@description Label for plural of worker type: web workers */ webWorkers: 'Web Workers', /** *@description Label in frame tree for unavailable document */ documentNotAvailable: 'Document not available', /** *@description Description of content of unavailable document in Application panel */ theContentOfThisDocumentHasBeen: 'The content of this document has been generated dynamically via \'document.write()\'.', /** *@description Text in Frames View of the Application panel */ windowWithoutTitle: 'Window without title', /** *@description Default name for worker */ worker: 'worker', }; const str_ = i18n.i18n.registerUIStrings('resources/ApplicationPanelSidebar.js', UIStrings); const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_); /** * @implements {SDK.SDKModel.Observer} */ export class ApplicationPanelSidebar extends UI.Widget.VBox { /** * @param {!ResourcesPanel} panel */ constructor(panel) { super(); this._panel = panel; /** @type {!Map<!Protocol.Page.FrameId, !ApplicationCacheItemsView>} */ this._applicationCacheViews = new Map(); /** @type {!Map<!Protocol.Page.FrameId, !ApplicationCacheFrameTreeElement>} */ this._applicationCacheFrameElements = new Map(); /** @type {!Map<!Protocol.Page.FrameId, !ApplicationCacheManifestTreeElement>} */ this._applicationCacheManifestElements = new Map(); 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); const applicationSectionTitle = i18nString(UIStrings.application); this._applicationTreeElement = this._addSidebarSection(applicationSectionTitle); 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 storageSectionTitle = i18nString(UIStrings.storage); const storageTreeElement = this._addSidebarSection(storageSectionTitle); this.localStorageListTreeElement = new ExpandableApplicationPanelTreeElement(panel, i18nString(UIStrings.localStorage), '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 ExpandableApplicationPanelTreeElement(panel, i18nString(UIStrings.sessionStorage), '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 ExpandableApplicationPanelTreeElement(panel, i18nString(UIStrings.webSql), '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 ExpandableApplicationPanelTreeElement(panel, i18nString(UIStrings.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); this.trustTokensTreeElement = new TrustTokensTreeElement(panel); storageTreeElement.appendChild(this.trustTokensTreeElement); const cacheSectionTitle = i18nString(UIStrings.cache); const cacheTreeElement = this._addSidebarSection(cacheSectionTitle); this.cacheStorageListTreeElement = new ServiceWorkerCacheTreeElement(panel); cacheTreeElement.appendChild(this.cacheStorageListTreeElement); this.applicationCacheListTreeElement = new ExpandableApplicationPanelTreeElement(panel, i18nString(UIStrings.applicationCache), '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 backgroundServiceSectionTitle = i18nString(UIStrings.backgroundServices); const backgroundServiceTreeElement = this._addSidebarSection(backgroundServiceSectionTitle); 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); } } const resourcesSectionTitle = i18nString(UIStrings.frames); const resourcesTreeElement = this._addSidebarSection(resourcesSectionTitle); this._resourcesSection = new ResourcesSection(panel, resourcesTreeElement); /** @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(); } // Work-around for crbug.com/1152713: Something is wrong with custom scrollbars and size containment. // @ts-ignore this.contentElement.style.contain = 'layout style'; } /** * @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); UI.ARIAUtils.setAccessibleName(treeElement.childrenListElement, title); 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 && this._target.model(SDK.ServiceWorkerCacheModel.ServiceWorkerCacheModel); if (cacheStorageModel) { cacheStorageModel.enable(); } const resourceTreeModel = this._target && this._target.model(SDK.ResourceTreeModel.ResourceTreeModel); if (resourceTreeModel) { this._populateApplicationCacheTree(resourceTreeModel); } SDK.SDKModel.TargetManager.instance().observeModels( DOMStorageModel, /** @type {!SDK.SDKModel.SDKModelObserver<!DOMStorageModel>} */ ({ modelAdded: model => this._domStorageModelAdded(model), modelRemoved: model => this._domStorageModelRemoved(model) })); this.indexedDBListTreeElement._initialize(); SDK.SDKModel.TargetManager.instance().observeModels( IndexedDBModel, /** @type {!SDK.SDKModel.SDKModelObserver<!IndexedDBModel>} */ ({ modelAdded: model => model.enable(), modelRemoved: model => this.indexedDBListTreeElement.removeIndexedDBForModel(model) })); const serviceWorkerCacheModel = this._target && this._target.model(SDK.ServiceWorkerCacheModel.ServiceWorkerCacheModel) || null; this.cacheStorageListTreeElement.initialize(serviceWorkerCacheModel); const backgroundServiceModel = this._target && this._target.model(BackgroundServiceModel) || null; if (Root.Runtime.experiments.isEnabled('backgroundServices')) { this.backgroundFetchTreeElement && this.backgroundFetchTreeElement._initialize(backgroundServiceModel); this.backgroundSyncTreeElement && this.backgroundSyncTreeElement._initialize(backgroundServiceModel); if (Root.Runtime.experiments.isEnabled('backgroundServicesNotifications') && this.notificationsTreeElement) { this.notificationsTreeElement._initialize(backgroundServiceModel); } if (Root.Runtime.experiments.isEnabled('backgroundServicesPaymentHandler') && this.paymentHandlerTreeElement) { this.paymentHandlerTreeElement._initialize(backgroundServiceModel); } this.periodicBackgroundSyncTreeElement && this.periodicBackgroundSyncTreeElement._initialize(backgroundServiceModel); if (Root.Runtime.experiments.isEnabled('backgroundServicesPushMessaging') && this.pushMessagingTreeElement) { 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 this._applicationCacheFrameElements.keys()) { 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.get(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; if (parentListTreeElement) { parentListTreeElement.removeChild(treeElement); if (wasSelected) { parentListTreeElement.select(); } } this._domStorageTreeElements.delete(domStorage); } /** * @param {!DatabaseModelDatabase} database */ selectDatabase(database) { if (database) { this._showDatabase(database); const treeElement = this._databaseTreeElements.get(database); treeElement && treeElement.select(); } } /** * @param {!SDK.Resource.Resource} resource * @param {number=} line * @param {number=} column * @return {!Promise<void>} */ 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); } /** * @param {!Protocol.Page.FrameId} frameId */ _showApplicationCache(frameId) { if (!this._applicationCacheModel) { return; } let view = this._applicationCacheViews.get(frameId); if (!view) { view = new ApplicationCacheItemsView(this._applicationCacheModel, frameId); this._applicationCacheViews.set(frameId, view); } this._innerShowView(view); } /** * @param {!UI.Widget.Widget} view */ showFileSystem(view) { this._innerShowView(view); } /** * @param {!UI.Widget.Widget} 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; } /** @type {!Set<string>} */ const tableNamesHash = new Set(); const panel = this._panel; const tableNames = await database.tableNames(); for (const tableName of tableNames) { tableNamesHash.add(tableName); } for (const tableName in tableViews) { if (!(tableNamesHash.has(tableName))) { if (panel.visibleView === tableViews[tableName]) { panel.showView(null); } delete tableViews[tableName]; } } await databasesTreeElement.updateChildren(); } /** * @param {!SDK.ResourceTreeModel.ResourceTreeModel} resourceTreeModel */ _populateApplicationCacheTree(resourceTreeModel) { if (!this._target) { return; } this._applicationCacheModel = this._target.model(ApplicationCacheModel); if (!this._applicationCacheModel) { return; } 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); } /** * @param {!Common.EventTarget.EventTargetEvent} event */ _applicationCacheFrameManifestAdded(event) { const frameId = event.data; if (!this._applicationCacheModel || !this._target || frameId !== 'string') { return; } const manifestURL = this._applicationCacheModel.frameManifestURL(frameId); let manifestTreeElement = this._applicationCacheManifestElements.get(manifestURL); if (!manifestTreeElement) { manifestTreeElement = new ApplicationCacheManifestTreeElement(this._panel, manifestURL); this.applicationCacheListTreeElement.appendChild(manifestTreeElement); this._applicationCacheManifestElements.set(manifestURL, manifestTreeElement); } const model = this._target.model(SDK.ResourceTreeModel.ResourceTreeModel); const frame = model && model.frameForId(frameId); if (model && frame) { const frameTreeElement = new ApplicationCacheFrameTreeElement(this, frame, manifestURL); manifestTreeElement.appendChild(frameTreeElement); manifestTreeElement.expand(); this._applicationCacheFrameElements.set(frameId, frameTreeElement); } } /** * @param {!Common.EventTarget.EventTargetEvent} event */ _applicationCacheFrameManifestRemoved(event) { const frameId = /** @type {!Protocol.Page.FrameId} */ (event.data); const frameTreeElement = this._applicationCacheFrameElements.get(frameId); if (!frameTreeElement) { return; } const manifestURL = frameTreeElement.manifestURL; this._applicationCacheFrameElements.delete(frameId); this._applicationCacheViews.delete(frameId); frameTreeElement.parent && frameTreeElement.parent.removeChild(frameTreeElement); const manifestTreeElement = this._applicationCacheManifestElements.get(manifestURL); if (!manifestTreeElement || manifestTreeElement.childCount()) { return; } this._applicationCacheManifestElements.delete(manifestURL); manifestTreeElement.parent && manifestTreeElement.parent.removeChild(manifestTreeElement); } /** * @param {!Common.EventTarget.EventTargetEvent} event */ _applicationCacheFrameManifestStatusChanged(event) { if (!this._applicationCacheModel) { return; } const frameId = /** @type {!Protocol.Page.FrameId} */ (event.data); const status = this._applicationCacheModel.frameManifestStatus(frameId); const view = this._applicationCacheViews.get(frameId); if (view) { view.updateStatus(status); } } /** * @param {!Common.EventTarget.EventTargetEvent} event */ _applicationCacheNetworkStateChanged(event) { const isNowOnline = /** @type {boolean} */ (event.data); for (const view of this._applicationCacheViews.values()) { view.updateNetworkState(isNowOnline); } } /** * * @param {!MouseEvent} event */ _onmousemove(event) { const nodeUnderMouse = /** @type {!Node} */ (event.target); if (!nodeUnderMouse) { return; } const listNode = UI.UIUtils.enclosingNodeOrSelfWithNodeName(nodeUnderMouse, 'li'); if (!listNode) { return; } const element = UI.TreeOutline.TreeElement.getTreeElementBylistItemNode(listNode); if (this._previousHoveredElement === element) { return; } if (this._previousHoveredElement) { this._previousHoveredElement.hovered = false; delete this._previousHoveredElement; } if (element instanceof FrameTreeElement) { /** @type {!FrameTreeElement} */ this._previousHoveredElement = element; element.hovered = true; } } /** * @param {!MouseEvent} event */ _onmouseleave(event) { if (this._previousHoveredElement) { this._previousHoveredElement.hovered = false; delete this._previousHoveredElement; } } } export class BackgroundServiceTreeElement extends ApplicationPanelTreeElement { /** * @param {!ResourcesPanel} 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} */ /** * @override * @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; } } export class DatabaseTreeElement extends ApplicationPanelTreeElement { /** * @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]); } /** * @override * @return {string} */ 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)); } } } export class DatabaseTableTreeElement extends ApplicationPanelTreeElement { /** * @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]); } /** * @override * @return {string} */ 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; } } export class ServiceWorkersTreeElement extends ApplicationPanelTreeElement { /** * @param {!ResourcesPanel} storagePanel */ constructor(storagePanel) { super(storagePanel, i18nString(UIStrings.serviceWorkers), false); const icon = UI.Icon.Icon.create('mediumicon-service-worker', 'resource-tree-item'); this.setLeadingIcons([icon]); } /** * @return {string} */ /** * @override * @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; } } export class AppManifestTreeElement extends ApplicationPanelTreeElement { /** * @param {!ResourcesPanel} storagePanel */ constructor(storagePanel) { super(storagePanel, i18nString(UIStrings.manifest), false); const icon = UI.Icon.Icon.create('mediumicon-manifest', 'resource-tree-item'); this.setLeadingIcons([icon]); } /** * @return {string} */ /** * @override * @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; } } export class ClearStorageTreeElement extends ApplicationPanelTreeElement { /** * @param {!ResourcesPanel} storagePanel */ constructor(storagePanel) { super(storagePanel, i18nString(UIStrings.storage), false); const icon = UI.Icon.Icon.create('mediumicon-database', 'resource-tree-item'); this.setLeadingIcons([icon]); } /** * @override * @return {string} */ get itemURL() { return 'clear-storage://'; } /** * @override * @param {boolean=} selectedByUser * @return {boolean} */ onselect(selectedByUser) { super.onselect(selectedByUser); if (!this._view) { this._view = new StorageView(); } this.showView(this._view); return false; } } export class IndexedDBTreeElement extends ExpandableApplicationPanelTreeElement { /** * @param {!ResourcesPanel} storagePanel */ constructor(storagePanel) { super(storagePanel, i18nString(UIStrings.indexeddb), 'IndexedDB'); const icon = UI.Icon.Icon.create('mediumicon-database', 'resource-tree-item'); this.setLeadingIcons([icon]); /** @type {!Array.<!IDBDatabaseTreeElement>} */ this._idbDatabaseTreeElements = []; } _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). 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); } /** * @param {!MouseEvent} event */ _handleContextMenuEvent(event) { const contextMenu = new UI.ContextMenu.ContextMenu(event); contextMenu.defaultSection().appendItem(i18nString(UIStrings.refreshIndexeddb), 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.resourcesPanel, 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; } } export class IDBDatabaseTreeElement extends ApplicationPanelTreeElement { /** * @param {!ResourcesPanel} storagePanel * @param {!IndexedDBModel} model * @param {!DatabaseId} databaseId */ constructor(storagePanel, model, databaseId) { super(storagePanel, databaseId.name + ' - ' + databaseId.securityOrigin, false); this._model = model; this._databaseId = databaseId; /** @type {!Map<string, !IDBObjectStoreTreeElement>} */ this._idbObjectStoreTreeElements = new Map(); const icon = UI.Icon.Icon.create('mediumicon-database', 'resource-tree-item'); this.setLeadingIcons([icon]); this._model.addEventListener(IndexedDBModelEvents.DatabaseNamesRefreshed, this._refreshIndexedDB, this); } /** * @override * @return {string} */ get itemURL() { return 'indexedDB://' + this._databaseId.securityOrigin + '/' + this._databaseId.name; } /** * @override */ onattach() { super.onattach(); this.listItemElement.addEventListener('contextmenu', this._handleContextMenuEvent.bind(this), true); } /** * @param {!MouseEvent} event */ _handleContextMenuEvent(event) { const contextMenu = new UI.ContextMenu.ContextMenu(event); contextMenu.defaultSection().appendItem(i18nString(UIStrings.refreshIndexeddb), this._refreshIndexedDB.bind(this)); contextMenu.show(); } _refreshIndexedDB() { this._model.refreshDatabase(this._databaseId); } /** * @param {string} objectStoreName */ indexedDBContentUpdated(objectStoreName) { const treeElement = this._idbObjectStoreTreeElements.get(objectStoreName); if (treeElement) { treeElement.markNeedsRefresh(); } } /** * @param {!IndexedDBModelDatabase} database * @param {boolean} entriesUpdated */ update(database, entriesUpdated) { this._database = database; /** @type {!Set<string>} */ const objectStoreNames = new Set(); for (const objectStoreName of [...this._database.objectStores.keys()].sort()) { const objectStore = this._database.objectStores.get(objectStore