| // 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 ProtocolProxyApi from '../../generated/protocol-proxy-api.js'; |
| import type * as Protocol from '../../generated/protocol.js'; |
| import { |
| createTarget, |
| } from '../../testing/EnvironmentHelpers.js'; |
| import { |
| describeWithMockConnection, |
| } from '../../testing/MockConnection.js'; |
| |
| import * as SDK from './sdk.js'; |
| |
| const TARGET_ID = 'TARGET_ID' as Protocol.Target.TargetID; |
| const TITLE = 'TITLE'; |
| |
| let nextTargetId = 0; |
| function createTargetInfo(targetId?: string, type?: string, url?: string, title?: string) { |
| return { |
| targetId: (targetId ?? String(++nextTargetId)) as Protocol.Target.TargetID, |
| type: type ?? 'page', |
| title: title ?? '', |
| url: url ?? 'http://example.com', |
| attached: true, |
| canAccessOpener: true, |
| }; |
| } |
| |
| let nextSessionId = 0; |
| function createSessionId() { |
| return ('SESSION_ID' + ++nextSessionId) as Protocol.Target.SessionID; |
| } |
| |
| describeWithMockConnection('ChildTargetManager', () => { |
| it('adds subtargets', async () => { |
| const target = createTarget(); |
| const childTargetManager = new SDK.ChildTargetManager.ChildTargetManager(target); |
| assert.lengthOf(childTargetManager.childTargets(), 0); |
| await childTargetManager.attachedToTarget( |
| {sessionId: createSessionId(), targetInfo: createTargetInfo(TARGET_ID), waitingForDebugger: false}); |
| assert.lengthOf(childTargetManager.childTargets(), 1); |
| assert.strictEqual(childTargetManager.childTargets()[0].id(), TARGET_ID); |
| }); |
| |
| it('sets subtarget type', async () => { |
| const target = createTarget(); |
| const childTargetManager = new SDK.ChildTargetManager.ChildTargetManager(target); |
| assert.lengthOf(childTargetManager.childTargets(), 0); |
| for (const [protocolType, sdkType] of [ |
| ['iframe', SDK.Target.Type.FRAME], |
| ['webview', SDK.Target.Type.FRAME], |
| ['page', SDK.Target.Type.FRAME], |
| ['background_page', SDK.Target.Type.FRAME], |
| ['app', SDK.Target.Type.FRAME], |
| ['popup_page', SDK.Target.Type.FRAME], |
| ['browser_ui', SDK.Target.Type.FRAME], |
| ['worker', SDK.Target.Type.Worker], |
| ['shared_worker', SDK.Target.Type.SHARED_WORKER], |
| ['service_worker', SDK.Target.Type.ServiceWorker], |
| ['auction_worklet', SDK.Target.Type.AUCTION_WORKLET], |
| ['browser', SDK.Target.Type.BROWSER], |
| ]) { |
| await childTargetManager.attachedToTarget({ |
| sessionId: createSessionId(), |
| targetInfo: createTargetInfo(undefined, protocolType), |
| waitingForDebugger: false, |
| }); |
| const [subtarget] = childTargetManager.childTargets().slice(-1); |
| assert.strictEqual(subtarget.type(), sdkType); |
| } |
| }); |
| |
| it('sets subtarget to frame for devtools scheme if type is other', async () => { |
| const target = createTarget(); |
| const childTargetManager = new SDK.ChildTargetManager.ChildTargetManager(target); |
| assert.lengthOf(childTargetManager.childTargets(), 0); |
| await childTargetManager.attachedToTarget({ |
| sessionId: createSessionId(), |
| targetInfo: createTargetInfo(undefined, 'other', 'devtools://foo/bar'), |
| waitingForDebugger: false, |
| }); |
| let [subtarget] = childTargetManager.childTargets().slice(-1); |
| assert.strictEqual(subtarget.type(), SDK.Target.Type.FRAME); |
| |
| await childTargetManager.attachedToTarget({ |
| sessionId: createSessionId(), |
| targetInfo: createTargetInfo(undefined, 'worker', 'devtools://foo/bar'), |
| waitingForDebugger: false, |
| }); |
| [subtarget] = childTargetManager.childTargets().slice(-1); |
| assert.strictEqual(subtarget.type(), SDK.Target.Type.Worker); |
| }); |
| |
| it('sets subtarget to frame for chrome://print/ if type is other', async () => { |
| const target = createTarget(); |
| const childTargetManager = new SDK.ChildTargetManager.ChildTargetManager(target); |
| assert.lengthOf(childTargetManager.childTargets(), 0); |
| await childTargetManager.attachedToTarget({ |
| sessionId: createSessionId(), |
| targetInfo: createTargetInfo(undefined, 'other', 'chrome://print/'), |
| waitingForDebugger: false, |
| }); |
| const [subtarget] = childTargetManager.childTargets().slice(-1); |
| assert.strictEqual(subtarget.type(), SDK.Target.Type.FRAME); |
| }); |
| |
| it('sets subtarget to frame for chrome://file-manager/ if type is other', async () => { |
| const target = createTarget(); |
| const childTargetManager = new SDK.ChildTargetManager.ChildTargetManager(target); |
| assert.lengthOf(childTargetManager.childTargets(), 0); |
| await childTargetManager.attachedToTarget({ |
| sessionId: createSessionId(), |
| targetInfo: createTargetInfo(undefined, 'other', 'chrome://file-manager/?%7B%22allowedPaths%22:%22anyPathOrUrl'), |
| waitingForDebugger: false, |
| }); |
| const [subtarget] = childTargetManager.childTargets().slice(-1); |
| assert.strictEqual(subtarget.type(), SDK.Target.Type.FRAME); |
| }); |
| |
| it('sets subtarget to frame for sidebar URLs if type is other', async () => { |
| const target = createTarget(); |
| const childTargetManager = new SDK.ChildTargetManager.ChildTargetManager(target); |
| assert.lengthOf(childTargetManager.childTargets(), 0); |
| await childTargetManager.attachedToTarget({ |
| sessionId: createSessionId(), |
| targetInfo: createTargetInfo(undefined, 'other', 'chrome://read-later.top-chrome/'), |
| waitingForDebugger: false, |
| }); |
| let [subtarget] = childTargetManager.childTargets().slice(-1); |
| assert.strictEqual(subtarget.type(), SDK.Target.Type.FRAME); |
| |
| await childTargetManager.attachedToTarget({ |
| sessionId: createSessionId(), |
| targetInfo: createTargetInfo(undefined, 'other', 'chrome://booksmarks-side-panel.top-chrome/'), |
| waitingForDebugger: false, |
| }); |
| [subtarget] = childTargetManager.childTargets().slice(-1); |
| assert.strictEqual(subtarget.type(), SDK.Target.Type.FRAME); |
| }); |
| |
| it('sets worker target name to the target title', async () => { |
| const target = createTarget(); |
| const childTargetManager = new SDK.ChildTargetManager.ChildTargetManager(target); |
| assert.lengthOf(childTargetManager.childTargets(), 0); |
| await childTargetManager.attachedToTarget({ |
| sessionId: createSessionId(), |
| targetInfo: createTargetInfo(undefined, 'worker', 'http://example.com/worker.js', TITLE), |
| waitingForDebugger: false, |
| }); |
| assert.strictEqual(childTargetManager.childTargets()[0].name(), TITLE); |
| }); |
| |
| it('sets non-frame target name to the last path component if present', async () => { |
| const target = createTarget(); |
| const childTargetManager = new SDK.ChildTargetManager.ChildTargetManager(target); |
| assert.lengthOf(childTargetManager.childTargets(), 0); |
| await childTargetManager.attachedToTarget({ |
| sessionId: createSessionId(), |
| targetInfo: createTargetInfo(undefined, 'service_worker', 'http://example.org/service_worker.html', TITLE), |
| waitingForDebugger: false, |
| }); |
| await childTargetManager.attachedToTarget({ |
| sessionId: createSessionId(), |
| targetInfo: createTargetInfo(undefined, 'worker', 'http://example.com/worker.js'), |
| waitingForDebugger: false, |
| }); |
| assert.strictEqual(childTargetManager.childTargets()[0].name(), 'service_worker.html'); |
| assert.strictEqual(childTargetManager.childTargets()[1].name(), 'worker.js'); |
| }); |
| |
| it('sets non-frame target a numbered name if it cannot use URL path', async () => { |
| const target = createTarget(); |
| const childTargetManager = new SDK.ChildTargetManager.ChildTargetManager(target); |
| assert.lengthOf(childTargetManager.childTargets(), 0); |
| await childTargetManager.attachedToTarget({ |
| sessionId: createSessionId(), |
| targetInfo: createTargetInfo(undefined, 'page', 'data:text/html,<!doctype html>'), |
| waitingForDebugger: false, |
| }); |
| await childTargetManager.attachedToTarget({ |
| sessionId: createSessionId(), |
| targetInfo: createTargetInfo(undefined, 'iframe', 'data:text/html,<!doctype html>'), |
| waitingForDebugger: false, |
| }); |
| await childTargetManager.attachedToTarget({ |
| sessionId: createSessionId(), |
| targetInfo: createTargetInfo(undefined, 'webview', 'data:text/html,<!doctype html>'), |
| waitingForDebugger: false, |
| }); |
| // The targets above are frames and should not be given a numbered named |
| await childTargetManager.attachedToTarget({ |
| sessionId: createSessionId(), |
| targetInfo: |
| createTargetInfo(undefined, 'service_worker', 'data:application/javascript;console.log("Service Worker")'), |
| waitingForDebugger: false, |
| }); |
| await childTargetManager.attachedToTarget({ |
| sessionId: createSessionId(), |
| targetInfo: createTargetInfo(undefined, 'worker', 'data:application/javascript;console.log("Worker")'), |
| waitingForDebugger: false, |
| }); |
| assert.strictEqual(childTargetManager.childTargets()[3].name(), '#1'); |
| assert.strictEqual(childTargetManager.childTargets()[4].name(), '#2'); |
| }); |
| |
| it('calls attach callback', async () => { |
| const target = createTarget(); |
| const childTargetManager = new SDK.ChildTargetManager.ChildTargetManager(target); |
| const attachCallback = sinon.spy(); |
| SDK.ChildTargetManager.ChildTargetManager.install(attachCallback); |
| await childTargetManager.attachedToTarget( |
| {sessionId: createSessionId(), targetInfo: createTargetInfo(TARGET_ID), waitingForDebugger: false}); |
| sinon.assert.calledOnce(attachCallback); |
| assert.strictEqual(attachCallback.firstCall.firstArg.target.id(), TARGET_ID); |
| assert.isFalse(attachCallback.firstCall.firstArg.waitingForDebugger); |
| |
| const OTHER_TARGET_ID = 'OTHER_TARGET_ID' as Protocol.Target.TargetID; |
| await childTargetManager.attachedToTarget( |
| {sessionId: createSessionId(), targetInfo: createTargetInfo(OTHER_TARGET_ID), waitingForDebugger: true}); |
| sinon.assert.calledTwice(attachCallback); |
| assert.strictEqual(attachCallback.secondCall.firstArg.target.id(), OTHER_TARGET_ID); |
| assert.isTrue(attachCallback.secondCall.firstArg.waitingForDebugger); |
| }); |
| |
| it('marks the target as crashed when it crashes', async () => { |
| const parentTarget = createTarget(); |
| const childTargetManager = new SDK.ChildTargetManager.ChildTargetManager(parentTarget); |
| await childTargetManager.attachedToTarget({ |
| sessionId: createSessionId(), |
| targetInfo: createTargetInfo('child-target-id'), |
| waitingForDebugger: false, |
| }); |
| const target = childTargetManager.childTargets().at(0); |
| assert.isDefined(target); |
| assert.strictEqual(target.id(), 'child-target-id'); |
| childTargetManager.targetCrashed( |
| {targetId: target.id() as Protocol.Target.TargetID, status: 'crashed', errorCode: 1}); |
| |
| assert.isTrue(target.hasCrashed()); |
| }); |
| |
| it('"un-crashes" a target when the target info message is received', async () => { |
| const parentTarget = createTarget(); |
| const childTargetManager = new SDK.ChildTargetManager.ChildTargetManager(parentTarget); |
| await childTargetManager.attachedToTarget({ |
| sessionId: createSessionId(), |
| targetInfo: createTargetInfo('child-target-id'), |
| waitingForDebugger: false, |
| }); |
| const target = childTargetManager.childTargets().at(0); |
| assert.isDefined(target); |
| assert.strictEqual(target.id(), 'child-target-id'); |
| childTargetManager.targetCrashed( |
| {targetId: target.id() as Protocol.Target.TargetID, status: 'crashed', errorCode: 1}); |
| assert.isTrue(target.hasCrashed()); |
| childTargetManager.targetInfoChanged({targetInfo: createTargetInfo(target.id())}); |
| assert.isFalse(target.hasCrashed()); |
| }); |
| |
| describe('Storage initialization', () => { |
| // Temporarily disabled until the root cause for the crashers in https://crbug.com/466134219 is |
| // found and resolved. |
| it.skip( |
| '[crbug.com/406991275] should initialize storage for a top-level worker with STORAGE capability', async () => { |
| const parentTarget = createTarget({type: SDK.Target.Type.BROWSER}); |
| |
| const getStorageKeyStub = sinon.stub().resolves({ |
| storageKey: 'https://example.com/' as Protocol.Storage.SerializedStorageKey, |
| getError: () => undefined, |
| }); |
| |
| sinon.stub(SDK.Target.Target.prototype, 'storageAgent').returns({ |
| invoke_getStorageKey: getStorageKeyStub, |
| } as sinon.SinonStubbedInstance<ProtocolProxyApi.StorageApi>); |
| |
| const setMainStorageKeySpy = |
| sinon.spy(SDK.StorageKeyManager.StorageKeyManager.prototype, 'setMainStorageKey'); |
| const updateStorageKeysSpy = |
| sinon.spy(SDK.StorageKeyManager.StorageKeyManager.prototype, 'updateStorageKeys'); |
| const setMainSecurityOriginSpy = |
| sinon.spy(SDK.SecurityOriginManager.SecurityOriginManager.prototype, 'setMainSecurityOrigin'); |
| const updateSecurityOriginsSpy = |
| sinon.spy(SDK.SecurityOriginManager.SecurityOriginManager.prototype, 'updateSecurityOrigins'); |
| |
| const childTargetManager = new SDK.ChildTargetManager.ChildTargetManager(parentTarget); |
| await childTargetManager.attachedToTarget({ |
| sessionId: createSessionId(), |
| targetInfo: createTargetInfo(undefined, 'service_worker'), |
| waitingForDebugger: false, |
| }); |
| |
| assert.isTrue(getStorageKeyStub.calledOnceWith({})); |
| assert.isTrue(setMainStorageKeySpy.calledOnceWith('https://example.com/')); |
| assert.isTrue(updateStorageKeysSpy.calledOnceWith(new Set(['https://example.com/']))); |
| assert.isTrue(setMainSecurityOriginSpy.calledOnceWith('https://example.com', '')); |
| assert.isTrue(updateSecurityOriginsSpy.calledOnceWith(new Set(['https://example.com']))); |
| }); |
| |
| it('should NOT initialize storage for a frame target', async () => { |
| const parentTarget = createTarget(); |
| const childTargetManager = new SDK.ChildTargetManager.ChildTargetManager(parentTarget); |
| const initializeStorageSpy = |
| sinon.spy(childTargetManager, 'initializeStorage' as keyof typeof childTargetManager); |
| |
| await childTargetManager.attachedToTarget({ |
| sessionId: createSessionId(), |
| targetInfo: createTargetInfo(undefined, 'iframe'), |
| waitingForDebugger: false, |
| }); |
| |
| sinon.assert.notCalled(initializeStorageSpy); |
| }); |
| |
| it('should NOT initialize storage for a worker without STORAGE capability', async () => { |
| const parentTarget = createTarget(); |
| const childTargetManager = new SDK.ChildTargetManager.ChildTargetManager(parentTarget); |
| const initializeStorageSpy = |
| sinon.spy(childTargetManager, 'initializeStorage' as keyof typeof childTargetManager); |
| |
| await childTargetManager.attachedToTarget({ |
| sessionId: createSessionId(), |
| targetInfo: createTargetInfo(undefined, 'service_worker'), |
| waitingForDebugger: false, |
| }); |
| |
| sinon.assert.notCalled(initializeStorageSpy); |
| }); |
| }); |
| }); |