title: Browser still leaking real user agent
body: <p>I am writing a privacy extension which requires me to spoof the user agent property of the browser aka navigator.userAgent (yes I already know about the User-Agent HTTP header and have already dealt with that).</p>
  
  <p>My issue is that a page might not just have a main frame but also a variable about of iframes as well. In my manifest file i am using all_frames: true to inject my content script into all frames and match_about_blank: true to inject into frames with a url of "about:blank".</p>
  
  <p>I am using <a href="https://browserleaks.com/javascript" rel="nofollow noreferrer">BrowserLeaks</a> to test my extension it seems to spoof the user agent correctly using the window option but when using the iframe.contentWindow method it shows the real user agent.</p>
  
  <p>I believe it might be because the iframe is sandboxed and you are not allowed to inject into sandboxed iframes. This would be a huge problem since sites could evade extensions and deny them access to a sandboxed iframe.</p>
  
  <p>This is the error I get on Chromium:</p>
  
  <blockquote>Blocked script execution in 'about:blank' because the document's frame is sandboxed and the 'allow-scripts' permission is not set.</blockquote>
  
  <p>From <a href="https://developer.chrome.com/extensions/content_scripts" rel="nofollow noreferrer">Chrome Developer</a></p>
  
  <p>match_about_blank:</p>
  
  <blockquote>Optional. Whether to insert the content script on about:blank and about:srcdoc. Content scripts will only be injected on pages when their inherit URL is matched by one of the declared patterns in the matches field. The inherit URL is the URL of the document that created the frame or window.
  Content scripts cannot be inserted in sandboxed frames.
  
  Defaults to false.</blockquote>
  
  <p>Or perhaps the script is running in all iframes including the sandboxed ones but the script is not running quickly enough i.e. not run_at: document_start.</p>
  
  <p>From <a href="https://developer.mozilla.org/en-US/Add-ons/WebExtensions/manifest.json/content_scripts" rel="nofollow noreferrer">MDN</a></p>
  
  <p>match_about_blank:</p>
  
  <blockquote>match_about_blank is supported in Firefox from version 52. Note that in Firefox, content scripts won't be injected into empty iframes at "document_start" even if you specify that value in run_at.</blockquote>
  
  <p>My title says chrome extension however it is going to be for Firefox too. I posted documentation from both MDN and Chrome since their wording is different. On Chrome when I test this on say github.com I get errors regarding sandboxing on iframe however on Firefox I get no errors of such, however it still doesn't spoof the property inside the iframe like I want it to. Any ideas?</p>
  
  <p>manifest.json</p>
  
  <pre><code>{
      "name": "Shape Shifter",
      "version": "0.0.1",
      "description": "Anti browser fingerprinting web extension. Generates randomised values for HTTP request headers and javascript API's.",
      "manifest_version": 2,
      "icons": {
          "16": "icons/crossed_eye_16x16.png",
          "32": "icons/crossed_eye_32x32.png",
          "48": "icons/crossed_eye_48x48.png",
          "128": "icons/crossed_eye_128x128.png"
      },
      "background": {
          "persistent": true,
          "scripts": ["js/background.js"]
      },
      "browser_action": {
          "default_title": "Shape Shifter",
          "default_icon": {
              "16": "icons/crossed_eye_16x16.png",
              "32": "icons/crossed_eye_32x32.png"
          },
          "default_popup": "html/popup.html"
      },
      "content_scripts": [
          {
              "all_frames": true,
              "match_about_blank": true,
              "run_at": "document_end",
              "matches": ["&lt;all_urls&gt;"],
              "js": ["js/inject.js"]
          }
      ],
      "permissions": [
          "webRequest",
          "webRequestBlocking",
          "&lt;all_urls&gt;"
      ],
      "web_accessible_resources": [
          "js/lib/seedrandom.min.js",
          "js/random.js",
          "js/api/document.js",
          "js/api/navigator.js",
          "js/api/canvas.js",
          "js/api/history.js",
          "js/api/battery.js",
          "js/api/audio.js",
          "js/api/element.js"
      ]
  }
  </code></pre>
  
  <p>inject.js (My content script)</p>
  
  <pre><code>console.log("Content Script Running ...");
  
  function inject(filePath, seed) {
    // Dynamically create a script
    var script = document.createElement('script');
  
    // Give the script a seed value to use for spoofing
    script.setAttribute("data-seed", seed);
  
    // Give the script a url to the javascript code to run
    script.src = chrome.extension.getURL(filePath);
  
    // Listen for the script loading event
    script.onload = function() {
      // Remove the script from the page so the page scripts don't see it
      this.remove();
    };
  
    // Add the script tag to the DOM
    (document.head || document.documentElement).appendChild(script);
  }
  
  function getSeed(origin) {
      // Get a Storage object
      var storage = window.sessionStorage;
  
      // Try to get a seed from sessionStorage
      var seed = storage.getItem(origin);
  
      // Do we already have a seed in storage for this origin or not?
      if (seed === null) {
          // Initialise a 32 byte buffer
          seed = new Uint8Array(32);
  
          // Fill it with cryptographically random values
          window.crypto.getRandomValues(seed);
  
          // Save it to storage
          storage.setItem(origin, seed);
      }
  
      return seed;
  }
  
  var seed = getSeed(window.location.hostname);
  
  inject("js/lib/seedrandom.min.js", seed);
  console.log("[INFO] Injected Seed Random ...");
  
  inject("js/random.js", seed);
  console.log("[INFO] Injected Random ...");
  
  inject("js/api/document.js", seed);
  console.log("[INFO] Injected Document API ...");
  
  inject("js/api/navigator.js", seed);
  console.log("[INFO] Injected Navigator API ...");
  
  inject("js/api/canvas.js", seed);
  console.log("[INFO] Injected Canvas API ...");
  
  inject("js/api/history.js", seed);
  console.log("[INFO] Injected History API ...");
  
  inject("js/api/battery.js", seed);
  console.log("[INFO] Injected Battery API ...");
  
  inject("js/api/audio.js", seed);
  console.log("[INFO] Injected Audio API ...");
  
  inject("js/api/element.js", seed);
  console.log("[INFO] Injected Element API ...");
  </code></pre>