-
Notifications
You must be signed in to change notification settings - Fork 33
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
exposeFunction: globalThis[(prefix + name)] is not a function #69
Comments
The method corresponding to async #onBindingCalled(
event: Protocol.Runtime.BindingCalledEvent,
): Promise<void> {
if (event.executionContextId !== this.#id) {
return;
}
let payload: BindingPayload;
try {
payload = JSON.parse(event.payload);
} catch {
// The binding was either called by something in the page or it was
// called before our wrapper was initialized.
return;
}
const { type, name, seq, args, isTrivial } = payload;
if (type !== 'internal') {
this.emit('bindingcalled', event);
return;
}
if (!this.#bindings.has(name)) {
this.emit('bindingcalled', event);
return;
}
try {
const binding = this.#bindings.get(name);
await binding?.run(this, seq, args, isTrivial);
} catch (err) {
debugError(err);
}
} |
Added an #bindings = new Set<string>();
@throwIfDetached
async addExposedFunctionBindingWithEvent(binding: Binding): Promise<void> {
const eventName = CDP_BINDING_PREFIX + binding.name;
// Add a flag to control the loop
const flagName = `${eventName}_running`;
await this.#client.send('Page.addScriptToEvaluateOnNewDocument', {
source: `
let ${eventName}_callbacks = [];
window['${flagName}'] = true;
window['${eventName}'] = (...args) => {
for(const callback of ${eventName}_callbacks) {
callback(...args);
}
${eventName}_callbacks = [];
};
window['get_${eventName}'] = async function() {
return new Promise((resolve, reject) => {
${eventName}_callbacks.push(resolve);
});
};
`,
runImmediately: true,
});
this.#bindings.add(binding.name)
// Start the polling loop with the flag
void (async () => {
while (true) {
try {
const mainWorld = this.worlds[MAIN_WORLD];
if (!mainWorld.context || mainWorld.disposed || !this.#bindings.has(binding.name)) {
break;
}
const { exceptionDetails, result: remoteObject } = await this.#client.send('Runtime.evaluate', {
expression: `window['get_${eventName}']()`,
awaitPromise: true
});
if (exceptionDetails) {
throw new Error(exceptionDetails.text);
}
if (!remoteObject.value) continue;
mainWorld.emitter.emit('bindingcalled', {
name: binding.name,
payload: remoteObject.value,
executionContextId: mainWorld.context.id
});
} catch (e) {
debugError(e)
// Add a small delay to prevent tight-loop CPU usage on errors
await new Promise(resolve => setTimeout(resolve, 10));
}
}
})();
}
@throwIfDetached
async addExposedFunctionBinding(binding: Binding): Promise<void> {
// If a frame has not started loading, it might never start. Rely on
// addScriptToEvaluateOnNewDocument in that case.
if (this !== this._frameManager.mainFrame() && !this._hasStartedLoading) {
return;
}
if (process.env['REBROWSER_PATCHES_RUNTIME_FIX_MODE'] !== '0') {
this.addExposedFunctionBindingWithEvent(binding)
}
await Promise.all([
this.#client.send('Runtime.addBinding', {
name: CDP_BINDING_PREFIX + binding.name,
}),
this.evaluate(binding.initSource).catch(debugError),
]);
}
@throwIfDetached
async removeExposedFunctionBinding(binding: Binding): Promise<void> {
...
if (process.env['REBROWSER_PATCHES_RUNTIME_FIX_MODE'] !== '0' && this.#bindings.has(binding.name)) {
this.#bindings.delete(binding.name)
}
...
} |
I'm really confused with this issue. I cannot reproduce it. Are you sure that it's relevant to the patches?
Could you please share the full code example? |
I tried disabling the patches, and it works fine. However, when the patches are enabled, the issue occurs. The problem is not that The issue lies in the fact that the method inside By debugging the puppeteer source code, the issue seems to be in the this.#client.send('Runtime.addBinding', {
name: CDP_BINDING_PREFIX + binding.name,
}) Therefore, the full method name for Below is my simplified reproduction code: import fs from 'fs';
import type { Browser } from "puppeteer-core";
import puppeteerExtra from "puppeteer-extra";
const findSystemChrome = () => {
// linux or windows
const chromePaths = [
'/usr/bin/google-chrome',
'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
];
return chromePaths.find((path) => fs.existsSync(path));
}
// primary logic
const args = [
'--no-sandbox',
'--disable-client-side-phishing-detection',
'--disable-default-apps',
'--disable-hang-monitor',
'--metrics-recording-only',
'--disable-sync',
'--safebrowsing-disable-auto-update',
'--disable-breakpad',
'--disable-extensions',
'--disable-blink-features=AutomationControlled'
];
const headless = false // or true
if (headless) {
args.push('--disable-gpu');
args.push('--disable-dev-shm-usage');
args.push('--no-first-run');
}
const browser: Browser = await puppeteerExtra.launch({
executablePath: findSystemChrome(),
ignoreDefaultArgs: ['--enable-automation'],
args,
headless,
timeout: 30_000,
}).catch((err: any) => {
console.error(`Unknown firebase issue, just die fast.`, { err });
return Promise.reject(err);
});
const page = await browser.newPage();
const preparations = [
page.exposeFunction('reportSnapshot', async () => {
console.log("server")
}),
page.evaluateOnNewDocument(`document.addEventListener('DOMContentLoaded', () => window.reportSnapshot());`)
]
await Promise.all(preparations);
await = page.goto("https://www.google.com", {
waitUntil: ['load', 'domcontentloaded', 'networkidle0'],
timeout: 30_000
}) |
Thanks for such a detailed report. The issue is that without having I consider this behavior a Chrome bug, as the CDP documentation clearly states:
I just filed a bug report with Chromium: https://issues.chromium.org/issues/382473087 Please vote +1 to get it fixed sooner (though this may not help). I don't think it's feasible to try to fix it with CDP - all solutions I can think of are quite hacky and won't guarantee that bindings will be enabled before Your solution with polling totally makes sense, thanks for sharing this. That could be used until the bug is fixed, but I won't add this to this repo's codebase as I consider this bug out of scope of this project. P.S. Actually, it might be possible to "fix" it by using |
Just curious if anyone else has encountered the same issue related to the missing binding. It is quite consistent with my project as most of our client sites don't allow any page refresh but reload. The rebrowser patch can help you get through the cloudflare challenge but it will cause a hang-up in such cases. |
had same issue, want to expose a function to log sth to the node process, but it's not worked, and page.on('console') is also not work for the patch
|
using a very tricky way to solve the issue
then use the event listener
|
An error occurred in the browser when using exposeFunction, and the server could not retrieve the value.
page error:
related code:
The text was updated successfully, but these errors were encountered: