chrome-devtools-frontend
Version:
Chrome DevTools UI
1,162 lines (1,030 loc) • 102 kB
text/typescript
// Copyright 2021 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
/* eslint-disable rulesdir/no-imperative-dom-api */
/*
* 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 '../../core/common/common.js';
import * as Host from '../../core/host/host.js';
import * as i18n from '../../core/i18n/i18n.js';
import * as Platform from '../../core/platform/platform.js';
import * as SDK from '../../core/sdk/sdk.js';
import * as Protocol from '../../generated/protocol.js';
import * as IssuesManager from '../../models/issues_manager/issues_manager.js';
import * as IconButton from '../../ui/components/icon_button/icon_button.js';
import * as LegacyWrapper from '../../ui/components/legacy_wrapper/legacy_wrapper.js';
import * as SourceFrame from '../../ui/legacy/components/source_frame/source_frame.js';
import * as UI from '../../ui/legacy/legacy.js';
import {ApplicationPanelTreeElement, ExpandableApplicationPanelTreeElement} from './ApplicationPanelTreeElement.js';
import {AppManifestView, Events as AppManifestViewEvents} from './AppManifestView.js';
import {BackForwardCacheTreeElement} from './BackForwardCacheTreeElement.js';
import {BackgroundServiceModel} from './BackgroundServiceModel.js';
import {BackgroundServiceView} from './BackgroundServiceView.js';
import {BounceTrackingMitigationsTreeElement} from './BounceTrackingMitigationsTreeElement.js';
import * as ApplicationComponents from './components/components.js';
import {type DOMStorage, DOMStorageModel, Events as DOMStorageModelEvents} from './DOMStorageModel.js';
import {
Events as ExtensionStorageModelEvents,
type ExtensionStorage,
ExtensionStorageModel,
} from './ExtensionStorageModel.js';
import {
type Database as IndexedDBModelDatabase,
type DatabaseId,
Events as IndexedDBModelEvents,
type Index,
IndexedDBModel,
type ObjectStore,
} from './IndexedDBModel.js';
import {IDBDatabaseView, IDBDataView} from './IndexedDBViews.js';
import {Events as InterestGroupModelEvents, InterestGroupStorageModel} from './InterestGroupStorageModel.js';
import {InterestGroupTreeElement} from './InterestGroupTreeElement.js';
import {OpenedWindowDetailsView, WorkerDetailsView} from './OpenedWindowDetailsView.js';
import type * as PreloadingHelper from './preloading/helper/helper.js';
import {
PreloadingSummaryTreeElement,
} from './PreloadingTreeElement.js';
import {ReportingApiTreeElement} from './ReportingApiTreeElement.js';
import type {ResourcesPanel} from './ResourcesPanel.js';
import resourcesSidebarStyles from './resourcesSidebar.css.js';
import {ServiceWorkerCacheTreeElement} from './ServiceWorkerCacheTreeElement.js';
import {ServiceWorkersView} from './ServiceWorkersView.js';
import {SharedStorageListTreeElement} from './SharedStorageListTreeElement.js';
import {
Events as SharedStorageModelEvents,
type SharedStorageForOrigin,
SharedStorageModel,
} from './SharedStorageModel.js';
import {SharedStorageTreeElement} from './SharedStorageTreeElement.js';
import {StorageBucketsTreeParentElement} from './StorageBucketsTreeElement.js';
import {StorageView} from './StorageView.js';
import {TrustTokensTreeElement} from './TrustTokensTreeElement.js';
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 Panelthat shows if no local storage
* can be shown.
*/
noLocalStorage: 'No local storage detected',
/**
*@description Text in Application Panel Sidebar of the Application panel
*/
localStorage: 'Local storage',
/**
*@description Text in the Application panel describing the local storage tab.
*/
localStorageDescription: 'On this page you can view, add, edit, and delete local storage key-value pairs.',
/**
*@description Text in Application Panel Sidebar of the Application panel
*/
sessionStorage: 'Session storage',
/**
*@description Text in Application Panel if no session storage can be shown.
*/
noSessionStorage: 'No session storage detected',
/**
*@description Text in the Application panel describing the session storage tab.
*/
sessionStorageDescription: 'On this page you can view, add, edit, and delete session storage key-value pairs.',
/**
*@description Text in Application Panel Sidebar of the Application panel
*/
extensionStorage: 'Extension storage',
/**
*@description Text in Application Panel if no extension storage can be shown
*/
noExtensionStorage: 'No extension storage detected',
/**
*@description Text in the Application panel describing the extension storage tab.
*/
extensionStorageDescription: 'On this page you can view, add, edit, and delete extension storage key-value pairs.',
/**
*@description Text for extension session storage in Application panel
*/
extensionSessionStorage: 'Session',
/**
*@description Text for extension local storage in Application panel
*/
extensionLocalStorage: 'Local',
/**
*@description Text for extension sync storage in Application panel
*/
extensionSyncStorage: 'Sync',
/**
*@description Text for extension managed storage in Application panel
*/
extensionManagedStorage: 'Managed',
/**
*@description Text for web cookies
*/
cookies: 'Cookies',
/**
*@description Text in the Application Panel if no cookies are set
*/
noCookies: 'No cookies set',
/**
*@description Text for web cookies
*/
cookiesDescription: 'On this page you can view, add, edit, and delete cookies.',
/**
*@description Text in Application Panel Sidebar of the Application panel
*/
backgroundServices: 'Background services',
/**
*@description Text for rendering frames
*/
frames: 'Frames',
/**
*@description Text that appears on a button for the manifest resource type filter.
*/
manifest: 'Manifest',
/**
*@description Text in App Manifest View of the Application panel
*/
noManifestDetected: 'No manifest detected',
/**
*@description Description text on manifests in App Manifest View of the Application panel which describes the app manifest view tab
*/
manifestDescription:
'A manifest defines how your app appears on phone’s home screens and what the app looks like on launch.',
/**
*@description Text in App Manifest View of the Application panel
*/
appManifest: 'Manifest',
/**
*@description Text in Application Panel Sidebar of the Application panel
*/
indexeddb: 'IndexedDB',
/**
*@description Text in Application Panel if no indexedDB is detected
*/
noIndexeddb: 'No indexedDB detected',
/**
*@description Text in the Application panel describing the extension storage tab.
*/
indexeddbDescription: 'On this page you can view and delete indexedDB key-value pairs and databases.',
/**
*@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
*/
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 Text in Frames View of the Application panel
*/
openedWindowsDescription: 'On this page you can view windows opened via window\.open\(\).',
/**
*@description Label for plural of worker type: web workers
*/
webWorkers: 'Web Workers',
/**
*@description Label in frame tree for unavailable document
*/
documentNotAvailable: 'No document detected',
/**
*@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',
/**
*@description Description text for describing the dedicated worker tab.
*/
workerDescription: 'On this page you can view dedicated workers that are created by the parent frame.',
/**
* @description Aria text for screen reader to announce they can scroll to top of manifest if invoked
*/
onInvokeManifestAlert: 'Manifest: Invoke to scroll to the top of manifest',
/**
* @description Aria text for screen reader to announce they can scroll to a section if invoked
* @example {"Identity"} PH1
*/
beforeInvokeAlert: '{PH1}: Invoke to scroll to this section in manifest',
/**
* @description Alert message for screen reader to announce which subsection is being scrolled to
* @example {"Identity"} PH1
*/
onInvokeAlert: 'Scrolled to {PH1}',
/**
* @description Application sidebar panel
*/
applicationSidebarPanel: 'Application panel sidebar',
/**
*@description Tooltip in Application Panel Sidebar of the Application panel
*@example {https://example.com} PH1
*/
thirdPartyPhaseout: 'Cookies from {PH1} may have been blocked due to third-party cookie phaseout.',
/**
* @description Description text in the Application Panel describing a frame's resources
*/
resourceDescription: 'On this page you can view the frame\'s resources.'
} as const;
const str_ = i18n.i18n.registerUIStrings('panels/application/ApplicationPanelSidebar.ts', UIStrings);
const i18nString = i18n.i18n.getLocalizedString.bind(undefined, str_);
function assertNotMainTarget(targetId: Protocol.Target.TargetID|'main'): asserts targetId is Protocol.Target.TargetID {
if (targetId === 'main') {
throw new Error('Unexpected main target id');
}
}
function nameForExtensionStorageArea(storageArea: Protocol.Extensions.StorageArea): string {
switch (storageArea) {
case Protocol.Extensions.StorageArea.Session:
return i18nString(UIStrings.extensionSessionStorage);
case Protocol.Extensions.StorageArea.Local:
return i18nString(UIStrings.extensionLocalStorage);
case Protocol.Extensions.StorageArea.Sync:
return i18nString(UIStrings.extensionSyncStorage);
case Protocol.Extensions.StorageArea.Managed:
return i18nString(UIStrings.extensionManagedStorage);
default:
throw new Error(`Unrecognized storage type: ${storageArea}`);
}
}
export namespace SharedStorageTreeElementDispatcher {
export const enum Events {
SHARED_STORAGE_TREE_ELEMENT_ADDED = 'SharedStorageTreeElementAdded',
}
export interface SharedStorageTreeElementAddedEvent {
origin: string;
}
export interface EventTypes {
[Events.SHARED_STORAGE_TREE_ELEMENT_ADDED]: SharedStorageTreeElementAddedEvent;
}
}
export class ApplicationPanelSidebar extends UI.Widget.VBox implements SDK.TargetManager.Observer {
panel: ResourcesPanel;
private readonly sidebarTree: UI.TreeOutline.TreeOutlineInShadow;
private readonly applicationTreeElement: UI.TreeOutline.TreeElement;
serviceWorkersTreeElement: ServiceWorkersTreeElement;
localStorageListTreeElement: ExpandableApplicationPanelTreeElement;
sessionStorageListTreeElement: ExpandableApplicationPanelTreeElement;
extensionStorageListTreeElement: ExpandableApplicationPanelTreeElement;
indexedDBListTreeElement: IndexedDBTreeElement;
interestGroupTreeElement: InterestGroupTreeElement;
cookieListTreeElement: ExpandableApplicationPanelTreeElement;
trustTokensTreeElement: TrustTokensTreeElement;
cacheStorageListTreeElement: ServiceWorkerCacheTreeElement;
sharedStorageListTreeElement: SharedStorageListTreeElement;
storageBucketsTreeElement: StorageBucketsTreeParentElement|undefined;
private backForwardCacheListTreeElement?: BackForwardCacheTreeElement;
backgroundFetchTreeElement: BackgroundServiceTreeElement;
backgroundSyncTreeElement: BackgroundServiceTreeElement;
bounceTrackingMitigationsTreeElement: BounceTrackingMitigationsTreeElement;
notificationsTreeElement: BackgroundServiceTreeElement;
paymentHandlerTreeElement: BackgroundServiceTreeElement;
periodicBackgroundSyncTreeElement: BackgroundServiceTreeElement;
pushMessagingTreeElement: BackgroundServiceTreeElement;
reportingApiTreeElement: ReportingApiTreeElement;
preloadingSummaryTreeElement: PreloadingSummaryTreeElement|undefined;
private readonly resourcesSection: ResourcesSection;
private domStorageTreeElements: Map<DOMStorage, DOMStorageTreeElement>;
private extensionIdToStorageTreeParentElement: Map<string, ExtensionStorageTreeParentElement>;
private extensionStorageModels: ExtensionStorageModel[];
private extensionStorageTreeElements: Map<string, ExtensionStorageTreeElement>;
private sharedStorageTreeElements: Map<string, SharedStorageTreeElement>;
private domains: Record<string, boolean>;
// Holds main frame target.
private target?: SDK.Target.Target;
private previousHoveredElement?: FrameTreeElement;
readonly sharedStorageTreeElementDispatcher:
Common.ObjectWrapper.ObjectWrapper<SharedStorageTreeElementDispatcher.EventTypes>;
constructor(panel: ResourcesPanel) {
super();
this.panel = panel;
this.sidebarTree = new UI.TreeOutline.TreeOutlineInShadow(UI.TreeOutline.TreeVariant.NAVIGATION_TREE);
this.sidebarTree.registerRequiredCSS(resourcesSidebarStyles);
this.sidebarTree.element.classList.add('resources-sidebar');
this.sidebarTree.hideOverflow();
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, 'application');
const applicationPanelSidebar = this.applicationTreeElement.treeOutline?.contentElement;
if (applicationPanelSidebar) {
applicationPanelSidebar.ariaLabel = i18nString(UIStrings.applicationSidebarPanel);
}
const manifestTreeElement = new AppManifestTreeElement(panel);
this.applicationTreeElement.appendChild(manifestTreeElement);
manifestTreeElement.generateChildren();
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, 'storage');
this.localStorageListTreeElement = new ExpandableApplicationPanelTreeElement(
panel, i18nString(UIStrings.localStorage), i18nString(UIStrings.noLocalStorage),
i18nString(UIStrings.localStorageDescription), 'local-storage');
this.localStorageListTreeElement.setLink(
'https://developer.chrome.com/docs/devtools/storage/localstorage/' as Platform.DevToolsPath.UrlString);
const localStorageIcon = IconButton.Icon.create('table');
this.localStorageListTreeElement.setLeadingIcons([localStorageIcon]);
storageTreeElement.appendChild(this.localStorageListTreeElement);
this.sessionStorageListTreeElement = new ExpandableApplicationPanelTreeElement(
panel, i18nString(UIStrings.sessionStorage), i18nString(UIStrings.noSessionStorage),
i18nString(UIStrings.sessionStorageDescription), 'session-storage');
this.sessionStorageListTreeElement.setLink(
'https://developer.chrome.com/docs/devtools/storage/sessionstorage/' as Platform.DevToolsPath.UrlString);
const sessionStorageIcon = IconButton.Icon.create('table');
this.sessionStorageListTreeElement.setLeadingIcons([sessionStorageIcon]);
storageTreeElement.appendChild(this.sessionStorageListTreeElement);
this.extensionStorageListTreeElement = new ExpandableApplicationPanelTreeElement(
panel, i18nString(UIStrings.extensionStorage), i18nString(UIStrings.noExtensionStorage),
i18nString(UIStrings.extensionStorageDescription), 'extension-storage');
this.extensionStorageListTreeElement.setLink(
'https://developer.chrome.com/docs/extensions/reference/api/storage/' as Platform.DevToolsPath.UrlString);
const extensionStorageIcon = IconButton.Icon.create('table');
this.extensionStorageListTreeElement.setLeadingIcons([extensionStorageIcon]);
storageTreeElement.appendChild(this.extensionStorageListTreeElement);
this.indexedDBListTreeElement = new IndexedDBTreeElement(panel);
this.indexedDBListTreeElement.setLink(
'https://developer.chrome.com/docs/devtools/storage/indexeddb/' as Platform.DevToolsPath.UrlString);
storageTreeElement.appendChild(this.indexedDBListTreeElement);
this.cookieListTreeElement = new ExpandableApplicationPanelTreeElement(
panel, i18nString(UIStrings.cookies), i18nString(UIStrings.noCookies), i18nString(UIStrings.cookiesDescription),
'cookies');
this.cookieListTreeElement.setLink(
'https://developer.chrome.com/docs/devtools/storage/cookies/' as Platform.DevToolsPath.UrlString);
const cookieIcon = IconButton.Icon.create('cookie');
this.cookieListTreeElement.setLeadingIcons([cookieIcon]);
storageTreeElement.appendChild(this.cookieListTreeElement);
this.trustTokensTreeElement = new TrustTokensTreeElement(panel);
storageTreeElement.appendChild(this.trustTokensTreeElement);
this.interestGroupTreeElement = new InterestGroupTreeElement(panel);
storageTreeElement.appendChild(this.interestGroupTreeElement);
this.sharedStorageListTreeElement = new SharedStorageListTreeElement(panel);
storageTreeElement.appendChild(this.sharedStorageListTreeElement);
this.cacheStorageListTreeElement = new ServiceWorkerCacheTreeElement(panel);
storageTreeElement.appendChild(this.cacheStorageListTreeElement);
this.storageBucketsTreeElement = new StorageBucketsTreeParentElement(panel);
storageTreeElement.appendChild(this.storageBucketsTreeElement);
const backgroundServiceSectionTitle = i18nString(UIStrings.backgroundServices);
const backgroundServiceTreeElement = this.addSidebarSection(backgroundServiceSectionTitle, 'background-services');
this.backForwardCacheListTreeElement = new BackForwardCacheTreeElement(panel);
backgroundServiceTreeElement.appendChild(this.backForwardCacheListTreeElement);
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);
this.bounceTrackingMitigationsTreeElement = new BounceTrackingMitigationsTreeElement(panel);
backgroundServiceTreeElement.appendChild(this.bounceTrackingMitigationsTreeElement);
this.notificationsTreeElement =
new BackgroundServiceTreeElement(panel, Protocol.BackgroundService.ServiceName.Notifications);
backgroundServiceTreeElement.appendChild(this.notificationsTreeElement);
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);
this.preloadingSummaryTreeElement = new PreloadingSummaryTreeElement(panel);
backgroundServiceTreeElement.appendChild(this.preloadingSummaryTreeElement);
this.preloadingSummaryTreeElement.constructChildren(panel);
this.pushMessagingTreeElement =
new BackgroundServiceTreeElement(panel, Protocol.BackgroundService.ServiceName.PushMessaging);
backgroundServiceTreeElement.appendChild(this.pushMessagingTreeElement);
this.reportingApiTreeElement = new ReportingApiTreeElement(panel);
backgroundServiceTreeElement.appendChild(this.reportingApiTreeElement);
const resourcesSectionTitle = i18nString(UIStrings.frames);
const resourcesTreeElement = this.addSidebarSection(resourcesSectionTitle, 'frames');
this.resourcesSection = new ResourcesSection(panel, resourcesTreeElement);
this.domStorageTreeElements = new Map();
this.extensionIdToStorageTreeParentElement = new Map();
this.extensionStorageTreeElements = new Map();
this.extensionStorageModels = [];
this.sharedStorageTreeElements = new Map();
this.domains = {};
this.sidebarTree.contentElement.addEventListener('mousemove', this.onmousemove.bind(this), false);
this.sidebarTree.contentElement.addEventListener('mouseleave', this.onmouseleave.bind(this), false);
SDK.TargetManager.TargetManager.instance().observeTargets(this, {scoped: true});
SDK.TargetManager.TargetManager.instance().addModelListener(
SDK.ResourceTreeModel.ResourceTreeModel, SDK.ResourceTreeModel.Events.FrameNavigated, this.frameNavigated, this,
{scoped: true});
const selection = this.panel.lastSelectedItemPath();
if (!selection.length) {
manifestTreeElement.select();
}
SDK.TargetManager.TargetManager.instance().observeModels(
DOMStorageModel, {
modelAdded: (model: DOMStorageModel) => this.domStorageModelAdded(model),
modelRemoved: (model: DOMStorageModel) => this.domStorageModelRemoved(model),
},
{scoped: true});
SDK.TargetManager.TargetManager.instance().observeModels(
ExtensionStorageModel, {
modelAdded: (model: ExtensionStorageModel) => this.extensionStorageModelAdded(model),
modelRemoved: (model: ExtensionStorageModel) => this.extensionStorageModelRemoved(model),
},
{scoped: true});
SDK.TargetManager.TargetManager.instance().observeModels(
IndexedDBModel, {
modelAdded: (model: IndexedDBModel) => this.indexedDBModelAdded(model),
modelRemoved: (model: IndexedDBModel) => this.indexedDBModelRemoved(model),
},
{scoped: true});
SDK.TargetManager.TargetManager.instance().observeModels(
InterestGroupStorageModel, {
modelAdded: (model: InterestGroupStorageModel) => this.interestGroupModelAdded(model),
modelRemoved: (model: InterestGroupStorageModel) => this.interestGroupModelRemoved(model),
},
{scoped: true});
SDK.TargetManager.TargetManager.instance().observeModels(
SharedStorageModel, {
modelAdded: (model: SharedStorageModel) => this.sharedStorageModelAdded(model).catch(err => {
console.error(err);
}),
modelRemoved: (model: SharedStorageModel) => this.sharedStorageModelRemoved(model),
},
{scoped: true});
SDK.TargetManager.TargetManager.instance().observeModels(
SDK.StorageBucketsModel.StorageBucketsModel, {
modelAdded: (model: SDK.StorageBucketsModel.StorageBucketsModel) => this.storageBucketsModelAdded(model),
modelRemoved: (model: SDK.StorageBucketsModel.StorageBucketsModel) => this.storageBucketsModelRemoved(model),
},
{scoped: true});
this.sharedStorageTreeElementDispatcher =
new Common.ObjectWrapper.ObjectWrapper<SharedStorageTreeElementDispatcher.EventTypes>();
this.contentElement.style.contain = 'layout style';
}
private addSidebarSection(title: string, jslogContext: string): UI.TreeOutline.TreeElement {
const treeElement = new UI.TreeOutline.TreeElement(title, true, jslogContext);
treeElement.listItemElement.classList.add('storage-group-list-item');
treeElement.setCollapsible(false);
treeElement.selectable = false;
this.sidebarTree.appendChild(treeElement);
UI.ARIAUtils.markAsHeading(treeElement.listItemElement, 3);
UI.ARIAUtils.setLabel(treeElement.childrenListElement, title);
return treeElement;
}
targetAdded(target: SDK.Target.Target): void {
if (target !== target.outermostTarget()) {
return;
}
this.target = target;
const interestGroupModel = target.model(InterestGroupStorageModel);
if (interestGroupModel) {
interestGroupModel.addEventListener(
InterestGroupModelEvents.INTEREST_GROUP_ACCESS, this.interestGroupAccess, 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);
}
targetRemoved(target: SDK.Target.Target): void {
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);
}
const interestGroupModel = target.model(InterestGroupStorageModel);
if (interestGroupModel) {
interestGroupModel.removeEventListener(
InterestGroupModelEvents.INTEREST_GROUP_ACCESS, this.interestGroupAccess, this);
}
this.resetWithFrames();
}
override focus(): void {
this.sidebarTree.focus();
}
private initialize(): void {
for (const frame of SDK.ResourceTreeModel.ResourceTreeModel.frames()) {
this.addCookieDocument(frame);
}
const interestGroupModel = this.target?.model(InterestGroupStorageModel);
if (interestGroupModel) {
interestGroupModel.enable();
}
this.cacheStorageListTreeElement.initialize();
const backgroundServiceModel = this.target?.model(BackgroundServiceModel) || null;
this.backgroundFetchTreeElement && this.backgroundFetchTreeElement.initialize(backgroundServiceModel);
this.backgroundSyncTreeElement && this.backgroundSyncTreeElement.initialize(backgroundServiceModel);
this.notificationsTreeElement.initialize(backgroundServiceModel);
this.paymentHandlerTreeElement.initialize(backgroundServiceModel);
this.periodicBackgroundSyncTreeElement.initialize(backgroundServiceModel);
this.pushMessagingTreeElement.initialize(backgroundServiceModel);
this.storageBucketsTreeElement?.initialize();
const preloadingModel = this.target?.model(SDK.PreloadingModel.PreloadingModel);
if (preloadingModel) {
this.preloadingSummaryTreeElement?.initialize(preloadingModel);
}
}
private domStorageModelAdded(model: DOMStorageModel): void {
model.enable();
model.storages().forEach(this.addDOMStorage.bind(this));
model.addEventListener(DOMStorageModelEvents.DOM_STORAGE_ADDED, this.domStorageAdded, this);
model.addEventListener(DOMStorageModelEvents.DOM_STORAGE_REMOVED, this.domStorageRemoved, this);
}
private domStorageModelRemoved(model: DOMStorageModel): void {
model.storages().forEach(this.removeDOMStorage.bind(this));
model.removeEventListener(DOMStorageModelEvents.DOM_STORAGE_ADDED, this.domStorageAdded, this);
model.removeEventListener(DOMStorageModelEvents.DOM_STORAGE_REMOVED, this.domStorageRemoved, this);
}
private extensionStorageModelAdded(model: ExtensionStorageModel): void {
this.extensionStorageModels.push(model);
model.enable();
model.storages().forEach(this.addExtensionStorage.bind(this));
model.addEventListener(ExtensionStorageModelEvents.EXTENSION_STORAGE_ADDED, this.extensionStorageAdded, this);
model.addEventListener(ExtensionStorageModelEvents.EXTENSION_STORAGE_REMOVED, this.extensionStorageRemoved, this);
}
private extensionStorageModelRemoved(model: ExtensionStorageModel): void {
console.assert(this.extensionStorageModels.includes(model));
this.extensionStorageModels.splice(this.extensionStorageModels.indexOf(model), 1);
model.storages().forEach(this.removeExtensionStorage.bind(this));
model.removeEventListener(ExtensionStorageModelEvents.EXTENSION_STORAGE_ADDED, this.extensionStorageAdded, this);
model.removeEventListener(
ExtensionStorageModelEvents.EXTENSION_STORAGE_REMOVED, this.extensionStorageRemoved, this);
}
private indexedDBModelAdded(model: IndexedDBModel): void {
model.enable();
this.indexedDBListTreeElement.addIndexedDBForModel(model);
}
private indexedDBModelRemoved(model: IndexedDBModel): void {
this.indexedDBListTreeElement.removeIndexedDBForModel(model);
}
private interestGroupModelAdded(model: InterestGroupStorageModel): void {
model.enable();
model.addEventListener(InterestGroupModelEvents.INTEREST_GROUP_ACCESS, this.interestGroupAccess, this);
}
private interestGroupModelRemoved(model: InterestGroupStorageModel): void {
model.disable();
model.removeEventListener(InterestGroupModelEvents.INTEREST_GROUP_ACCESS, this.interestGroupAccess, this);
}
private async sharedStorageModelAdded(model: SharedStorageModel): Promise<void> {
await model.enable();
for (const storage of model.storages()) {
await this.addSharedStorage(storage);
}
model.addEventListener(SharedStorageModelEvents.SHARED_STORAGE_ADDED, this.sharedStorageAdded, this);
model.addEventListener(SharedStorageModelEvents.SHARED_STORAGE_REMOVED, this.sharedStorageRemoved, this);
model.addEventListener(SharedStorageModelEvents.SHARED_STORAGE_ACCESS, this.sharedStorageAccess, this);
}
private sharedStorageModelRemoved(model: SharedStorageModel): void {
model.disable();
for (const storage of model.storages()) {
this.removeSharedStorage(storage);
}
model.removeEventListener(SharedStorageModelEvents.SHARED_STORAGE_ADDED, this.sharedStorageAdded, this);
model.removeEventListener(SharedStorageModelEvents.SHARED_STORAGE_REMOVED, this.sharedStorageRemoved, this);
model.removeEventListener(SharedStorageModelEvents.SHARED_STORAGE_ACCESS, this.sharedStorageAccess, this);
}
private storageBucketsModelAdded(model: SDK.StorageBucketsModel.StorageBucketsModel): void {
model.enable();
}
private storageBucketsModelRemoved(model: SDK.StorageBucketsModel.StorageBucketsModel): void {
this.storageBucketsTreeElement?.removeBucketsForModel(model);
}
private resetWithFrames(): void {
this.resourcesSection.reset();
this.reset();
}
private treeElementAdded(event: Common.EventTarget.EventTargetEvent<UI.TreeOutline.TreeElement>): void {
// 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 as UI.TreeOutline.TreeElement | ApplicationPanelTreeElement];
for (let parent = element.parent as UI.TreeOutline.TreeElement | ApplicationPanelTreeElement | null;
parent && 'itemURL' in 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] as ApplicationPanelTreeElement).itemURL) {
if (!elementPath[j].expanded) {
if (i > 0) {
elementPath[j].expand();
}
if (!elementPath[j].selected) {
elementPath[j].select();
}
}
i--;
j--;
}
}
private reset(): void {
this.domains = {};
this.cookieListTreeElement.removeChildren();
this.interestGroupTreeElement.clearEvents();
}
private frameNavigated(event: Common.EventTarget.EventTargetEvent<SDK.ResourceTreeModel.ResourceTreeFrame>): void {
const frame = event.data;
if (frame.isOutermostFrame()) {
this.reset();
}
this.addCookieDocument(frame);
}
private interestGroupAccess(event: Common.EventTarget.EventTargetEvent<Protocol.Storage.InterestGroupAccessedEvent>):
void {
this.interestGroupTreeElement.addEvent(event.data);
}
private addCookieDocument(frame: SDK.ResourceTreeModel.ResourceTreeFrame): void {
// In case the current frame was unreachable, show its 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, parsedURL);
this.cookieListTreeElement.appendChild(cookieDomainTreeElement);
}
}
private domStorageAdded(event: Common.EventTarget.EventTargetEvent<DOMStorage>): void {
const domStorage = (event.data);
this.addDOMStorage(domStorage);
}
private addDOMStorage(domStorage: DOMStorage): void {
console.assert(!this.domStorageTreeElements.get(domStorage));
console.assert(Boolean(domStorage.storageKey));
const domStorageTreeElement = new DOMStorageTreeElement(this.panel, domStorage);
this.domStorageTreeElements.set(domStorage, domStorageTreeElement);
if (domStorage.isLocalStorage) {
this.localStorageListTreeElement.appendChild(domStorageTreeElement, comparator);
} else {
this.sessionStorageListTreeElement.appendChild(domStorageTreeElement, comparator);
}
function comparator(a: UI.TreeOutline.TreeElement, b: UI.TreeOutline.TreeElement): number {
const aTitle = a.titleAsText().toLocaleLowerCase();
const bTitle = b.titleAsText().toLocaleUpperCase();
return aTitle.localeCompare(bTitle);
}
}
private domStorageRemoved(event: Common.EventTarget.EventTargetEvent<DOMStorage>): void {
const domStorage = (event.data);
this.removeDOMStorage(domStorage);
}
private removeDOMStorage(domStorage: DOMStorage): void {
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);
}
private extensionStorageAdded(event: Common.EventTarget.EventTargetEvent<ExtensionStorage>): void {
const extensionStorage = event.data;
this.addExtensionStorage(extensionStorage);
}
private useTreeViewForExtensionStorage(extensionStorage: ExtensionStorage): boolean {
// If the origin the storage is associated with matches the top-level
// target (e.g, an extension service worker or top-level
// chrome-extension:// page), there is likely only one extension in the
// context we are inspecting and we can show the storage as a direct child.
// In other contexts (where multiple extensions may be injected) use a tree
// view where storage areas are children of the extension they are
// associated with.
return !extensionStorage.matchesTarget(this.target);
}
private getExtensionStorageAreaParent(extensionStorage: ExtensionStorage): ApplicationPanelTreeElement|undefined {
if (!this.useTreeViewForExtensionStorage(extensionStorage)) {
return this.extensionStorageListTreeElement;
}
const existingParent = this.extensionIdToStorageTreeParentElement.get(extensionStorage.extensionId);
if (existingParent) {
return existingParent;
}
const parent =
new ExtensionStorageTreeParentElement(this.panel, extensionStorage.extensionId, extensionStorage.name);
this.extensionIdToStorageTreeParentElement.set(extensionStorage.extensionId, parent);
this.extensionStorageListTreeElement?.appendChild(parent);
return parent;
}
private addExtensionStorage(extensionStorage: ExtensionStorage): void {
if (this.extensionStorageModels.find(
m => m !== extensionStorage.model &&
m.storageForIdAndArea(extensionStorage.extensionId, extensionStorage.storageArea))) {
// There's at least one model that already has this storage area, so no need
// to do anything.
return;
}
console.assert(Boolean(this.extensionStorageListTreeElement));
console.assert(!this.extensionStorageTreeElements.get(extensionStorage.key));
const extensionStorageTreeElement = new ExtensionStorageTreeElement(this.panel, extensionStorage);
this.extensionStorageTreeElements.set(extensionStorage.key, extensionStorageTreeElement);
this.getExtensionStorageAreaParent(extensionStorage)?.appendChild(extensionStorageTreeElement, comparator);
function comparator(a: UI.TreeOutline.TreeElement, b: UI.TreeOutline.TreeElement): number {
const getStorageArea = (e: UI.TreeOutline.TreeElement): Protocol.Extensions.StorageArea =>
(e as ExtensionStorageTreeElement).storageArea;
const order = [
Protocol.Extensions.StorageArea.Session,
Protocol.Extensions.StorageArea.Local,
Protocol.Extensions.StorageArea.Sync,
Protocol.Extensions.StorageArea.Managed,
];
return order.indexOf(getStorageArea(a)) - order.indexOf(getStorageArea(b));
}
}
private extensionStorageRemoved(event: Common.EventTarget.EventTargetEvent<ExtensionStorage>): void {
const extensionStorage = event.data;
this.removeExtensionStorage(extensionStorage);
}
private removeExtensionStorage(extensionStorage: ExtensionStorage): void {
if (this.extensionStorageModels.find(
(m => m.storageForIdAndArea(extensionStorage.extensionId, extensionStorage.storageArea)))) {
// There's at least one model that still has this storage area, so no need
// to do anything.
return;
}
const treeElement = this.extensionStorageTreeElements.get(extensionStorage.key);
if (!treeElement) {
return;
}
const wasSelected = treeElement.selected;
const parentListTreeElement = treeElement.parent;
if (parentListTreeElement) {
parentListTreeElement.removeChild(treeElement);
if (this.useTreeViewForExtensionStorage(extensionStorage) && parentListTreeElement.childCount() === 0) {
this.extensionStorageListTreeElement?.removeChild(parentListTreeElement);
this.extensionIdToStorageTreeParentElement.delete(extensionStorage.extensionId);
} else if (wasSelected) {
parentListTreeElement.select();
}
}
this.extensionStorageTreeElements.delete(extensionStorage.key);
}
private async sharedStorageAdded(event: Common.EventTarget.EventTargetEvent<SharedStorageForOrigin>): Promise<void> {
await this.addSharedStorage(event.data);
}
private async addSharedStorage(sharedStorage: SharedStorageForOrigin): Promise<void> {
const sharedStorageTreeElement = await SharedStorageTreeElement.createElement(this.panel, sharedStorage);
// A tree element for `sharedStorage.securityOrigin` may have been added while we were waiting for `sharedStorageTreeElement` to be created.
if (this.sharedStorageTreeElements.has(sharedStorage.securityOrigin)) {
return;
}
this.sharedStorageTreeElements.set(sharedStorage.securityOrigin, sharedStorageTreeElement);
this.sharedStorageListTreeElement.appendChild(sharedStorageTreeElement);
this.sharedStorageTreeElementDispatcher.dispatchEventToListeners(
SharedStorageTreeElementDispatcher.Events.SHARED_STORAGE_TREE_ELEMENT_ADDED,
{origin: sharedStorage.securityOrigin});
}
private sharedStorageRemoved(event: Common.EventTarget.EventTargetEvent<SharedStorageForOrigin>): void {
this.removeSharedStorage(event.data);
}
private removeSharedStorage(sharedStorage: SharedStorageForOrigin): void {
const treeElement = this.sharedStorageTreeElements.get(sharedStorage.securityOrigin);
if (!treeElement) {
return;
}
const wasSelected = treeElement.selected;
const parentListTreeElement = treeElement.parent;
if (parentListTreeElement) {
parentListTreeElement.removeChild(treeElement);
parentListTreeElement.setExpandable(parentListTreeElement.childCount() > 0);
if (wasSelected) {
parentListTreeElement.select();
}
}
this.sharedStorageTreeElements.delete(sharedStorage.securityOrigin);
}
private sharedStorageAccess(event: Common.EventTarget.EventTargetEvent<Protocol.Storage.SharedStorageAccessedEvent>):
void {
this.sharedStorageListTreeElement.addEvent(event.data);
}
async showResource(resource: SDK.Resource.Resource, line?: number, column?: number): Promise<void> {
await this.resourcesSection.revealResource(resource, line, column);
}
showFrame(frame: SDK.ResourceTreeModel.ResourceTreeFrame): void {
this.resourcesSection.revealAndSelectFrame(frame);
}
showPreloadingRuleSetView(revealInfo: PreloadingHelper.PreloadingForward.RuleSetView): void {
if (this.preloadingSummaryTreeElement) {
this.preloadingSummaryTreeElement.expandAndRevealRuleSet(revealInfo);
}
}
showPreloadingAttemptViewWithFilter(filter: PreloadingHelper.PreloadingForward.AttemptViewWithFilter): void {
if (this.preloadingSummaryTreeElement) {
this.preloadingSummaryTreeElement.expandAndRevealAttempts(filter);
}
}
private onmousemove(event: MouseEvent): void {
const nodeUnderMouse = (event.target as Node);
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) {
this.previousHoveredElement = element;
element.hovered = true;
}
}
private onmouseleave(_event: MouseEvent): void {
if (this.previousHoveredElement) {
this.previousHoveredElement.hovered = false;
delete this.previousHoveredElement;
}
}
}
export class BackgroundServiceTreeElement extends ApplicationPanelTreeElement {
private serviceName: Protocol.BackgroundService.ServiceName;
private view: BackgroundServiceView|null;
private model: BackgroundServiceModel|null;
private selectedInternal: boolean;
constructor(storagePanel: ResourcesPanel, serviceName: Protocol.BackgroundService.ServiceName) {
super(
storagePanel, BackgroundServiceView.getUIString(serviceName), false,
Platform.StringUtilities.toKebabCase(serviceName));
this.serviceName = serviceName;
/* Whether the element has been selected. */
this.selectedInternal = false;
this.view = null;
this.model = null;
const backgroundServiceIcon = IconButton.Icon.create(this.getIconType());
this.setLeadingIcons([backgroundServiceIcon]);
}
private getIconType(): string {
switch (this.serviceName) {
case Protocol.BackgroundService.ServiceName.BackgroundFetch:
return 'arrow-up-down';
case Protocol.BackgroundService.ServiceName.BackgroundSync:
return 'sync';
case Protocol.BackgroundService.ServiceName.PushMessaging:
return 'cloud';
case Protocol.BackgroundService.ServiceName.Notifications:
return 'bell';
case Protocol.BackgroundService.ServiceName.PaymentHandler:
return 'credit-card';
case Protocol.BackgroundService.ServiceName.PeriodicBackgroundSync:
return 'watch';
default:
console.error(`Service ${this.serviceName} does not have a dedicated icon`);
return 'table';
}
}
initialize(model: BackgroundServiceModel|null): void {
this.model = model;
// Show the view if the model was initialized after selection.
if (this.selectedInternal && !this.view) {
this.onselect(false);
}
}
override get itemURL(): Platform.DevToolsPath.UrlString {
return `background-service://${this.serviceName}` as Platform.DevToolsPath.UrlString;
}
override get selectable(): boolean {
if (!this.model) {
return false;
}
return super.selectable;
}
override onselect(selectedByUser?: boolean): boolean {
super.onselect(selectedByUser);
this.selectedInternal = 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);
Host.userMetrics.panelShown('background_service_' + this.serviceName);
return false;
}
}
export class ServiceWorkersTreeElement extends ApplicationPanelTreeElement {
private view?: ServiceWorkersView;
constructor(storagePanel: ResourcesPanel) {
super(storagePanel, i18n.i18n.lockedString('Service workers'), false, 'service-workers');
const icon = IconButton.Icon.create('gears');
this.setLeadingIcons([icon]);
}
override get itemURL(): Platform.DevToolsPath.UrlString {
return 'service-workers://' as Platform.DevToolsPath.UrlString;
}
override onselect(selectedByUser?: boolean): boolean {
super.onselect(selectedByUser);
if (!this.view) {
this.view = new ServiceWorkersView();
}
this.showView(this.view);
Host.userMetrics.panelShown('service-workers');
return false;
}
}
export class AppManifestTreeElement extends ApplicationPanelTreeElement {
private view: AppManifestView;
constructor(storagePanel: ResourcesPanel) {
super(storagePanel, i18nString(UIStrings.manifest), true, 'manifest');
const icon = IconButton.Icon.create('document');
this.setLeadingIcons([icon]);
self.onInvokeElement(this.listItemElement, this.onInvoke.bind(this));
const emptyView = new UI.EmptyWidget.EmptyWidget(
i18nString(UIStrings.noManifestDetected), i18nString(UIStrings.manifestDescription));
// TODO(crbug.com/1156978): Replace UI.ReportView.ReportView with ReportView.ts web component.
const reportView