feat(bidi): implement UserContexts (#11784)

This commit is contained in:
Alex Rudenko 2024-01-31 09:22:47 +01:00 committed by GitHub
parent 9ff6bd3805
commit 2930a70c88
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 169 additions and 42 deletions

View File

@ -119,7 +119,9 @@ export class BidiBrowser extends Browser {
this.#browserCore = browserCore;
this.#defaultViewport = opts.defaultViewport;
this.#browserTarget = new BiDiBrowserTarget(this);
this.#createBrowserContext(this.#browserCore.defaultUserContext);
for (const context of this.#browserCore.userContexts) {
this.#createBrowserContext(context);
}
}
#initialize() {
@ -159,6 +161,7 @@ export class BidiBrowser extends Browser {
const target = this.#targets.get(event.context);
if (target) {
this.emit(BrowserEvent.TargetChanged, target);
target.browserContext().emit(BrowserContextEvent.TargetChanged, target);
}
}
@ -177,10 +180,12 @@ export class BidiBrowser extends Browser {
this.#browserName
);
this.connection.registerBrowsingContexts(context);
// TODO: once more browsing context types are supported, this should be
// updated to support those. Currently, all top-level contexts are treated
// as pages.
const browserContext = this.browserContexts().at(-1);
const browserContext =
event.userContext === 'default'
? this.defaultBrowserContext()
: this.browserContexts().find(browserContext => {
return browserContext.id === event.userContext;
});
if (!browserContext) {
throw new Error('Missing browser contexts');
}

View File

@ -69,6 +69,7 @@ export class BidiBrowserContext extends BrowserContext {
override async newPage(): Promise<Page> {
const {result} = await this.#connection.send('browsingContext.create', {
type: Bidi.BrowsingContext.CreateType.Tab,
userContext: this.#userContext.id,
});
const target = this.#browser._getTargetById(result.context);
@ -99,16 +100,6 @@ export class BidiBrowserContext extends BrowserContext {
throw new Error('Default context cannot be closed!');
}
// TODO: Remove once we have adopted the new browsing contexts.
for (const target of this.targets()) {
const page = await target?.page();
try {
await page?.close();
} catch (error) {
debugError(error);
}
}
try {
await this.#userContext.remove();
} catch (error) {
@ -142,4 +133,11 @@ export class BidiBrowserContext extends BrowserContext {
override clearPermissionOverrides(): never {
throw new UnsupportedOperation();
}
override get id(): string | undefined {
if (this.#userContext.id === 'default') {
return undefined;
}
return this.#userContext.id;
}
}

View File

@ -85,9 +85,26 @@ export class Browser extends EventEmitter<{
}
});
await this.#syncUserContexts();
await this.#syncBrowsingContexts();
}
async #syncUserContexts() {
const {
result: {userContexts},
} = await this.session.send('browser.getUserContexts', {});
for (const context of userContexts) {
if (context.userContext === UserContext.DEFAULT) {
continue;
}
this.#userContexts.set(
context.userContext,
UserContext.create(this, context.userContext)
);
}
}
async #syncBrowsingContexts() {
// In case contexts are created or destroyed during `getTree`, we use this
// set to detect them.
@ -185,16 +202,14 @@ export class Browser extends EventEmitter<{
});
}
static userContextId = 0;
@throwIfDisposed<Browser>(browser => {
// SAFETY: By definition of `disposed`, `#reason` is defined.
return browser.#reason!;
})
async createUserContext(): Promise<UserContext> {
// TODO: implement incognito context https://github.com/w3c/webdriver-bidi/issues/289.
// TODO: Call `createUserContext` once available.
// Generating a monotonically increasing context id.
const context = `${++Browser.userContextId}`;
const {
result: {userContext: context},
} = await this.session.send('browser.createUserContext', {});
const userContext = UserContext.create(this, context);
this.#userContexts.set(userContext.id, userContext);

View File

@ -38,6 +38,21 @@ export interface Commands {
returnType: Bidi.EmptyResult;
};
'browser.createUserContext': {
params: Bidi.EmptyParams;
returnType: Bidi.Browser.CreateUserContextResult;
};
'browser.getUserContexts': {
params: Bidi.EmptyParams;
returnType: Bidi.Browser.GetUserContextsResult;
};
'browser.removeUserContext': {
params: {
userContext: Bidi.Browser.UserContext;
};
returnType: Bidi.Browser.RemoveUserContext;
};
'browsingContext.activate': {
params: Bidi.BrowsingContext.ActivateParameters;
returnType: Bidi.EmptyResult;

View File

@ -84,6 +84,10 @@ export class UserContext extends EventEmitter<{
return;
}
if (info.userContext !== this.#id) {
return;
}
const browsingContext = BrowsingContext.from(
this,
undefined,
@ -143,6 +147,7 @@ export class UserContext extends EventEmitter<{
type,
...options,
referenceContext: options.referenceContext?.id,
userContext: this.#id,
});
const browsingContext = this.#browsingContexts.get(contextId);
@ -161,7 +166,9 @@ export class UserContext extends EventEmitter<{
})
async remove(): Promise<void> {
try {
// TODO: Call `removeUserContext` once available.
await this.#session.send('browser.removeUserContext', {
userContext: this.#id,
});
} finally {
this.dispose('User context already closed.');
}

