| // Copyright 2022 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import type * as Common from '../../core/common/common.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 {createTarget, expectConsoleLogs, stubNoopSettings} from '../../testing/EnvironmentHelpers.js'; |
| import { |
| describeWithMockConnection, |
| setMockConnectionResponseHandler, |
| } from '../../testing/MockConnection.js'; |
| import {createResource, getMainFrame} from '../../testing/ResourceTreeHelpers.js'; |
| import * as UI from '../../ui/legacy/legacy.js'; |
| |
| import * as Application from './application.js'; |
| |
| const {urlString} = Platform.DevToolsPath; |
| |
| class SharedStorageTreeElementListener { |
| #sidebar: Application.ApplicationPanelSidebar.ApplicationPanelSidebar; |
| #originsAdded: string[] = []; |
| |
| constructor(sidebar: Application.ApplicationPanelSidebar.ApplicationPanelSidebar) { |
| this.#sidebar = sidebar; |
| |
| this.#sidebar.sharedStorageTreeElementDispatcher.addEventListener( |
| Application.ApplicationPanelSidebar.SharedStorageTreeElementDispatcher.Events.SHARED_STORAGE_TREE_ELEMENT_ADDED, |
| this.#treeElementAdded, this); |
| } |
| |
| dispose(): void { |
| this.#sidebar.sharedStorageTreeElementDispatcher.removeEventListener( |
| Application.ApplicationPanelSidebar.SharedStorageTreeElementDispatcher.Events.SHARED_STORAGE_TREE_ELEMENT_ADDED, |
| this.#treeElementAdded, this); |
| } |
| |
| #treeElementAdded( |
| event: Common.EventTarget.EventTargetEvent<Application.ApplicationPanelSidebar.SharedStorageTreeElementDispatcher |
| .SharedStorageTreeElementAddedEvent>): void { |
| this.#originsAdded.push(event.data.origin); |
| } |
| |
| async waitForElementsAdded(expectedCount: number): Promise<void> { |
| while (this.#originsAdded.length < expectedCount) { |
| await this.#sidebar.sharedStorageTreeElementDispatcher.once( |
| Application.ApplicationPanelSidebar.SharedStorageTreeElementDispatcher.Events |
| .SHARED_STORAGE_TREE_ELEMENT_ADDED); |
| } |
| } |
| } |
| |
| describeWithMockConnection('ApplicationPanelSidebar', () => { |
| let target: SDK.Target.Target; |
| |
| const TEST_ORIGIN_A = 'http://www.example.com/'; |
| const TEST_SITE_A = 'http://example.com'; |
| const TEST_ORIGIN_B = 'http://www.example.org/'; |
| const TEST_ORIGIN_C = 'http://www.example.net/'; |
| const TEST_SITE_C = 'http://example.net'; |
| |
| const TEST_EXTENSION_NAME = 'Test Extension'; |
| |
| const ID = 'main' as Protocol.Page.FrameId; |
| |
| const EVENTS = [ |
| { |
| accessTime: 0, |
| method: Protocol.Storage.SharedStorageAccessMethod.Append, |
| mainFrameId: ID, |
| ownerOrigin: TEST_ORIGIN_A, |
| ownerSite: TEST_SITE_A, |
| params: {key: 'key0', value: 'value0'} as Protocol.Storage.SharedStorageAccessParams, |
| scope: Protocol.Storage.SharedStorageAccessScope.Window, |
| }, |
| { |
| accessTime: 10, |
| method: Protocol.Storage.SharedStorageAccessMethod.Get, |
| mainFrameId: ID, |
| ownerOrigin: TEST_ORIGIN_A, |
| ownerSite: TEST_SITE_A, |
| params: {key: 'key0'} as Protocol.Storage.SharedStorageAccessParams, |
| scope: Protocol.Storage.SharedStorageAccessScope.SharedStorageWorklet, |
| }, |
| { |
| accessTime: 15, |
| method: Protocol.Storage.SharedStorageAccessMethod.Length, |
| mainFrameId: ID, |
| ownerOrigin: TEST_ORIGIN_A, |
| ownerSite: TEST_SITE_A, |
| params: {} as Protocol.Storage.SharedStorageAccessParams, |
| scope: Protocol.Storage.SharedStorageAccessScope.SharedStorageWorklet, |
| }, |
| { |
| accessTime: 20, |
| method: Protocol.Storage.SharedStorageAccessMethod.Clear, |
| mainFrameId: ID, |
| ownerOrigin: TEST_ORIGIN_C, |
| ownerSite: TEST_SITE_C, |
| params: {} as Protocol.Storage.SharedStorageAccessParams, |
| scope: Protocol.Storage.SharedStorageAccessScope.Window, |
| }, |
| { |
| accessTime: 100, |
| method: Protocol.Storage.SharedStorageAccessMethod.Set, |
| mainFrameId: ID, |
| ownerOrigin: TEST_ORIGIN_C, |
| ownerSite: TEST_SITE_C, |
| params: {key: 'key0', value: 'value1', ignoreIfPresent: true} as Protocol.Storage.SharedStorageAccessParams, |
| scope: Protocol.Storage.SharedStorageAccessScope.SharedStorageWorklet, |
| }, |
| { |
| accessTime: 150, |
| method: Protocol.Storage.SharedStorageAccessMethod.RemainingBudget, |
| mainFrameId: ID, |
| ownerOrigin: TEST_ORIGIN_C, |
| ownerSite: TEST_SITE_C, |
| params: {} as Protocol.Storage.SharedStorageAccessParams, |
| scope: Protocol.Storage.SharedStorageAccessScope.SharedStorageWorklet, |
| }, |
| ]; |
| |
| beforeEach(() => { |
| stubNoopSettings(); |
| SDK.ChildTargetManager.ChildTargetManager.install(); |
| const tabTarget = createTarget({type: SDK.Target.Type.TAB}); |
| createTarget({parentTarget: tabTarget, subtype: 'prerender'}); |
| target = createTarget({parentTarget: tabTarget}); |
| sinon.stub(UI.ViewManager.ViewManager.instance(), 'showView').resolves(); // Silence console error |
| setMockConnectionResponseHandler( |
| 'Storage.getSharedStorageEntries', () => ({} as Protocol.Storage.GetSharedStorageEntriesResponse)); |
| setMockConnectionResponseHandler('Storage.setSharedStorageTracking', () => ({})); |
| }); |
| |
| it('shows cookies for all frames', async () => { |
| Application.ResourcesPanel.ResourcesPanel.instance({forceNew: true}); |
| const sidebar = await Application.ResourcesPanel.ResourcesPanel.showAndGetSidebar(); |
| const resourceTreeModel = target.model(SDK.ResourceTreeModel.ResourceTreeModel); |
| assert.exists(resourceTreeModel); |
| sinon.stub(resourceTreeModel, 'frames').returns([ |
| { |
| url: 'https://example.com/', |
| unreachableUrl: () => null, |
| resourceTreeModel: () => resourceTreeModel, |
| } as unknown as SDK.ResourceTreeModel.ResourceTreeFrame, |
| { |
| url: 'https://example.com/admin/', |
| unreachableUrl: () => null, |
| resourceTreeModel: () => resourceTreeModel, |
| } as unknown as SDK.ResourceTreeModel.ResourceTreeFrame, |
| { |
| url: 'https://example.org/', |
| unreachableUrl: () => null, |
| resourceTreeModel: () => resourceTreeModel, |
| } as unknown as SDK.ResourceTreeModel.ResourceTreeFrame, |
| ]); |
| resourceTreeModel.dispatchEventToListeners(SDK.ResourceTreeModel.Events.CachedResourcesLoaded, resourceTreeModel); |
| |
| assert.strictEqual(sidebar.cookieListTreeElement.childCount(), 2); |
| assert.deepEqual( |
| sidebar.cookieListTreeElement.children().map(e => e.title), ['https://example.com', 'https://example.org']); |
| }); |
| |
| it('shows shared storages and events for origins using shared storage', async () => { |
| const securityOriginManager = target.model(SDK.SecurityOriginManager.SecurityOriginManager); |
| assert.exists(securityOriginManager); |
| sinon.stub(securityOriginManager, 'securityOrigins').returns([ |
| TEST_ORIGIN_A, |
| TEST_ORIGIN_B, |
| TEST_ORIGIN_C, |
| ]); |
| |
| const sharedStorageModel = target.model(Application.SharedStorageModel.SharedStorageModel); |
| assert.exists(sharedStorageModel); |
| const setTrackingSpy = sinon.stub(sharedStorageModel.storageAgent, 'invoke_setSharedStorageTracking').resolves({ |
| getError: () => undefined, |
| }); |
| |
| Application.ResourcesPanel.ResourcesPanel.instance({forceNew: true}); |
| const sidebar = await Application.ResourcesPanel.ResourcesPanel.showAndGetSidebar(); |
| |
| const listener = new SharedStorageTreeElementListener(sidebar); |
| const addedPromise = listener.waitForElementsAdded(4); |
| |
| const resourceTreeModel = target.model(SDK.ResourceTreeModel.ResourceTreeModel); |
| assert.exists(resourceTreeModel); |
| resourceTreeModel.dispatchEventToListeners(SDK.ResourceTreeModel.Events.CachedResourcesLoaded, resourceTreeModel); |
| await addedPromise; |
| |
| sinon.assert.calledOnceWithExactly(setTrackingSpy, {enable: true}); |
| |
| assert.strictEqual(sidebar.sharedStorageListTreeElement.childCount(), 4); |
| assert.deepEqual(sidebar.sharedStorageListTreeElement.children().map(e => e.title), [ |
| TEST_ORIGIN_A, TEST_ORIGIN_B, TEST_ORIGIN_C, |
| 'https://example.com', // frame origin |
| ]); |
| |
| sidebar.sharedStorageListTreeElement.view.setDefaultIdForTesting(ID); |
| for (const event of EVENTS) { |
| sharedStorageModel.dispatchEventToListeners(Application.SharedStorageModel.Events.SHARED_STORAGE_ACCESS, event); |
| } |
| |
| assert.deepEqual(sidebar.sharedStorageListTreeElement.view.getEventsForTesting(), EVENTS); |
| }); |
| |
| it('shows extension storage based on added models', async () => { |
| for (const useTreeView of [false, true]) { |
| Application.ResourcesPanel.ResourcesPanel.instance({forceNew: true}); |
| const sidebar = await Application.ResourcesPanel.ResourcesPanel.showAndGetSidebar(); |
| |
| // Cast to any allows overriding private method. |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any |
| sinon.stub(sidebar, 'useTreeViewForExtensionStorage' as any).returns(useTreeView); |
| |
| const extensionStorageModel = target.model(Application.ExtensionStorageModel.ExtensionStorageModel); |
| assert.exists(extensionStorageModel); |
| |
| const makeFakeExtensionStorage = (storageArea: Protocol.Extensions.StorageArea) => |
| new Application.ExtensionStorageModel.ExtensionStorage( |
| extensionStorageModel, '', TEST_EXTENSION_NAME, storageArea); |
| |
| const fakeModelLocal = makeFakeExtensionStorage(Protocol.Extensions.StorageArea.Local); |
| const fakeModelSession = makeFakeExtensionStorage(Protocol.Extensions.StorageArea.Session); |
| |
| extensionStorageModel.dispatchEventToListeners( |
| Application.ExtensionStorageModel.Events.EXTENSION_STORAGE_ADDED, fakeModelLocal); |
| extensionStorageModel.dispatchEventToListeners( |
| Application.ExtensionStorageModel.Events.EXTENSION_STORAGE_ADDED, fakeModelSession); |
| |
| if (useTreeView) { |
| assert.strictEqual(sidebar.extensionStorageListTreeElement.childCount(), 1); |
| assert.strictEqual(sidebar.extensionStorageListTreeElement.children()[0].title, TEST_EXTENSION_NAME); |
| assert.deepEqual( |
| sidebar.extensionStorageListTreeElement.children()[0].children().map(e => e.title), ['Session', 'Local']); |
| } else { |
| assert.strictEqual(sidebar.extensionStorageListTreeElement.childCount(), 2); |
| assert.deepEqual(sidebar.extensionStorageListTreeElement.children().map(e => e.title), ['Session', 'Local']); |
| } |
| |
| extensionStorageModel.dispatchEventToListeners( |
| Application.ExtensionStorageModel.Events.EXTENSION_STORAGE_REMOVED, fakeModelLocal); |
| extensionStorageModel.dispatchEventToListeners( |
| Application.ExtensionStorageModel.Events.EXTENSION_STORAGE_REMOVED, fakeModelSession); |
| assert.strictEqual(sidebar.extensionStorageListTreeElement.childCount(), 0); |
| } |
| }); |
| |
| it('does not add extension storage if already added by another model', async () => { |
| Application.ResourcesPanel.ResourcesPanel.instance({forceNew: true}); |
| const sidebar = await Application.ResourcesPanel.ResourcesPanel.showAndGetSidebar(); |
| |
| // Fakes adding an ExtensionStorage to the ExtensionStorageModel for |
| // `target`. Returns a function that can be used to trigger a removal. |
| const addFakeExtensionStorage = (target: SDK.Target.Target): () => void => { |
| const model = target.model(Application.ExtensionStorageModel.ExtensionStorageModel); |
| assert.exists(model); |
| |
| const extensionStorage = new Application.ExtensionStorageModel.ExtensionStorage( |
| model, '', TEST_EXTENSION_NAME, Protocol.Extensions.StorageArea.Local); |
| |
| const stub = sinon.stub(model, 'storageForIdAndArea').returns(extensionStorage); |
| model.dispatchEventToListeners( |
| Application.ExtensionStorageModel.Events.EXTENSION_STORAGE_ADDED, extensionStorage); |
| |
| return () => { |
| stub.restore(); |
| model.dispatchEventToListeners( |
| Application.ExtensionStorageModel.Events.EXTENSION_STORAGE_REMOVED, extensionStorage); |
| }; |
| }; |
| |
| // Add a fake extension storage to the main target. The UI should be updated. |
| addFakeExtensionStorage(target); |
| assert.strictEqual(sidebar.extensionStorageListTreeElement.children()[0].childCount(), 1); |
| |
| // Add a fake extension storage using a non-main target (e.g, an iframe). |
| // Make sure we don't add a second entry to the UI. |
| const removeFrameStorage = |
| addFakeExtensionStorage(createTarget({type: SDK.Target.Type.FRAME, parentTarget: target})); |
| assert.strictEqual(sidebar.extensionStorageListTreeElement.children()[0].childCount(), 1); |
| |
| // Removing the frame also shouldn't do anything, since the main frame |
| // still exists. |
| removeFrameStorage(); |
| assert.strictEqual(sidebar.extensionStorageListTreeElement.children()[0].childCount(), 1); |
| }); |
| |
| async function getExpectedCall(expectedCall: string): Promise<sinon.SinonSpy> { |
| Application.ResourcesPanel.ResourcesPanel.instance({forceNew: true}); |
| const sidebar = await Application.ResourcesPanel.ResourcesPanel.showAndGetSidebar(); |
| const components = expectedCall.split('.'); |
| assert.lengthOf(components, 2); |
| // @ts-expect-error |
| const object = sidebar[components[0]]; |
| assert.exists(object); |
| return sinon.spy(object, components[1]); |
| } |
| |
| const MOCK_EVENT_ITEM = { |
| addEventListener: () => {}, |
| securityOrigin: 'https://example.com', |
| databaseId: new Application.IndexedDBModel.DatabaseId({storageKey: ''}, ''), |
| getEntries: () => Promise.resolve([]), |
| }; |
| |
| const testUiUpdate = <Events, T extends keyof Events>( |
| event: T, modelClass: new (arg1: SDK.Target.Target) => SDK.SDKModel.SDKModel<Events>, expectedCallString: string, |
| inScope: boolean) => async () => { |
| SDK.TargetManager.TargetManager.instance().setScopeTarget(inScope ? target : null); |
| const expectedCall = await getExpectedCall(expectedCallString); |
| const model = target.model(modelClass); |
| assert.exists(model); |
| const data = [{...MOCK_EVENT_ITEM, model}] as Common.EventTarget.EventPayloadToRestParameters<Events, T>; |
| model.dispatchEventToListeners(event as Platform.TypeScriptUtilities.NoUnion<T>, ...data); |
| await new Promise(resolve => setTimeout(resolve, 0)); |
| assert.strictEqual(expectedCall.called, inScope); |
| }; |
| |
| it('adds interest group event on in scope event', |
| testUiUpdate( |
| Application.InterestGroupStorageModel.Events.INTEREST_GROUP_ACCESS, |
| Application.InterestGroupStorageModel.InterestGroupStorageModel, 'interestGroupTreeElement.addEvent', true)); |
| |
| it('does not add interest group event on out of scope event', |
| testUiUpdate( |
| Application.InterestGroupStorageModel.Events.INTEREST_GROUP_ACCESS, |
| Application.InterestGroupStorageModel.InterestGroupStorageModel, 'interestGroupTreeElement.addEvent', false)); |
| it('adds DOM storage on in scope event', |
| testUiUpdate( |
| Application.DOMStorageModel.Events.DOM_STORAGE_ADDED, Application.DOMStorageModel.DOMStorageModel, |
| 'sessionStorageListTreeElement.appendChild', true)); |
| |
| it('does not add DOM storage on out of scope event', |
| testUiUpdate( |
| Application.DOMStorageModel.Events.DOM_STORAGE_ADDED, Application.DOMStorageModel.DOMStorageModel, |
| 'sessionStorageListTreeElement.appendChild', false)); |
| |
| it('adds indexed DB on in scope event', |
| testUiUpdate( |
| Application.IndexedDBModel.Events.DatabaseAdded, Application.IndexedDBModel.IndexedDBModel, |
| 'indexedDBListTreeElement.appendChild', true)); |
| |
| it('does not add indexed DB on out of scope event', |
| testUiUpdate( |
| Application.IndexedDBModel.Events.DatabaseAdded, Application.IndexedDBModel.IndexedDBModel, |
| 'indexedDBListTreeElement.appendChild', false)); |
| |
| it('adds shared storage on in scope event', |
| testUiUpdate( |
| Application.SharedStorageModel.Events.SHARED_STORAGE_ADDED, Application.SharedStorageModel.SharedStorageModel, |
| 'sharedStorageListTreeElement.appendChild', true)); |
| |
| it('does not add shared storage on out of scope event', |
| testUiUpdate( |
| Application.SharedStorageModel.Events.SHARED_STORAGE_ADDED, Application.SharedStorageModel.SharedStorageModel, |
| 'sharedStorageListTreeElement.appendChild', false)); |
| |
| const MOCK_GETTER_ITEM = { |
| ...MOCK_EVENT_ITEM, |
| ...MOCK_EVENT_ITEM.databaseId, |
| }; |
| // eslint-disable-next-line @typescript-eslint/no-explicit-any |
| const testUiUpdateOnScopeChange = <T extends SDK.SDKModel.SDKModel<any>>( |
| modelClass: new (arg1: SDK.Target.Target) => T, getter: keyof T, expectedCallString: string) => async () => { |
| SDK.TargetManager.TargetManager.instance().setScopeTarget(null); |
| const expectedCall = await getExpectedCall(expectedCallString); |
| const model = target.model(modelClass); |
| assert.exists(model); |
| sinon.stub(model, getter).returns([MOCK_GETTER_ITEM]); |
| SDK.TargetManager.TargetManager.instance().setScopeTarget(target); |
| await new Promise(resolve => setTimeout(resolve, 0)); |
| sinon.assert.called(expectedCall); |
| }; |
| |
| it('adds DOM storage element after scope change', |
| testUiUpdateOnScopeChange( |
| Application.DOMStorageModel.DOMStorageModel, 'storages', 'sessionStorageListTreeElement.appendChild')); |
| |
| it('adds shared storage after scope change', |
| testUiUpdateOnScopeChange( |
| Application.SharedStorageModel.SharedStorageModel, 'storages', 'sharedStorageListTreeElement.appendChild')); |
| |
| it('adds indexed db after scope change', |
| testUiUpdateOnScopeChange( |
| Application.IndexedDBModel.IndexedDBModel, 'databases', 'indexedDBListTreeElement.appendChild')); |
| |
| it('uses extension name when available for tree element title', () => { |
| const panel = Application.ResourcesPanel.ResourcesPanel.instance({forceNew: true}); |
| const extensionName = 'Test Extension'; |
| assert.strictEqual( |
| new Application.ApplicationPanelSidebar.ExtensionStorageTreeParentElement(panel, 'id', extensionName).title, |
| extensionName); |
| }); |
| |
| it('uses extension id as fallback for tree element title', () => { |
| const panel = Application.ResourcesPanel.ResourcesPanel.instance({forceNew: true}); |
| const extensionId = 'id'; |
| assert.strictEqual( |
| new Application.ApplicationPanelSidebar.ExtensionStorageTreeParentElement(panel, extensionId, '').title, |
| extensionId); |
| }); |
| }); |
| |
| describeWithMockConnection('IDBDatabaseTreeElement', () => { |
| beforeEach(() => { |
| stubNoopSettings(); |
| }); |
| expectConsoleLogs({ |
| error: ['Error: No LanguageSelector instance exists yet.'], |
| }); |
| |
| it('only becomes selectable after database is updated', () => { |
| const target = createTarget(); |
| const model = target.model(Application.IndexedDBModel.IndexedDBModel); |
| assert.exists(model); |
| const panel = Application.ResourcesPanel.ResourcesPanel.instance({forceNew: true}); |
| const databaseId = new Application.IndexedDBModel.DatabaseId({storageKey: ''}, ''); |
| const treeElement = new Application.ApplicationPanelSidebar.IDBDatabaseTreeElement(panel, model, databaseId); |
| |
| assert.isFalse(treeElement.selectable); |
| treeElement.update(new Application.IndexedDBModel.Database(databaseId, 1), false); |
| assert.isTrue(treeElement.selectable); |
| }); |
| }); |
| |
| describeWithMockConnection('ResourcesSection', () => { |
| const tests = (inScope: boolean) => () => { |
| let target: SDK.Target.Target; |
| beforeEach(() => { |
| stubNoopSettings(); |
| SDK.FrameManager.FrameManager.instance({forceNew: true}); |
| target = createTarget(); |
| }); |
| |
| expectConsoleLogs({ |
| error: ['Error: No LanguageSelector instance exists yet.'], |
| }); |
| |
| it('adds tree elements for a frame and resource', () => { |
| SDK.TargetManager.TargetManager.instance().setScopeTarget(inScope ? target : null); |
| const panel = Application.ResourcesPanel.ResourcesPanel.instance({forceNew: true}); |
| const treeElement = new UI.TreeOutline.TreeElement(); |
| new Application.ApplicationPanelSidebar.ResourcesSection(panel, treeElement); |
| |
| const model = target.model(SDK.ResourceTreeModel.ResourceTreeModel); |
| assert.exists(model); |
| |
| assert.strictEqual(treeElement.childCount(), 0); |
| const frame = getMainFrame(target); |
| |
| const url = urlString`http://example.com`; |
| assert.strictEqual(treeElement.firstChild()?.childCount() ?? 0, 0); |
| createResource(frame, url, 'text/html', ''); |
| assert.strictEqual(treeElement.firstChild()?.childCount() ?? 0, inScope ? 1 : 0); |
| }); |
| |
| it('picks up existing frames and resource', () => { |
| SDK.TargetManager.TargetManager.instance().setScopeTarget(null); |
| const panel = Application.ResourcesPanel.ResourcesPanel.instance({forceNew: true}); |
| const treeElement = new UI.TreeOutline.TreeElement(); |
| new Application.ApplicationPanelSidebar.ResourcesSection(panel, treeElement); |
| |
| const url = urlString`http://example.com`; |
| createResource(getMainFrame(target), url, 'text/html', ''); |
| assert.strictEqual(treeElement.firstChild()?.childCount() ?? 0, 0); |
| |
| assert.strictEqual(treeElement.childCount(), 0); |
| SDK.TargetManager.TargetManager.instance().setScopeTarget(inScope ? target : null); |
| assert.strictEqual(treeElement.childCount(), inScope ? 1 : 0); |
| assert.strictEqual(treeElement.firstChild()?.childCount() ?? 0, inScope ? 1 : 0); |
| }); |
| }; |
| |
| describe('in scope', tests(true)); |
| describe('out of scope', tests(false)); |
| }); |
| |
| describeWithMockConnection('IndexedDBTreeElement live update', () => { |
| let target: SDK.Target.Target; |
| let model: Application.IndexedDBModel.IndexedDBModel; |
| let sidebar: Application.ApplicationPanelSidebar.ApplicationPanelSidebar; |
| let indexedDBTreeElement: Application.ApplicationPanelSidebar.IndexedDBTreeElement; |
| |
| beforeEach(async () => { |
| stubNoopSettings(); |
| target = createTarget(); |
| model = target.model(Application.IndexedDBModel.IndexedDBModel) as Application.IndexedDBModel.IndexedDBModel; |
| sinon.stub(model, 'refreshDatabase'); |
| sinon.stub(UI.ViewManager.ViewManager.instance(), 'showView').resolves(); // Silence console error |
| SDK.ChildTargetManager.ChildTargetManager.install(); |
| Application.ResourcesPanel.ResourcesPanel.instance({forceNew: true}); |
| sidebar = await Application.ResourcesPanel.ResourcesPanel.showAndGetSidebar(); |
| indexedDBTreeElement = sidebar.indexedDBListTreeElement; |
| }); |
| |
| it('updates tree on database, object store, and index changes', async () => { |
| const MAIN_FRAME_ID = 'main' as Protocol.Page.FrameId; |
| const storageKey = `test-storage-key|${MAIN_FRAME_ID}|http://www.example.com`; |
| assert.strictEqual(indexedDBTreeElement.childCount(), 0); |
| |
| // 1. Create database "database1" |
| const db1Id = new Application.IndexedDBModel.DatabaseId({storageKey}, 'database1'); |
| model.dispatchEventToListeners(Application.IndexedDBModel.Events.DatabaseAdded, {databaseId: db1Id, model}); |
| const db1 = new Application.IndexedDBModel.Database(db1Id, 1); |
| model.dispatchEventToListeners( |
| Application.IndexedDBModel.Events.DatabaseLoaded, {database: db1, model, entriesUpdated: false}); |
| await new Promise(resolve => setTimeout(resolve, 0)); |
| |
| assert.strictEqual(indexedDBTreeElement.childCount(), 1); |
| const db1TreeElement = indexedDBTreeElement.children()[0]; |
| assert.strictEqual(db1TreeElement.titleAsText(), 'database1'); |
| assert.strictEqual(db1TreeElement.childCount(), 0); |
| |
| // 2. Create database "database2" |
| const db2Id = new Application.IndexedDBModel.DatabaseId({storageKey}, 'database2'); |
| model.dispatchEventToListeners(Application.IndexedDBModel.Events.DatabaseAdded, {databaseId: db2Id, model}); |
| const db2 = new Application.IndexedDBModel.Database(db2Id, 1); |
| model.dispatchEventToListeners( |
| Application.IndexedDBModel.Events.DatabaseLoaded, {database: db2, model, entriesUpdated: false}); |
| await new Promise(resolve => setTimeout(resolve, 0)); |
| assert.strictEqual(indexedDBTreeElement.childCount(), 2); |
| assert.strictEqual(indexedDBTreeElement.children()[0].titleAsText(), 'database1'); |
| assert.strictEqual(indexedDBTreeElement.children()[0].childCount(), 0); |
| const db2TreeElement = indexedDBTreeElement.children()[1]; |
| assert.strictEqual(db2TreeElement.titleAsText(), 'database2'); |
| assert.strictEqual(db2TreeElement.childCount(), 0); |
| |
| // 3. Create object store "objectStore1" with index "index1" in "database1" |
| const os1 = new Application.IndexedDBModel.ObjectStore('objectStore1', 'test', false); |
| os1.indexes.set('index1', new Application.IndexedDBModel.Index('index1', 'test', false, false)); |
| db1.objectStores.set('objectStore1', os1); |
| model.dispatchEventToListeners( |
| Application.IndexedDBModel.Events.DatabaseLoaded, {database: db1, model, entriesUpdated: true}); |
| await new Promise(resolve => setTimeout(resolve, 0)); |
| assert.strictEqual(db1TreeElement.childCount(), 1); |
| const os1TreeElement = db1TreeElement.children()[0]; |
| assert.strictEqual(os1TreeElement.titleAsText(), 'objectStore1'); |
| assert.strictEqual(os1TreeElement.childCount(), 1); |
| const index1TreeElement = os1TreeElement.children()[0]; |
| assert.strictEqual(index1TreeElement.titleAsText(), 'index1'); |
| assert.strictEqual(db2TreeElement.childCount(), 0); |
| |
| // 4. Create object store "objectStore2" with index "index2" in "database1" |
| const os2 = new Application.IndexedDBModel.ObjectStore('objectStore2', 'test', false); |
| os2.indexes.set('index2', new Application.IndexedDBModel.Index('index2', 'test', false, false)); |
| db1.objectStores.set('objectStore2', os2); |
| model.dispatchEventToListeners( |
| Application.IndexedDBModel.Events.DatabaseLoaded, {database: db1, model, entriesUpdated: true}); |
| await new Promise(resolve => setTimeout(resolve, 0)); |
| assert.strictEqual(db1TreeElement.childCount(), 2); |
| assert.strictEqual(os1TreeElement.childCount(), 1); |
| assert.strictEqual(os1TreeElement.children()[0].titleAsText(), 'index1'); |
| const os2TreeElement = db1TreeElement.children()[1]; |
| assert.strictEqual(os2TreeElement.titleAsText(), 'objectStore2'); |
| assert.strictEqual(os2TreeElement.childCount(), 1); |
| const index2TreeElement = os2TreeElement.children()[0]; |
| assert.strictEqual(index2TreeElement.titleAsText(), 'index2'); |
| assert.strictEqual(db2TreeElement.childCount(), 0); |
| |
| // 5. Create index "index3" in "objectStore1" in "database1" |
| os1.indexes.set('index3', new Application.IndexedDBModel.Index('index3', 'test', false, false)); |
| model.dispatchEventToListeners( |
| Application.IndexedDBModel.Events.DatabaseLoaded, {database: db1, model, entriesUpdated: true}); |
| await new Promise(resolve => setTimeout(resolve, 0)); |
| assert.strictEqual(os1TreeElement.childCount(), 2); |
| assert.strictEqual(os1TreeElement.children()[0].titleAsText(), 'index1'); |
| assert.strictEqual(os1TreeElement.children()[1].titleAsText(), 'index3'); |
| assert.strictEqual(os2TreeElement.childCount(), 1); |
| |
| // 6. Delete index "index3" from "objectStore1" in "database1" |
| os1.indexes.delete('index3'); |
| model.dispatchEventToListeners( |
| Application.IndexedDBModel.Events.DatabaseLoaded, {database: db1, model, entriesUpdated: true}); |
| await new Promise(resolve => setTimeout(resolve, 0)); |
| assert.strictEqual(os1TreeElement.childCount(), 1); |
| assert.strictEqual(os1TreeElement.children()[0].titleAsText(), 'index1'); |
| |
| // 7. Delete object store "objectStore2" from "database1" |
| db1.objectStores.delete('objectStore2'); |
| model.dispatchEventToListeners( |
| Application.IndexedDBModel.Events.DatabaseLoaded, {database: db1, model, entriesUpdated: true}); |
| await new Promise(resolve => setTimeout(resolve, 0)); |
| assert.strictEqual(db1TreeElement.childCount(), 1); |
| assert.strictEqual(os1TreeElement.titleAsText(), 'objectStore1'); |
| |
| // 8. Delete database "database1" |
| model.dispatchEventToListeners(Application.IndexedDBModel.Events.DatabaseRemoved, {databaseId: db1Id, model}); |
| await new Promise(resolve => setTimeout(resolve, 0)); |
| assert.strictEqual(indexedDBTreeElement.childCount(), 1); |
| assert.strictEqual(indexedDBTreeElement.children()[0].titleAsText(), 'database2'); |
| assert.strictEqual(indexedDBTreeElement.children()[0].childCount(), 0); |
| |
| // 9. Delete database "database2" |
| model.dispatchEventToListeners(Application.IndexedDBModel.Events.DatabaseRemoved, {databaseId: db2Id, model}); |
| await new Promise(resolve => setTimeout(resolve, 0)); |
| assert.strictEqual(indexedDBTreeElement.childCount(), 0); |
| }); |
| }); |