This is how I understand the situation so far:
Content scripts are executed under a principle subsuming only the principle of the window
they are attached to. The implications of that are quite well documented. Basically this means that you can only do stuff that the page can.
content scripts in previous extensions worked differently.
There are two things a content script can do that the page scripts can’t:
So what you are observing is by design and likely won’t ever change.
Now, for a way around it:
Instead on fetch
ing the CSS (which should always work, but may cause an additional network request) and injecting it as a <style>
(which should work unless the pages CSP disallows style-src 'unasfe-inline'
, but probably breaks the location of the style even if you append \n/*# sourceURL=${ originalUrl } */
to it, you could also try to set the crossorigin
attribute on the link (you may have to clone the link to re-evaluate it).
If the server sets the correct Access-Control-*
headers, you should then be able to access the corresponding document.styleSheets
entry without restrictions.
Here is some more stuff I originally thought was relevant for the answer. It isn’t really relevant, but I think it is still interesting:
There is some additional some magic happening in content scripts. This is very poorly documented – or I haven’t found the documentation yet, but I don’t think there is any. That means everything that follows is what I analyzed from the resulting behavior and may be inaccurate:
- Much of it starts with the fact that, in content scripts,
this !== window
. The very last section on the MDN page about content script hints at that by saying that eval()
does something different than window.eval()
. But that is it in terms of documentation. So far, what I have found is this:
-
Window.prototype === this.__proto__ >>> true
which would imply that this is a different Window
instance
- but someone is trying to fool us:
Object.getPrototypeOf(this) === this.__proto__ >>> false
which means that someone overwrote the ES6 .__proto__
getter. I have no idea why that was done and think was is a very bad idea.
- so, what is the [[Prototype]] of this? Well,
Object.getPrototypeOf(this) >> Window → https://...
- which gives the same output as
window >> Window → https://...
so surely those two are the same
- nope:
Object.getPrototypeOf(this) === window >>> false
-.-
- but setting properties on
window
makes them appear on this
(but not vice versa) which generally means that window
should be in the prototype chain of this
- so
this instanceof Object.assign(function() { }, { prototype: window, })
should return true. It doesn’t.
- in conclusion, after about an hour of typing test in the JS console of a content script, I still don’t know what the precise relation between
this
and window
in content scripts is. In some regards, this
seems to inherit from window
, but that is not actually the case. The [[Prototype]] of window seems to be some kind of wired Proxy with a number of actual properties that proxies other properties to the window
. I am pretty sure this is not ES6 standards compliant.
- Why did I mention all the above? Well, I wanted to check if
fetch()
(i.e. this.fetch
) is the same as window.fetch
. As function values, they are not, in fact, there are three instances of fetch
(and many other build-in functions): this.fetch
, window.fetch
and Object.getPrototypeOf(this).fetch
are all three different, as far as the ===
operator is concerned.
But since all three fetch
functions seem to behave the way described in the first section, this doesn’t really matter.