Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Hide the page running the BIDI mapper #2509

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ wptreport*.json
.eslintcache
.nyc_output*
.wireit/
MANIFEST.json
coverage/
*.cddl
wpt-metadata/mapper/headful/webdriver/tests/bidi/
122 changes: 77 additions & 45 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@
},
"dependencies": {
"mitt": "3.0.1",
"rollup-plugin-copy": "3.5.0",
"urlpattern-polyfill": "10.0.0",
"zod": "3.23.8"
}
Expand Down
6 changes: 6 additions & 0 deletions rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import alias from '@rollup/plugin-alias';
import commonjs from '@rollup/plugin-commonjs';
import {nodeResolve} from '@rollup/plugin-node-resolve';
import license from 'rollup-plugin-license';
import copy from 'rollup-plugin-copy'

export default {
input: 'lib/cjs/bidiTab/bidiTab.js',
Expand Down Expand Up @@ -77,5 +78,10 @@ export default {
// without webcrypto exposes globally.
ignore: ['crypto'],
}),
copy({
targets: [
{ src: 'src/extension/*', dest: 'lib/extension' },
]
})
],
};
2 changes: 2 additions & 0 deletions src/bidiServer/BrowserInstance.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import {getMapperTabSource} from './reader.js';
import type {SimpleTransport} from './SimpleTransport.js';

const debugInternal = debug('bidi:mapper:internal');
const pathToExtension = path.join(__dirname, '../../extension');

export type ChromeOptions = {
chromeArgs: string[];
Expand Down Expand Up @@ -80,6 +81,7 @@ export class BrowserInstance {
'--remote-debugging-port=9222',
'--use-mock-keychain',
`--user-data-dir=${profileDir}`,
`--load-extension=${pathToExtension}`,
// keep-sorted end
...chromeOptions.chromeArgs,
'about:blank',
Expand Down
70 changes: 52 additions & 18 deletions src/bidiServer/MapperCdpConnection.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ const getLogger = (type: LogPrefix) => {
}
return logger;
};
const MAX_SW_TARGET_RETRIES = 20;
const RETRY_TIMEOUT_MS = 100;

export class MapperServerCdpConnection {
#cdpConnection: MapperCdpConnection;
Expand Down Expand Up @@ -142,44 +144,76 @@ export class MapperServerCdpConnection {
debugInternal('Initializing Mapper.');

const browserClient = await cdpConnection.createBrowserSession();

let mapperWorkerTargetId: string | null = null;

// Command extension to open an offscreen page.
for (let i = 0; i < MAX_SW_TARGET_RETRIES; i++) {
await new Promise((resolve) => setTimeout(resolve, RETRY_TIMEOUT_MS));
let targets = (await cdpConnection.sendCommand(
'Target.getTargets',
{}
)) as Protocol.Target.GetTargetsResponse;
const mapperWorkerTarget = targets.targetInfos.filter(
(target) => target.type === 'service_worker'
)[0];
if (mapperWorkerTarget !== undefined) {
mapperWorkerTargetId = mapperWorkerTarget.targetId;
break;
}
}

// Run mapper in the first open tab.
if (mapperWorkerTargetId === null) {
throw new Error("Mapper extension did not start");
}

const {sessionId: mapperWorkerSessionId} = await browserClient.sendCommand(
'Target.attachToTarget',
{targetId: mapperWorkerTargetId, flatten: true}
);
const mapperWorkerCdpClient = cdpConnection.getCdpClient(mapperWorkerSessionId);
// `chrome.offscreen` is not immidiately available in the SW context. Evaluate a dummy expression to
// wait for it.
// TODO: Find a better way to wait for when `chrome.offscreen` becomes available.
await mapperWorkerCdpClient.sendCommand('Runtime.evaluate', {
expression:
"console.log(chrome.offscreen)",
});
const res = await mapperWorkerCdpClient.sendCommand('Runtime.evaluate', {
expression:
"chrome.offscreen.createDocument({" +
"url: chrome.runtime.getURL('hello.html')," +
"reasons: ['CLIPBOARD']," +
"justification: 'a context to load the mapper script to'," +
"})",
awaitPromise: true,
userGesture: true,
});
// Run mapper in the opened offscreen page.
const targets = (await cdpConnection.sendCommand(
'Target.getTargets',
{}
)) as Protocol.Target.GetTargetsResponse;
const mapperTabTargetId = targets.targetInfos.filter(
(target) => target.type === 'page'
(target) => target.type === 'page' && target.url.startsWith("chrome-extension://")
)[0]!.targetId;

const {sessionId: mapperSessionId} = await browserClient.sendCommand(
'Target.attachToTarget',
{targetId: mapperTabTargetId, flatten: true}
);

const mapperCdpClient = cdpConnection.getCdpClient(mapperSessionId);

// Click on the body to interact with the page in order to "beforeunload" being
// triggered when the tab is closed.
await mapperCdpClient.sendCommand('Runtime.evaluate', {
expression: 'document.body.click()',
userGesture: true,
});

// Create and activate new tab with a blank page.
await browserClient.sendCommand('Target.createTarget', {
url: 'about:blank',
});

const bidiSession = new SimpleTransport(
async (message) => await this.#sendMessage(mapperCdpClient, message)
);

// Process responses from the mapper tab.
// Process responses from the mapper page.
mapperCdpClient.on('Runtime.bindingCalled', (params) =>
this.#onBindingCalled(params, bidiSession)
);
// Forward console messages from the mapper tab.
// Forward console messages from the mapper page.
mapperCdpClient.on('Runtime.consoleAPICalled', this.#onConsoleAPICalled);
// Catch unhandled exceptions in the mapper.
mapperCdpClient.on(
Expand All @@ -205,7 +239,7 @@ export class MapperServerCdpConnection {
});
}

// Evaluate Mapper Tab sources in the tab.
// Evaluate Mapper sources on the offscreen page.
await mapperCdpClient.sendCommand('Runtime.evaluate', {
expression: mapperTabSource,
});
Expand Down
4 changes: 4 additions & 0 deletions src/extension/hello.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
<html>
<body>
</body>
</html>
12 changes: 12 additions & 0 deletions src/extension/manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
{
"manifest_version": 3,
"name": "Bidi to CDP mapper",
"version": "1.0",
"background": {
"service_worker": "service-worker.js",
"type": "module"
},
"permissions": [
"offscreen"
]
}
Empty file added src/extension/service-worker.js
Empty file.
Loading