Puppeteerのコードを見つつ、BiDiを手でさわってみる
protocolにそういう指定をするところが違う、と。

現時点ではFirefoxしか対応してなさげ。

起動パラメータが変わるとかではなく、設定値に書かれる値が変わるだけ?
リモートデバッギングを有効にするのはオプションパラメータで、そのときに使うのがCDPかWebDriver BiDiかの違いのようだ。
userDataDir = await mkdtemp(this.getProfilePath());
firefoxArguments.push('--profile');
firefoxArguments.push(userDataDir);
}
await createProfile(SupportedBrowsers.FIREFOX, {
path: userDataDir,
preferences: FirefoxLauncher.getPreferences(
extraPrefsFirefox,
options.protocol
),
});
static getPreferences(
extraPrefsFirefox?: Record<string, unknown>,
protocol?: 'cdp' | 'webDriverBiDi'
): Record<string, unknown> {
return {
...extraPrefsFirefox,
...(protocol === 'webDriverBiDi'
? {
// Only enable the WebDriver BiDi protocol
'remote.active-protocols': 1,
}
: {
// Do not close the window when the last tab gets closed
'browser.tabs.closeWindowWithLastTab': false,
// Prevent various error message on the console
// jest-puppeteer asserts that no error message is emitted by the console
'network.cookie.cookieBehavior': 0,
// Temporarily force disable BFCache in parent (https://bit.ly/bug-1732263)
'fission.bfcacheInParent': false,
// Only enable the CDP protocol
'remote.active-protocols': 2,
}),
// Force all web content to use a single content process. TODO: remove
// this once Firefox supports mouse event dispatch from the main frame
// context. Once this happens, webContentIsolationStrategy should only
// be set for CDP. See
// https://bugzilla.mozilla.org/show_bug.cgi?id=1773393
'fission.webContentIsolationStrategy': 0,
};
Firefoxのドキュメントにも明記がある
remote.active-protocols¶
Defines the remote protocols that are active. Available protocols are, WebDriver BiDi (1), and CDP (2). Multiple protocols can be activated at the same time by using bitwise or with the values. Defaults to 3 (WebDriver BiDi and CDP).
import { createProfile } from '@puppeteer/browsers';
await createProfile('firefox', {
path: '/Users/yusuke-iwaki/src/github.com/YusukeIwaki/bidi_playground_firefox/my_prefs',
preferences: {
'remote.active-protocols': 1,
'fission.webContentIsolationStrategy': 0,
}
})
console.log("DONE")
node profile.mjs
すると、以下のようなファイルが書き出された。
user_pref("app.normandy.api_url", "");
user_pref("app.update.checkInstallTime", false);
user_pref("app.update.disabledForTesting", true);
user_pref("apz.content_response_timeout", 60000);
user_pref("browser.contentblocking.features.standard", "-tp,tpPrivate,cookieBehavior0,-cm,-fp");
user_pref("browser.dom.window.dump.enabled", true);
user_pref("browser.newtabpage.activity-stream.feeds.system.topstories", false);
user_pref("browser.newtabpage.enabled", false);
user_pref("browser.pagethumbnails.capturing_disabled", true);
user_pref("browser.safebrowsing.blockedURIs.enabled", false);
user_pref("browser.safebrowsing.downloads.enabled", false);
user_pref("browser.safebrowsing.malware.enabled", false);
user_pref("browser.safebrowsing.phishing.enabled", false);
user_pref("browser.search.update", false);
user_pref("browser.sessionstore.resume_from_crash", false);
user_pref("browser.shell.checkDefaultBrowser", false);
user_pref("browser.startup.homepage", "about:blank");
user_pref("browser.startup.homepage_override.mstone", "ignore");
user_pref("browser.startup.page", 0);
user_pref("browser.tabs.disableBackgroundZombification", false);
user_pref("browser.tabs.warnOnCloseOtherTabs", false);
user_pref("browser.tabs.warnOnOpen", false);
user_pref("browser.translations.automaticallyPopup", false);
user_pref("browser.uitour.enabled", false);
user_pref("browser.urlbar.suggest.searches", false);
user_pref("browser.usedOnWindows10.introURL", "");
user_pref("browser.warnOnQuit", false);
user_pref("datareporting.healthreport.documentServerURI", "http://dummy.test/dummy/healthreport/");
user_pref("datareporting.healthreport.logging.consoleEnabled", false);
user_pref("datareporting.healthreport.service.enabled", false);
user_pref("datareporting.healthreport.service.firstRun", false);
user_pref("datareporting.healthreport.uploadEnabled", false);
user_pref("datareporting.policy.dataSubmissionEnabled", false);
user_pref("datareporting.policy.dataSubmissionPolicyBypassNotification", true);
user_pref("devtools.jsonview.enabled", false);
user_pref("dom.disable_open_during_load", false);
user_pref("dom.file.createInChild", true);
user_pref("dom.ipc.reportProcessHangs", false);
user_pref("dom.max_chrome_script_run_time", 0);
user_pref("dom.max_script_run_time", 0);
user_pref("extensions.autoDisableScopes", 0);
user_pref("extensions.enabledScopes", 5);
user_pref("extensions.getAddons.cache.enabled", false);
user_pref("extensions.installDistroAddons", false);
user_pref("extensions.screenshots.disabled", true);
user_pref("extensions.update.enabled", false);
user_pref("extensions.update.notifyUser", false);
user_pref("extensions.webservice.discoverURL", "http://dummy.test/dummy/discoveryURL");
user_pref("focusmanager.testmode", true);
user_pref("general.useragent.updates.enabled", false);
user_pref("geo.provider.testing", true);
user_pref("geo.wifi.scan", false);
user_pref("hangmonitor.timeout", 0);
user_pref("javascript.options.showInConsole", true);
user_pref("media.gmp-manager.updateEnabled", false);
user_pref("media.sanity-test.disabled", true);
user_pref("network.cookie.sameSite.laxByDefault", false);
user_pref("network.http.prompt-temp-redirect", false);
user_pref("network.http.speculative-parallel-limit", 0);
user_pref("network.manage-offline-status", false);
user_pref("network.sntp.pools", "dummy.test");
user_pref("plugin.state.flash", 0);
user_pref("privacy.trackingprotection.enabled", false);
user_pref("remote.enabled", true);
user_pref("security.certerrors.mitm.priming.enabled", false);
user_pref("security.fileuri.strict_origin_policy", false);
user_pref("security.notification_enable_delay", 0);
user_pref("services.settings.server", "http://dummy.test/dummy/blocklist/");
user_pref("signon.autofillForms", false);
user_pref("signon.rememberSignons", false);
user_pref("startup.homepage_welcome_url", "about:blank");
user_pref("startup.homepage_welcome_url.additional", "");
user_pref("toolkit.cosmeticAnimations.enabled", false);
user_pref("toolkit.startup.max_resumed_crashes", -1);
user_pref("remote.active-protocols", 1);
user_pref("fission.webContentIsolationStrategy", 0);
#!/bin/sh
"/Applications/Firefox Developer Edition.app/Contents/MacOS/firefox" \
--remote-debugging-port=0 \
--profile my_prefs \
--no-remote \
--foreground \
about:blank
$ ./launch.sh
console.warn: services.settings: Ignoring preference override of remote settings server
console.warn: services.settings: Allow by setting MOZ_REMOTE_SETTINGS_DEVTOOLS=1 in the environment
WebDriver BiDi listening on ws://127.0.0.1:51376

こんな感じでblank画面が起動し、BiDiポートが開かれる。

/session というところにWebSocket経由でしゃべりかけるといいらしい。
./node_modules/.bin/wscat -c ws://127.0.0.1:51376/session
Connected (press CTRL+C to quit)
import { launch } from 'puppeteer-core'
const browser = await launch({
product: 'firefox',
protocol: 'webDriverBiDi',
executablePath: '/Applications/Firefox Developer Edition.app/Contents/MacOS/firefox',
})
const page = await browser.newPage()
await page.goto('https://github.com/YusukeIwaki')
await page.screenshot({path: 'YusukeIwaki.png'})
await browser.close()
こういうサンプルコードを動かしてみる。
$ DEBUG=puppeteer:* node yusukeiwaki.mjs
puppeteer:browsers:launcher Launching /Applications/Firefox Developer Edition.app/Contents/MacOS/firefox --no-remote --foreground --headless about:blank --remote-debugging-port=0 --profile /var/folders/d6/f04pgvzn7zv_sfkxgzs5tfxc0000gn/T/puppeteer_dev_firefox_profile-Ugxjz0 { detached: true, env: {}, stdio: [ 'pipe', 'ignore', 'pipe' ] } +0ms
puppeteer:browsers:launcher Launched 98295 +6ms
puppeteer:webDriverBiDi:SEND ► [
puppeteer:webDriverBiDi:SEND ► '{"id":1,"method":"session.new","params":{"capabilities":{"alwaysMatch":{"acceptInsecureCerts":false,"webSocketUrl":true}}}}'
puppeteer:webDriverBiDi:SEND ► ] +0ms
puppeteer:webDriverBiDi:RECV ◀ [
puppeteer:webDriverBiDi:RECV ◀ '{"type":"success","id":1,"result":{"sessionId":"a421f2b8-9da8-4bf3-8ba3-f39a9bcec3f4","capabilities":{"browserName":"firefox","browserVersion":"125.0","platformName":"mac","acceptInsecureCerts":false,"proxy":{},"setWindowRect":true,"userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:125.0) Gecko/20100101 Firefox/125.0","moz:accessibilityChecks":false,"moz:buildID":"20240405130120","moz:headless":true,"moz:platformVersion":"23.3.0","moz:processID":98295,"moz:profile":"/var/folders/d6/f04pgvzn7zv_sfkxgzs5tfxc0000gn/T/puppeteer_dev_firefox_profile-Ugxjz0","moz:shutdownTimeout":60000,"moz:windowless":false}}}'
puppeteer:webDriverBiDi:RECV ◀ ] +0ms
puppeteer:webDriverBiDi:SEND ► [ '{"id":2,"method":"browser.getUserContexts","params":{}}' ] +0ms
puppeteer:webDriverBiDi:RECV ◀ [
puppeteer:webDriverBiDi:RECV ◀ '{"type":"success","id":2,"result":{"userContexts":[{"userContext":"default"},{"userContext":"20ebb20a-259d-489c-99af-9c916b83022f"},{"userContext":"8c6db705-9ce3-4b5c-adb2-d2db53c8d443"},{"userContext":"59494d2b-d668-4ee0-84ac-8c2a21fa6f4a"},{"userContext":"dfc081f1-47a2-4d41-a086-6d1b96eef8fd"}]}}'
puppeteer:webDriverBiDi:RECV ◀ ] +0ms
puppeteer:webDriverBiDi:SEND ► [ '{"id":3,"method":"browsingContext.getTree","params":{}}' ] +0ms
puppeteer:webDriverBiDi:RECV ◀ [
puppeteer:webDriverBiDi:RECV ◀ '{"type":"success","id":3,"result":{"contexts":[{"children":[],"context":"c4ee8e5e-6b43-4fd6-974a-c09cb16e8b39","url":"about:blank","userContext":"default","parent":null}]}}'
puppeteer:webDriverBiDi:RECV ◀ ] +0ms
puppeteer:webDriverBiDi:SEND ► [
puppeteer:webDriverBiDi:SEND ► '{"id":4,"method":"session.subscribe","params":{"events":["browsingContext","network","log","script"]}}'
puppeteer:webDriverBiDi:SEND ► ] +0ms
puppeteer:webDriverBiDi:RECV ◀ [
puppeteer:webDriverBiDi:RECV ◀ '{"type":"event","method":"script.realmCreated","params":{"realm":"6bff0075-2438-4bdc-bc3f-545c9fc5e43c","origin":"null","context":"c4ee8e5e-6b43-4fd6-974a-c09cb16e8b39","type":"window"}}'
puppeteer:webDriverBiDi:RECV ◀ ] +0ms
puppeteer:webDriverBiDi:RECV ◀ [ '{"type":"success","id":4,"result":{}}' ] +0ms
puppeteer:webDriverBiDi:SEND ► [
puppeteer:webDriverBiDi:SEND ► '{"id":5,"method":"browsingContext.create","params":{"type":"tab","userContext":"default"}}'
puppeteer:webDriverBiDi:SEND ► ] +0ms
puppeteer:webDriverBiDi:RECV ◀ [
puppeteer:webDriverBiDi:RECV ◀ '{"type":"event","method":"browsingContext.contextCreated","params":{"children":null,"context":"851bf1c6-652f-487c-89ef-c0bc231782ae","url":"about:blank","userContext":"default","parent":null}}'
puppeteer:webDriverBiDi:RECV ◀ ] +0ms
puppeteer:webDriverBiDi:RECV ◀ [
puppeteer:webDriverBiDi:RECV ◀ '{"type":"event","method":"script.realmCreated","params":{"realm":"d14a4c8f-b576-4ff0-8dfe-abcba8def3f1","origin":"null","context":"851bf1c6-652f-487c-89ef-c0bc231782ae","type":"window"}}'
puppeteer:webDriverBiDi:RECV ◀ ] +0ms
puppeteer:webDriverBiDi:RECV ◀ [
puppeteer:webDriverBiDi:RECV ◀ '{"type":"event","method":"browsingContext.navigationStarted","params":{"context":"851bf1c6-652f-487c-89ef-c0bc231782ae","navigation":"57e85a6a-3796-4290-b67e-0725c4b86673","timestamp":1712495783822,"url":"about:blank"}}'
puppeteer:webDriverBiDi:RECV ◀ ] +0ms
puppeteer:webDriverBiDi:RECV ◀ [
puppeteer:webDriverBiDi:RECV ◀ '{"type":"event","method":"script.realmDestroyed","params":{"realm":"d14a4c8f-b576-4ff0-8dfe-abcba8def3f1"}}'
puppeteer:webDriverBiDi:RECV ◀ ] +0ms
puppeteer:webDriverBiDi:RECV ◀ [
puppeteer:webDriverBiDi:RECV ◀ '{"type":"event","method":"script.realmCreated","params":{"realm":"9e784e87-5c31-4cd1-afbb-6c556eb79f1e","origin":"null","context":"851bf1c6-652f-487c-89ef-c0bc231782ae","type":"window"}}'
puppeteer:webDriverBiDi:RECV ◀ ] +0ms
puppeteer:webDriverBiDi:RECV ◀ [
puppeteer:webDriverBiDi:RECV ◀ '{"type":"event","method":"browsingContext.domContentLoaded","params":{"context":"851bf1c6-652f-487c-89ef-c0bc231782ae","timestamp":1712495783856,"url":"about:blank","navigation":"57e85a6a-3796-4290-b67e-0725c4b86673"}}'
puppeteer:webDriverBiDi:RECV ◀ ] +0ms
puppeteer:webDriverBiDi:RECV ◀ [
puppeteer:webDriverBiDi:RECV ◀ '{"type":"event","method":"browsingContext.load","params":{"context":"851bf1c6-652f-487c-89ef-c0bc231782ae","timestamp":1712495783856,"url":"about:blank","navigation":"57e85a6a-3796-4290-b67e-0725c4b86673"}}'
puppeteer:webDriverBiDi:RECV ◀ ] +0ms
puppeteer:webDriverBiDi:RECV ◀ [
puppeteer:webDriverBiDi:RECV ◀ '{"type":"success","id":5,"result":{"context":"851bf1c6-652f-487c-89ef-c0bc231782ae"}}'
puppeteer:webDriverBiDi:RECV ◀ ] +0ms
puppeteer:webDriverBiDi:SEND ► [
puppeteer:webDriverBiDi:SEND ► '{"id":6,"method":"browsingContext.setViewport","params":{"context":"851bf1c6-652f-487c-89ef-c0bc231782ae","viewport":{"width":800,"height":600},"devicePixelRatio":null}}'
puppeteer:webDriverBiDi:SEND ► ] +0ms
puppeteer:webDriverBiDi:RECV ◀ [ '{"type":"success","id":6,"result":{}}' ] +0ms
puppeteer:webDriverBiDi:SEND ► [
puppeteer:webDriverBiDi:SEND ► '{"id":7,"method":"browsingContext.navigate","params":{"context":"851bf1c6-652f-487c-89ef-c0bc231782ae","url":"https://github.com/YusukeIwaki","wait":"interactive"}}'
puppeteer:webDriverBiDi:SEND ► ] +0ms
puppeteer:webDriverBiDi:RECV ◀ [
puppeteer:webDriverBiDi:RECV ◀ '{"type":"event","method":"browsingContext.navigationStarted","params":{"context":"851bf1c6-652f-487c-89ef-c0bc231782ae","navigation":"bc45c64c-f687-491b-b493-d30939ac916d","timestamp":1712495783916,"url":"https://github.com/YusukeIwaki"}}'
puppeteer:webDriverBiDi:RECV ◀ ] +0ms
puppeteer:webDriverBiDi:RECV ◀ [
puppeteer:webDriverBi
こんな具合に、いろいろやり取りが交わされている。
wscatで手で打っていってみる。
> {"id":1,"method":"session.new","params":{"capabilities":{"alwaysMatch":{"acceptInsecureCerts":false,"webSocketUrl":true}}}}
< {"type":"success","id":1,"result":{"sessionId":"73f08924-7d62-400c-bcfd-f0b665658d23","capabilities":{"browserName":"firefox","browserVersion":"125.0","platformName":"mac","acceptInsecureCerts":false,"proxy":{},"setWindowRect":true,"userAgent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:125.0) Gecko/20100101 Firefox/125.0","moz:accessibilityChecks":false,"moz:buildID":"20240405130120","moz:headless":false,"moz:platformVersion":"23.3.0","moz:processID":93550,"moz:profile":"/Users/yusuke-iwaki/src/github.com/YusukeIwaki/bidi_playground_firefox/my_prefs","moz:shutdownTimeout":60000,"moz:windowless":false}}}
セッションID 73f08924-7d62-400c-bcfd-f0b665658d23 が払い出された。
> {"id":2,"method":"browser.getUserContexts","params":{}}
< {"type":"success","id":2,"result":{"userContexts":[{"userContext":"default"},{"userContext":"afb4559f-4293-4649-9a4e-35a505c1f2b0"},{"userContext":"dd9fb073-d7ca-4fd8-b77e-4d93724a9122"},{"userContext":"088f3c09-b35d-4569-8ca7-37acc6e00b5f"},{"userContext":"a1aa0e16-d3fd-4193-a103-e3a7808644e0"}]}}
> {"id":3,"method":"browsingContext.getTree","params":{}}
< {"type":"success","id":3,"result":{"contexts":[{"children":[],"context":"18e4e97c-2427-4264-aa39-cf2391b34831","url":"about:blank","userContext":"default","parent":null}]}}
現在のBrowserContextのIDなどもわかる。
{"id":4,"method":"session.subscribe","params":{"events":["browsingContext","network","log","script"]}}
これ、networkをsubscribeすると、ものすごいログが出るので、一旦 browsingContext だけsubscribeしておけばよかろう。
> {"id":4,"method":"session.subscribe","params":{"events":["browsingContext"]}}
< {"type":"success","id":4,"result":{}}
> {"id":5,"method":"browsingContext.create","params":{"type":"tab","userContext":"default"}}
< {"type":"event","method":"browsingContext.contextCreated","params":{"children":null,"context":"eebf5426-34d2-4017-b0ec-f920553a4f2e","url":"about:blank","userContext":"default","parent":null}}
< {"type":"event","method":"browsingContext.navigationStarted","params":{"context":"eebf5426-34d2-4017-b0ec-f920553a4f2e","navigation":"a553e0f2-3817-4ed0-9f91-ef1365ba154e","timestamp":1712496372734,"url":"about:blank"}}
< {"type":"event","method":"browsingContext.domContentLoaded","params":{"context":"eebf5426-34d2-4017-b0ec-f920553a4f2e","timestamp":1712496372758,"url":"about:blank","navigation":"a553e0f2-3817-4ed0-9f91-ef1365ba154e"}}
< {"type":"event","method":"browsingContext.load","params":{"context":"eebf5426-34d2-4017-b0ec-f920553a4f2e","timestamp":1712496372758,"url":"about:blank","navigation":"a553e0f2-3817-4ed0-9f91-ef1365ba154e"}}
< {"type":"success","id":5,"result":{"context":"eebf5426-34d2-4017-b0ec-f920553a4f2e"}}
タブが1個開いた。

eebf5426-34d2-4017-b0ec-f920553a4f2e というBrowserContextでアクセスすればいいらしい。
> {"id":7,"method":"browsingContext.navigate","params":{"context":"eebf5426-34d2-4017-b0ec-f920553a4f2e","url":"https://github.com/YusukeIwaki","wait":"interactive"}}
< {"type":"event","method":"browsingContext.navigationStarted","params":{"context":"eebf5426-34d2-4017-b0ec-f920553a4f2e","navigation":"3a68d01f-db3b-43f5-bb29-4011565b9276","timestamp":1712496544684,"url":"https://github.com/YusukeIwaki"}}
< {"type":"event","method":"browsingContext.fragmentNavigated","params":{"context":"eebf5426-34d2-4017-b0ec-f920553a4f2e","navigation":"fcbaa27c-2f34-446f-a9e4-e1af2d0b4fa8","timestamp":1712496546023,"url":"https://github.com/YusukeIwaki"}}
< {"type":"success","id":7,"result":{"navigation":"3a68d01f-db3b-43f5-bb29-4011565b9276","url":"https://github.com/YusukeIwaki"}}
< {"type":"event","method":"browsingContext.fragmentNavigated","params":{"context":"eebf5426-34d2-4017-b0ec-f920553a4f2e","navigation":"e3654924-487b-408e-9a95-3f7989fa00f0","timestamp":1712496546097,"url":"https://github.com/YusukeIwaki"}}
< {"type":"event","method":"browsingContext.domContentLoaded","params":{"context":"eebf5426-34d2-4017-b0ec-f920553a4f2e","timestamp":1712496546131,"url":"https://github.com/YusukeIwaki","navigation":"e3654924-487b-408e-9a95-3f7989fa00f0"}}
< {"type":"event","method":"browsingContext.fragmentNavigated","params":{"context":"eebf5426-34d2-4017-b0ec-f920553a4f2e","navigation":"a49e32a5-5d99-4fc1-b6ae-6a882d17c7c2","timestamp":1712496546160,"url":"https://github.com/YusukeIwaki"}}
< {"type":"event","method":"browsingContext.load","params":{"context":"eebf5426-34d2-4017-b0ec-f920553a4f2e","timestamp":1712496546161,"url":"https://github.com/YusukeIwaki","navigation":"a49e32a5-5d99-4fc1-b6ae-6a882d17c7c2"}}
おお、ページが読み込まれた。

> {"id":9,"method":"browsingContext.captureScreenshot","params":{"context":"eebf5426-34d2-4017-b0ec-f920553a4f2e","origin":"viewport","format":{"type":"image/png"}}}
< {"type":"success","id":9,"result":{"data":"iVBORw0KGgoAAAANSUhEUgAACtIAAAYsCAYAAACRfouVAAAgAElEQVR4XuydBbwV1RrFP0HqCQgijYSBgAqohJSSKiopiEiHpKAoXSKSktJdgpS0CkgKIl2KEhYo3d........m9DeelSI5Pq3qtZbsFUi6nLwrRrGzQ4jrXGsMRMIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIgAAIGEUARlqjSGMeEAABEACB+EDg/+r/v/hgEVweAAAAAElFTkSuQmCC"}}
スクリーンショットがBase64エンコードされて返ってくる。
タブを閉じるのが
{"id":10,"method":"browsingContext.close","params":{"context":"eebf5426-34d2-4017-b0ec-f920553a4f2e"}}
< {"type":"event","method":"browsingContext.contextDestroyed","params":{"children":null,"context":"eebf5426-34d2-4017-b0ec-f920553a4f2e","url":"https://github.com/YusukeIwaki","userContext":"default","parent":null}}
< {"type":"success","id":10,"result":{}}
ブラウザ自身を落とすには
> {"id":11,"method":"browser.close","params":{}}
< {"type":"success","id":11,"result":{}}
Disconnected (code: 1000, reason: "")
Elementを見つける系の処理
これはがっつりJS。CDPにせよBiDiにせよ、ただの土管。
- script.evaluate
- script.callFunction
あたりがフル活用されている。
ざっくりいうと、DOMノードをワンショットで見つける系の処理は querySelector , querySelectorAll などが
waitForSelectorは、MutationObserverなどが
それぞれ内部で使われている。
なんとなくGemつくってみた。