9
votes

For the life of me, I cannot get my Chrome extension to display an iframe with a remote URL.

I see the following message in the console -

Refused to frame 'https://www.example.com/' because it violates the following Content Security Policy directive: "child-src 'self'". Note that 'frame-src' was not explicitly set, so 'child-src' is used as a fallback.

I found a solution on here (Injecting iframe into page with restrictive Content Security Policy), which requires injecting a local iframe which then comatins another iframe that references the remote url. This is supposed to bypass the sontent security policy. But for some reason, it does not seem to work in my case. Am I missing something or has the chrome security policy changed?

Below are portions of my extension that pertain to this issue. Note - this code is not the prettiest as I've been hacking around trying to get this to work.

The way this works right now is background.js sends a message to inject.js. inject.js inserts the first iframe, referencing the local file infobar.html. This page is our main user interface, we want the remote html page displayed in an iframe as part of this page. Then infobar.js inserts an iframe referencing the local file frame.html. Finally, frame.html has an iframe hard coded to our remote url.

Based on the previous answer, only the first iframe should have been subject to the content security policy. However, that doesn't seem to be the case here, as the one referencing example.com is actually 3 iframes deep.

manifest.json

{
...
  "content_security_policy": "script-src 'self'; object-src 'self'; frame-src https://www.example.com; child-src https://www.example.com",
  "background": {
    "scripts": [
      "js/jquery/jquery.min.js",
      "src/bg/background.min.js"
    ],
    "persistent": true
  },
...
  "content_scripts": [
    {
      ...
      "css": [
        ...
        "src/inject/inject.min.css"
      ],
      "js": [
        ...
        "src/inject/inject.min.js"
      ]
    }
  ],
  "externally_connectable": {
    "matches": [
      "*://localhost/*",
      "*://*.example.com/*
    ]
  },
  "web_accessible_resources": [
    "src/inject/inject.html",
    "src/inject/infobar.html",
    "src/inject/infobar.min.js",
    "src/inject/frame.html"
  ],
  "sandbox": {
    "pages": [
      "src/inject/infobar.html",
      "src/inject/frame.html"
    ]
  }
}

inject.js

var iframe = document.createElement("iframe");
iframe.scrolling = "no";
iframe.style.cssText = "display:none;";
...
$(iframe).load(function () {
    var message = {
        command: "render-frame",
        context: data,
        frameUrl: chrome.runtime.getURL("src/inject/frame.html")
    };
    iframe.contentWindow.postMessage(message, '*');
    iframe.style.cssText = "border: 0px; overflow: visible; padding: 0px; right: auto; width: 100%; height: " + toolbarHeight + "px; top: 0px; left: 0px; z-index: 2147483647; box-shadow: rgba(0, 0, 0, 0.498039) 0px 3px 10px; position: fixed; display: none;";
});
...
iframe.src = chrome.runtime.getURL("src/inject/infobar.html");      
...     
document.documentElement.appendChild(iframe);  

infobar.html

Simple HTML page. Nothing pertinent in there. References infobar.js.

infobar.js

window.addEventListener("message", function (event) {
    var command = event.data.command;
    switch (command) {
        case "render-frame":
            var frame = document.createElement("iframe");
            frame.scrolling = "no";
            frame.src = event.data.frameUrl;
            document.getElementById("content").appendChild(frame); 
...
            break;
    }
});

frame.html

<html>
<head>
    <style>
        html, body, iframe, h2 {
            margin: 0;
            border: 0;
            padding: 0;
            display: block;
            width: 100vw;
            height: 100vh;
            background: white;
            color: black;
        }
    </style>
</head>
<body>
<iframe src="https://www.example.com/page.html"></iframe>
</body>
</html>
1
What happens if you insert that external iframe inside infobar.js i.e. two-level deep?wOxxOm
Same issue. In inject.js, I can change the line frameUrl: chrome.runtime.getURL("src/inject/frame.html") to frameUrl: chrome.runtime.getURL("example.com/page.html") and I see the same "Refused to frame" error.Phil Figgins
getURL is only for local pages. Use a normal http url for the external site.wOxxOm
You're right, sorry. I manually typed that comment. The actual code was just a string url.Phil Figgins

1 Answers

1
votes

The proper way is to use the chrome.webRequest API in your background script and intercept HTTP responses.

You can then override response headers to modify Content-Security-Policy header. You can also modify X-Frame-Options header (if required).

Documentation: chrome.webRequest