I'm trying to access the contentDocument of a cross origin iframe using Runtime.evaluate
. As far as I understand the docs this should be possible by creating an executionContext
with universal access using Page.createIsolatedWorld
+ grantUniveralAccess: true
[1] and passing the returned executionContextId
to Runtime.evaluate
as contextId
.
Any ideas?
Given a chromium process started with chromium-browser --user-data-dir=/tmp/headless --remote-debugging-port=9000
[2].
// See [3] for full code
const frameId = /* frameId of our page with origin localhost:9000 */
function execute(command, args) { /* ... send and receive on websocket */ }
const {executionContextId} = await execute("Page.createIsolatedWorld", {
frameId: frameId,
grantUniveralAccess: true // NOT grantUniversalAccess. Typo in devtools protocol itself [4].
})
// fails with:
// Access to fetch at 'http://example.com/' from origin 'http://localhost:9000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
await execute("Runtime.evaluate", {
awaitPromise: true,
expression: `fetch("http://example.com").then(r => r.text())`,
contextId: executionContextId
})
// fails with:
// Uncaught DOMException: Blocked a frame with origin "http://localhost:9000" from accessing a cross-origin frame.
execute("Runtime.evaluate", {
awaitPromise: true,
expression: `
new Promise((resolve, reject) => {
const iframe = document.createElement("iframe");
iframe.src = "http://example.com"
iframe.onload = () => resolve(iframe)
iframe.onerror = reject;
document.body.append(iframe)
}).then(iframe => iframe.contentWindow.document)`,
contextId: executionContextId
})
[1] I would have expected universal access to allow me to acess cross origin resources the same way the --disable-web-security
flag does - which internally grants universal access
if (!frame_->GetSettings()->GetWebSecurityEnabled()) {
// Web security is turned off. We should let this document access
// every other document. This is used primary by testing harnesses for
// web sites.
origin->GrantUniversalAccess();
[2] Running head-full for easier debugging (e.g. seeing the full cors error only printed to the console) - running with --headless
doesn't work either.
[3]
const targets = await fetch("http://localhost:9000/json").then(r => r.json());
const tab = targets.filter(t => t.type === "page")[0];
let counter = 0, commands = {};
const w = new WebSocket(tab.webSocketDebuggerUrl);
await new Promise(resolve => { w.onopen = resolve; })
w.onmessage = event => {
const json = JSON.parse(event.data)
if (commands[json.id]) commands[json.id](json);
else console.log(json); // event
}
function execute(method, params) {
return new Promise((resolve, reject) => {
const id = counter++;
commands[id] = ({result, error}) => {
console.log(method, params, result, error)
if (error) reject(error);
else resolve(result);
// delete commands[id];
};
w.send(JSON.stringify({method, id, params}));
});
}
window.execute = execute;
window.frameId = tab.id;
[4] The correct parameter name is grantUniveralAccess (no s
in univeral
). Easily validated by passing a value with an incorrect type (expects a bool)
// fails with:
// Failed to deserialize params.grantUniveralAccess - BINDINGS: bool value expected at position 69
await execute("Page.createIsolatedWorld", {frameId, grantUniveralAccess: "true"})