blob: fb6b4fbbcec1130cf1e2f56c1a6c83765e404fc2 [file] [log] [blame]
// 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);
});
});
});