View File

@ -353,6 +353,18 @@
"parameters": ["webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[browsercontext.spec] *",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["chrome", "webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions *",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["chrome", "webDriverBiDi"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[browsercontext.spec] BrowserContext BrowserContext.overridePermissions should be prompt by default",
"platforms": ["darwin", "linux", "win32"],
@ -629,12 +641,6 @@
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["SKIP"]
},
{
"testIdPattern": "[launcher.spec] Launcher specs Puppeteer Browser.disconnect should reject navigation when browser closes",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["webDriverBiDi"],
"expectations": ["FAIL", "TIMEOUT"]
},
{
"testIdPattern": "[launcher.spec] Launcher specs Puppeteer Browser.disconnect should reject waitForSelector when browser closes",
"platforms": ["darwin", "linux", "win32"],
@ -1363,6 +1369,20 @@
"parameters": ["chrome", "webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[browsercontext.spec] BrowserContext should create new incognito context",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["FAIL"],
"comment": "Re-test after user contexts land"
},
{
"testIdPattern": "[browsercontext.spec] BrowserContext should fire target events",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["chrome", "webDriverBiDi"],
"expectations": ["FAIL"],
"comment": "In BiDi currently more events than needed are fired (because target is updated more often). We probably need to adjust the test as the behavior is not broken per se"
},
{
"testIdPattern": "[browsercontext.spec] BrowserContext should fire target events",
"platforms": ["darwin", "linux", "win32"],
@ -1370,10 +1390,11 @@
"expectations": ["FAIL"]
},
{
"testIdPattern": "[browsercontext.spec] BrowserContext should provide a context id",
"testIdPattern": "[browsercontext.spec] BrowserContext should have default context",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["cdp", "firefox"],
"expectations": ["FAIL"]
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["FAIL"],
"comment": "Re-test after user contexts land"
},
{
"testIdPattern": "[browsercontext.spec] BrowserContext should timeout waiting for a non-existent target",
@ -1405,6 +1426,13 @@
"parameters": ["chrome", "webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[browsercontext.spec] BrowserContext window.open should use parent tab context",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["FAIL"],
"comment": "Re-test after user contexts land"
},
{
"testIdPattern": "[CDPSession.spec] Target.createCDPSession should be able to detach session",
"platforms": ["darwin", "linux", "win32"],
@ -2894,6 +2922,20 @@
"parameters": ["cdp", "firefox"],
"expectations": ["SKIP"]
},
{
"testIdPattern": "[page.spec] Page Page.Events.Close should work with window.close",
"platforms": ["linux"],
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["TIMEOUT"],
"comment": "Re-test after user contexts land"
},
{
"testIdPattern": "[page.spec] Page Page.Events.Close should work with window.close",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["PASS", "TIMEOUT"],
"comment": "Re-test after user contexts land"
},
{
"testIdPattern": "[page.spec] Page Page.Events.Console should have location and stack trace for console API calls",
"platforms": ["darwin", "linux", "win32"],
@ -2924,6 +2966,20 @@
"parameters": ["cdp", "firefox"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[page.spec] Page Page.Events.Console should not throw when there are console messages in detached iframes",
"platforms": ["linux"],
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["FAIL"],
"comment": "Re-test after user contexts land"
},
{
"testIdPattern": "[page.spec] Page Page.Events.Console should not throw when there are console messages in detached iframes",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["FAIL", "PASS"],
"comment": "Re-test after user contexts land"
},
{
"testIdPattern": "[page.spec] Page Page.Events.Console should trigger correct Log",
"platforms": ["darwin", "linux", "win32"],
@ -3404,6 +3460,13 @@
"parameters": ["chrome", "webDriverBiDi"],
"expectations": ["PASS"]
},
{
"testIdPattern": "[target.spec] Target should be able to use async waitForTarget",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["FAIL"],
"comment": "Re-test after user contexts land"
},
{
"testIdPattern": "[target.spec] Target should contain browser target",
"platforms": ["darwin", "linux", "win32"],
@ -3428,6 +3491,13 @@
"parameters": ["cdp", "firefox"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[target.spec] Target should not crash while redirecting if original request was missed",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["FAIL"],
"comment": "Re-test after user contexts land"
},
{
"testIdPattern": "[target.spec] Target should not crash while redirecting if original request was missed",
"platforms": ["darwin", "linux", "win32"],
@ -3446,6 +3516,13 @@
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["FAIL"]
},
{
"testIdPattern": "[target.spec] Target should report when a new page is created and closed",
"platforms": ["darwin", "linux", "win32"],
"parameters": ["firefox", "webDriverBiDi"],
"expectations": ["FAIL"],
"comment": "Re-test after user contexts land"
},
{
"testIdPattern": "[target.spec] Target should report when a new page is created and closed",
"platforms": ["darwin", "linux", "win32"],

View File

@ -52,7 +52,9 @@ describe('Browser specs', function () {
expect(process!.pid).toBeGreaterThan(0);
});
it('should not return child_process for remote browser', async () => {
const {browser, puppeteer} = await getTestState();
const {browser, puppeteer} = await getTestState({
skipContextCreation: true,
});
const browserWSEndpoint = browser.wsEndpoint();
const remoteBrowser = await puppeteer.connect({
@ -66,7 +68,9 @@ describe('Browser specs', function () {
describe('Browser.isConnected', () => {
it('should set the browser connected state', async () => {
const {browser, puppeteer} = await getTestState();
const {browser, puppeteer} = await getTestState({
skipContextCreation: true,
});
const browserWSEndpoint = browser.wsEndpoint();
const newBrowser = await puppeteer.connect({

View File

@ -214,15 +214,18 @@ describe('BrowserContext', function () {
expect(browser.browserContexts()).toHaveLength(1);
const context = await browser.createIncognitoBrowserContext();
expect(browser.browserContexts()).toHaveLength(2);
const remoteBrowser = await puppeteer.connect({
browserWSEndpoint: browser.wsEndpoint(),
protocol: browser.protocol,
});
const contexts = remoteBrowser.browserContexts();
expect(contexts).toHaveLength(2);
await remoteBrowser.disconnect();
await context.close();
try {
expect(browser.browserContexts()).toHaveLength(2);
const remoteBrowser = await puppeteer.connect({
browserWSEndpoint: browser.wsEndpoint(),
protocol: browser.protocol,
});
const contexts = remoteBrowser.browserContexts();
expect(contexts).toHaveLength(2);
await remoteBrowser.disconnect();
} finally {
await context.close();
}
});
it('should provide a context id', async () => {

View File

@ -48,7 +48,10 @@ describe('Launcher specs', function () {
[
'Navigating frame was detached',
'Protocol error (Page.navigate): Target closed.',
].includes(error.message)
'Protocol error (browsingContext.navigate): Target closed',
].some(message => {
return error.message.startsWith(message);
})
).toBeTruthy();
} finally {
await close();