Search code examples
iframegoogle-chrome-extensioncontent-script

Chrome extension contentscript with iframe "blocked a frame with origin from accessing a cross-origin frame"


I've browsed some of the related issues and solutions on SO and none seem to have either an up-to-date solution or a similar issue as mine.

So here is the basic issue. I have a content script that runs on https://example.com/* matches with the goal of changing the HTML5 <video> player on the page. The actual player, however, is within an iframe. In order to get the actual HTMLMediaElement, I should be able to run

document.querySelector("iframe").contentWindow.document.querySelector("video")

However, this fails with the error

Uncaught DOMException: Blocked a frame with origin "https://example.com" from accessing a cross-origin frame.

For additional information document.querySelector("iframe").src returns https://static.example.com/.../player.html. The "origin" is different, but is the same root-domain of the original site.

So I am wondering how I can use the content script (or, I guess, any other method) to manipulate the media player within the iframe.


Here is what the extension roughly looks like.

manifest.json

{
    "manifest_version": 2,
    //...
    "permissions": ["activeTab", "storage", "https://example.com/*"],
    //...
    "content_scripts": [
        {
            "all_frames": true,
            "matches": [
                "https://example.com/*"
            ],
            "js": [
                "inject.js"
            ]
        }
    ],
    //...
}

inject.js

winndow.onload = () => {
    document
        .querySelector("iframe.video-player")
        .contentWindow.document
        .querySelector("video#player_html5_api");
};

Update:

I'm not really sure why this didn't cross my mind earlier, but as mentioned in the answer, the key to treat it as a completely different content injection. Assuming you don't need any interaction across the iframe boundary (which can still be achieved with message passing) you can just modify the manifest as below. The key is matching the source of the iframe, not the source of the page. The "all frames: true line is essential as by default frames that are not top-most will not be matched.

manifest.json

{
    //...
    "content_scripts": [
        {
            "all_frames": true,
            "matches": [
                "https://static.example.com/.../player.html"
            ],
            "js": [
                "inject.js"
            ]
        }
    ],
    //...
}

Solution

  • The important takeaway here is from this sentence:

    For additional information document.querySelector("iframe").src returns https://static.example.com/.../player.html. The "origin" is different, but is the same root-domain of the original site.

    The origin is different. (static.example.com != example.com) Even though the root domain is the same, the origin is not. (This is why tuckerchapin.sketchyfreewebhost.com can't serve and modify content from j6m8.sketchyfreewebhost.com). So this, theoretically, "better" behavior.

    Taken from this answer:

    Protocol, hostname and port must be the same of your domain, if you want to access a frame.

    (emphasis mine)