Skip to content

Commit b3b8b5e

Browse files
authored
Merge pull request #310 from tribhuwan-kumar/iframe
fix: cors issue with iframes
2 parents 3665f05 + 8433481 commit b3b8b5e

File tree

1 file changed

+123
-0
lines changed

1 file changed

+123
-0
lines changed

terminator/browser-extension/worker.js

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -464,6 +464,129 @@ function wrapCodeIfNeeded(code) {
464464
}
465465

466466
async function evalInTab(tabId, code, awaitPromise, evalId) {
467+
468+
/*
469+
* executes only for iframes by bypassing the CORS issue
470+
* so this implementatiton is something
471+
* first we'll get every single frames from the tab
472+
* then parse an special const name `IFRAMESELCTOR` from raw js code to
473+
get the selector of iframe from main document
474+
* after that we'll get the `frameId` of the iframe to execute
475+
raw js code inside the iframe's document to avoid CORS issue
476+
* rewrite the raw js code so it removes the unneccesary `IFRAMESELCTOR` from eval code
477+
* run user code safely by creating a Function rather than eval (still executes arbitrary code)
478+
*/
479+
if (code.includes("IFRAMESELCTOR")) {
480+
const frames = await chrome.webNavigation.getAllFrames({ tabId });
481+
log("Frames seen by extension:", frames);
482+
483+
const regex = /const\s+IFRAMESELCTOR\s*=\s*['"`]?\s*(querySelector|getElementById)\s*\(\s*(['"`])(.*?)\2\s*\)\s*;?\s*['"`]?/s;
484+
const match = code.match(regex);
485+
const iframeInfo = {
486+
method: match[1],
487+
selector: match[3]
488+
};
489+
if (!match) {
490+
throw new Error(`No selector named 'IFRAMESELCTOR' have been defined please define
491+
this variable to execute code inside in iframe's document context`);
492+
};
493+
494+
if (iframeInfo) {
495+
const iframeSelector = iframeInfo.selector;
496+
if (iframeSelector) {
497+
log(`Executing in given iframe of selector: ${iframeSelector}, selector type: ${iframeInfo.method}`);
498+
if (!attachedTabs.has(tabId)) {
499+
await debuggerAttach(tabId);
500+
attachedTabs.add(tabId);
501+
await chrome.storage.session.set({ attached: [...attachedTabs] });
502+
log(`Debugger attached to tab '${tabId}' for iframe evaluation`);
503+
}
504+
505+
let frameId;
506+
try {
507+
await sendCommand(tabId, "Page.enable", {});
508+
await sendCommand(tabId, "DOM.enable", {});
509+
const { root } = await sendCommand(tabId, "DOM.getDocument", { depth: -1 });
510+
const { nodeId } = await sendCommand(tabId, "DOM.querySelector", {
511+
nodeId: root.nodeId,
512+
selector: iframeSelector,
513+
});
514+
515+
if (!nodeId) {
516+
throw new Error(`Iframe with selector "${iframeSelector}" not found.`);
517+
}
518+
const { node } = await sendCommand(tabId, "DOM.describeNode", { nodeId });
519+
if (!node) {
520+
throw new Error(`Could not retrieve node info for the specified iframe, selector ${iframeInfo.selector}`);
521+
}
522+
523+
/* now here we've to get the actual correct `frameId` from by matching
524+
the `src` attributes of iframe to the all the existing iframe on that opened tab
525+
*/
526+
const iframeSrc = node.attributes?.[node.attributes.indexOf("src") + 1];
527+
log("iframeSrc", iframeSrc)
528+
const match = frames.find(f => {
529+
try {
530+
const u1 = new URL(f.url);
531+
const u2 = new URL(iframeSrc);
532+
const host1 = u1.hostname.replace(/^www\./, "").toLowerCase();
533+
const host2 = u2.hostname.replace(/^www\./, "").toLowerCase();
534+
if (host1 !== host2) return false;
535+
const p1 = u1.pathname.split("/").filter(Boolean);
536+
const p2 = u2.pathname.split("/").filter(Boolean);
537+
for (let i = 0; i < Math.min(3, p1.length, p2.length); i++) {
538+
if (p1[i] !== p2[i]) return false;
539+
}
540+
return true;
541+
} catch {
542+
return false;
543+
}
544+
});
545+
frameId = match.frameId;
546+
log(`matched frameId: ${match.frameId}`)
547+
548+
} catch (err) {
549+
log("Error finding frame:", err);
550+
try { await debuggerDetach(tabId); } catch (_) {}
551+
attachedTabs.delete(tabId);
552+
throw err;
553+
}
554+
555+
const lines = code.split("\n").filter(line => !line.includes("IFRAMESELCTOR"));
556+
const rewritten = lines.join("\n")
557+
log(`Rewritten code for iframe: ${rewritten}`);
558+
559+
/* execute the rewritten code inside the found frame */
560+
try {
561+
const results = await chrome.scripting.executeScript({
562+
target: { tabId: tabId, frameIds: [frameId] },
563+
world: "MAIN",
564+
func: (userScript, shouldAwait) => {
565+
return (async () => {
566+
const fn = new Function(userScript);
567+
const result = fn();
568+
if (shouldAwait && result instanceof Promise) {
569+
return await result;
570+
}
571+
return result;
572+
})();
573+
},
574+
args: [rewritten, !!awaitPromise],
575+
});
576+
577+
if (!results || results.length === 0) {
578+
throw new Error("Script execution in frame produced no result.");
579+
}
580+
return results[0].result;
581+
} catch (err) {
582+
log(`Error executing script in frame ${frameId}:`, err);
583+
throw err;
584+
}
585+
}
586+
}
587+
}
588+
589+
467590
const perfStart = performance.now();
468591
const timings = {};
469592

0 commit comments

Comments
 (0)