8

Before someone marks this post as a duplicate of another post, such as this: SecurityError: Blocked a frame with origin from accessing a cross-origin frame this post is different because it is about avoiding this error in the context of a Chrome web extension, which means there may be unique solutions.

I am porting a Firefox Quantum extension to Chrome. The extension injects an iFrame into the user's current web page. Right now, the extension works without problems in Firefox Quantum, you can find it here: https://addons.mozilla.org/en-US/firefox/addon/tl-dr-auto-summarizer/?src=search

The source of the iFrame is a HTML file called "inject.html" that is bundled within the extension.

Here is the shortened (to avoid making the post excessively long) code that injects the iFrame. This code is within a content script in the user's current tab:

var iFrame = document.createElement("iFrame");
iFrame.id = "contentFrame";
iFrame.classList.add("cleanslate");
iFrame.style.cssText = "width: 100% !important; height: 100% !important; border: none !important;";
iFrame.src = browser.extension.getURL("inject-content/inject.html");
document.body.appendChild(iFrame);

Here is the manifest.json

{
    "manifest_version": 2,
    "name": "TL;DR - Summarizer",
    "version": "3.0",

    "description": "Summarizes webpages",

    "permissions": [
        "activeTab",
        "tabs",
        "*://*.smmry.com/*"
    ],

    "icons":
    {
        "48": "icons/border-48.png"
    },

    "browser_action":
    {
        "browser_style": true,
        "default_popup": "popup/choose_length_page.html",
        "default_icon":
        {
            "16": "icons/summarizer-icon-16.png",
            "32": "icons/summarizer-icon-32.png"
        }
    },

    "web_accessible_resources": [
        "inject-content/inject.html",
        "inject-content/cleanslate.css"
    ],

    "content_security_policy": "script-src 'self' 'sha256-AeZmmPP/9ueCrodQPplotiV3Pw0YW4QqifjUL7NE248='; object-src 'self'"

}

After injecting the iFrame, I set the "click" listeners for the buttons within the iFrame, once the iFrame has loaded. I do this using the following code sample. However, while the following code works in Firefox Quantum, it throws an exception in Chrome.

iFrame.onload = () => {

                //The error occurs on the following line.

                var closeButton = iFrame.contentWindow.document.getElementById("close-btn");

                closeButton.addEventListener("click", () => {
                    //Do Stuff
                });

                var copyButton = iFrame.contentWindow.document.getElementById("copy-btn");

                copyButton.addEventListener("click", () => {
                    //Do stuff
                });

            }

I get the following exception:

Uncaught DOMException: Blocked a frame with origin "http://example.com" from accessing a cross-origin frame. at HTMLIFrameElement.iFrame.onload (file:///C:/Users/vroy1/Documents/Programming/web-extension-summarizer/src/inject-content/inject.js:58:56)

How can I avoid this error?

In case anyone is wondering, the reason I am able to use the Promise API and the browser namespace inside of a Chrome extension is because I am using a polyfill provided by Mozilla that allows me to use promises and the browser namespace.

Here is the code for the popup that the extension displays when its toolbar icon is clicked:

//Enable the polyfill for the content script and execute it in the current tab

browser.tabs.executeScript({ file: "/polyfills/browser-polyfill.js" }).then(loadContentScript).catch((error) => logError(error));

function loadContentScript() {
    browser.tabs.executeScript({ file: "/inject-content/inject.js" }).then(listenForClicks).catch((error) => logError(error));
}

function listenForClicks() {
    document.addEventListener('click', e => {
        if (!e.target.classList.contains('btn')) {
            return;
        } else {
            browser.tabs.query({ active: true, currentWindow: true })
                .then(tabs => {
                    browser.tabs.sendMessage(tabs[0].id, { summaryLength: e.target.id, targetURL: tabs[0].url });
                });
        }
    });
}

function logError(error) {
    console.log(error);
}

Lastly, here is the full code of the content script:

https://pastebin.com/Yrs68zAB

Roymunson
  • 5,099
  • 10
  • 48
  • 106
  • It looks like Chrome doesn't remember the iframe was created by your content script. Whether it's a bug or WAI (working-as-intended), I don't know. Try in an old portable Chrome and in new Chrome Canary. If the behavior is same, it's likely WAI. Otherwise consider reporting on https://crbug.com – wOxxOm May 03 '18 at 16:51
  • Is there a way to add the iFrame to my manifest.json so it gets recognized? – Roymunson May 03 '18 at 22:42
  • No, but you can rework the code and use DOM listeners inside the iframe, which then calls chrome.tabs.sendMessage with tab id obtained from chrome.tabs.getCurrent. The content script will receive it in its chrome.runtime.onMessage. – wOxxOm May 04 '18 at 05:17
  • Can you post an example on how to use DOM listeners inside of the iFrame as an answer? Also, would that actually work? The problem isn't detecting when the iFrame has loaded, rather I can't fetch the button object from inside the iFrame because it results a exception. – Roymunson May 07 '18 at 21:07
  • The iframe contains your own page so there's no need to detect anything, simply put the code in js file and load it normally with a script tag in iframe.html. – wOxxOm May 08 '18 at 03:35
  • @Roymunson did you find any solution? – Amirmohammad Moradi Aug 07 '18 at 21:46
  • @Roymunson I have exactly the same issue, it works in FF but not in Chrome. Maybe postMessage could work. – a.s.t.r.o Jan 28 '19 at 16:02
  • @a.s.t.r.o See https://stackoverflow.com/questions/19493020/adding-file-permission-to-chrome-extension – guest271314 Feb 03 '19 at 13:59
  • @a.s.t.r.o I found an old repository with my source code that solves this problem. I'll answer this question soon. – Roymunson Feb 03 '19 at 22:51
  • @Roymunson that'll be great, especially if you have a two-way communication solution. – a.s.t.r.o Feb 04 '19 at 14:24

2 Answers2

0

You can try uploading that code, what you want on your iframe, to a web server, and setting the header.

'Access-Control-Allow-Origin: *'

Firefox usually works better with local files, that could explain your error

origin "http://example.com" from accessing a cross-origin frame. at file:///C:/Users/vroy1/Documents/Programming/web-extension-summarizer/src/inject-content/inject.js
Alberto Perez
  • 695
  • 12
  • 15
0

For Chrome--I included a script tag in my iframe. I then could use <button element>.addEventListener("click", function() {} inside of the script loaded by the iframe. For frame to host communication, I used window.parent.postMessage and other such methods. In order to load the iframe, I added the following to my manifest.json:

  "web_accessible_resources": [
    "inject-content/inject.html",
    "inject-content/cleanslate.css"
  ]
Roymunson
  • 5,099
  • 10
  • 48
  • 106