Excruciatingly complicated to understand how to inject a script in a page at document_start with dynamic data

553 views
Skip to first unread message

Particle

unread,
Feb 5, 2024, 8:14:24 PMFeb 5
to Chromium Extensions
I've got a Firefox extension that is working without any issues and I began trying to export it to Chrome as well (both MV3).

However, this proved to be pretty much impossible as far as available documentation, and web search could indicate.

The base of the extension is very simple:
  1. For a certain website (ex: www.google.com) I want to inject a function
  2. This function contains dynamic information (user settings, extension id, etc.)
  3. It must be injected at document_start
I've tried multiple different ideas on the official docs and floating on multiple forums, to no avail.

I tried using the manifest content_script field coupled with injecting a script into the page DOM, but then I face the CORS prohibition
I tried setting the world to MAIN, but then any browser API is totally lost
I tried making the script a web accessible resource, but again the same world constraints kick back
I tried using a background script/service worker to chrome.scripting.registerContentScripts but again the same problems persist

Is there absolutely no way to achieve this in Chrome?

In Firefox it was super easy and straight forward, but in Chrome it feels like this kind of extension interactivity has been completely killed, or, at the very least, documented in a nightmare way making it almost impossible to understand how to do it.

wOxxOm

unread,
Feb 6, 2024, 6:47:28 AMFeb 6
to Chromium Extensions, Particle
It's unclear why you need the MAIN world. Did you use wrappedJSObject in Firefox? If so, indeed there's no such alternative in Chrome. In Chrome you would need two content scripts: one in the default ISOLATED world and another one in the MAIN world. They can communicate via CustomEvent message with a unique id to exchange configuration. This won't achieve true document_start though because getting the configuration from the extension would have to be asynchronous (well, strictly speaking there might be one very complicated workaround via the offscreen document for URL.createObjectURL + declarativeNetRequest and then a synchronous XMLHttpRequest in the content script, but it's not 100% reliable).

Assuming you actually don't need the MAIN world i.e. in Firefox you were using browser.contentScripts to register a dynamically crafted code string with the settings inside: the only solution in Chrome is chrome.userScripts. Unfortunately, it is still unreasonably gated behind the requirement to enable developer mode in chrome://extensions page, but for a personal extension it should be usable.

Particle

unread,
Feb 6, 2024, 7:33:13 AMFeb 6
to Chromium Extensions, wOxxOm, Particle
I need the main world in order to manipulate parts of the page, code, etc.

I do not make use of wrappedJSObject, but something as simple as this appears to be completely impossible to achieve with Chrome in a time sensitive matter, either due to CORS or world context:

contentscript.js

const script = document.createElement("script");
script.textContent = `dialog.debug("this is running on the webpage before any other code can run from the extension with manifest version ${chrome.runtime.getManifest().manifest_version}")`;
document.documentElement.appendChild(script);

That's pretty much a great simple example of what a much more complex equivalent of my extension is trying to do; inject and run code in the web page context with dynamic information provided by the content script before any code in the page can be executed.

As you can see I am not making use of any browser.contentScripts or similar, this is simply done on its own since the contenscript.js is configured in the manifest.json to run at document_start for the matching urls.

wOxxOm

unread,
Feb 6, 2024, 7:53:42 AMFeb 6
to Chromium Extensions, Particle, wOxxOm
Yeah, the CSP of the default ISOLATED world in MV3 forbids setting textContent of a script element, which is an intentional restriction to prevent dynamically constructed code. You'll have to use the two workarounds suggested in my previous message until a better alternative is implemented in some distant future.

Oliver Dunk

unread,
Feb 6, 2024, 7:53:52 AMFeb 6
to Particle, Chromium Extensions, wOxxOm
Hi,

If I understand correctly from your second message it sounds like what you are trying to do is use a static content script with dynamic configuration, and in a synchronous way (i.e it should complete loading the configuration and executing before any other scripts are allowed to load on the page).

At the moment, there are a few limitations which make this hard to do, such as both the storage and messaging APIs being synchronous. This means there is no way to load configuration without causing a delay during which time other code on the page could execute.

As wOxxOm mentioned, one option would be using the chrome.userScripts API, which allows you to register arbitrary code in a similar way to a content script.

There are some other options too, like the `chrome.debugger` API, but understanding more about your use case would help to point us in the right direction. Feel free to share more about what you're doing if you're comfortable.

This use case has also come up in the past and actually got discussed in the last Web Extensions Community Group meeting. If you search the minutes for "Rediscussion of Issue 103", there was some discussion of allowing parameters to be set for a content script which everyone seemed supportive of. That isn't an API we have today though or are actively working on at the moment.
Oliver Dunk | DevRel, Chrome Extensions | https://developer.chrome.com/ | London, GB


--
You received this message because you are subscribed to the Google Groups "Chromium Extensions" group.
To unsubscribe from this group and stop receiving emails from it, send an email to chromium-extens...@chromium.org.
To view this discussion on the web visit https://groups.google.com/a/chromium.org/d/msgid/chromium-extensions/0d68f91c-7fcf-479e-97a0-ea7b64d07c21n%40chromium.org.

Particle

unread,
Feb 6, 2024, 8:34:35 AMFeb 6
to Chromium Extensions, Oliver Dunk, Chromium Extensions, wOxxOm, Particle
Here's the thing, the main concern at this time is to ensure the content script in the MAIN world can be present before any other code runs in the page, so definitely at document_start.

As for the async storage environment, I understand there is currently no way around it in MV3, nor any intention of changing that any time soon.

For this reason I would be OK with working around the limitation by ensuring the MAIN world script has all the barebones logic to intercept everything it MIGHT need, pending dynamic settings/instructions that are received shortly after asynchronously, as long as it won't be too late.

One example of an extremely important use case in favor of the ability to pass dynamic content with static script is to pass a secret key to the script that will run in the MAIN world, which is then used to validate communications with the ISOLATED content script and/or service worker.

On one hand, I could easily just inject the content script in MAIN world, signal the ISOLATED script (or service worker) the script is ready, then send the information that it needs back. On the other hand, at that time it is already so late that most time sensitive features lost their ability to intercept any primary code events in time.

Oliver, the issue 103 you mentioned ( https://github.com/w3c/webextensions/issues/103 ) sounds like something that could work for me since it would provide a way for the static script to be accompanied with dynamic information.

I am quite surprised that this already exists for executeScript (args parameter), but it was never implemented for registerContentScripts.

The proposed workaround that requires enabling developer mode is simply not acceptable because this is not a personal use extension. I have recently resume work on an extension on Firefox, and I am trying to find out if it can be ported to Chrome again:


Although I've been successful in dropping the requirement for the deprecated webRequestBlocking in Chrome MV3 within my current extension code (all of it is in my local dev env, not online), I am now struggling with making something as simple as injecting the content script in the web page with dynamic data work.

I definitely require MAIN world for this extension to work, but I also require it to be able to read extension dynamic data in a time sensitive matter since most features depend on intercepting and modifying page content (dom, code) at early stages.

Another way I explored to work around all of these limitations was to 2 the injection in 2 stages:
  1. First stage: Immediately inject the static code at document_start (this can even be done by the manifest rules instead of using registerContentScripts)
  2. Second stage: The service worker obtains the dynamic data (user settings, etc.) and then sends it to the page using executeScript, OR, with an ISOLATED content script, do something similar but using events to pass the data instead of executeScript, which a content script does not have access to if I understand correctly.
However, in any of these workarounds I am always sacrificing a secret key, so any communications between host-privileged host would be open to interception. Although the risk of that happening from the target webpage is pretty much none, the concern is with any other extensions that might be running on the user's page.

Particle

unread,
Feb 6, 2024, 8:40:25 AMFeb 6
to Chromium Extensions, Particle, Oliver Dunk, Chromium Extensions, wOxxOm
PS: And I didn't even mention just how much code fragmentation this will incur between the Firefox and Chrome extension version since the content-script injection done by Firefox is fundamentally different than Chrome's (Xray vision vs MAIN/ISOLATED worlds), and that's just the tip of the iceberg.

wOxxOm

unread,
Feb 6, 2024, 8:43:33 AMFeb 6
to Chromium Extensions, Particle, Oliver Dunk, Chromium Extensions, wOxxOm
Yeah, there's currently no way in Chrome's MV3 to share a secret key between the ISOLATED and the MAIN world. The closest workaround is to use chrome.userScripts to construct the code string with an embedded random event id for 1) the leading script in the USER_SCRIPT world which can use chrome.runtime messaging to obtain the config and 2) the MAIN world script with the same event id.

Note that due to https://crbug.com/40202434 this is still unsafe because the web page can spoof addEventListener in iframes and documents opened via window.open.

wOxxOm

unread,
Feb 6, 2024, 8:50:49 AMFeb 6
to Chromium Extensions, wOxxOm, Particle, Oliver Dunk, Chromium Extensions
P.S. there's a very complicated and wasteful workaround to avoid https://crbug.com/40202434 implemented by Tampermonkey/Violentmonkey via a temporary iframe.

wOxxOm

unread,
Feb 6, 2024, 9:00:34 AMFeb 6
to Chromium Extensions, wOxxOm, Particle, Oliver Dunk, Chromium Extensions
P.P.S. Reminding that with chrome.userScripts you can embed the config into the code string so you can achieve a true document_start for the MAIN world without the need to use messaging. The only disadvantage is the inherently problematic requirement to enable developer mode in chrome://extensions page.

Oliver Dunk

unread,
Feb 6, 2024, 9:02:25 AMFeb 6
to wOxxOm, Chromium Extensions, Particle
Thanks for taking the time to write this up! This is definitely an interesting use case and I can appreciate the hurdles you're running up against.

Isolation and the worlds concept are definitely an interesting area of divergence in general. It would be nice to have more consistent capabilities there but it's tricky because these are so tied to the implementation and what a particular browser vendor is comfortable with.

Another API we have discussed in the community group is a (potentially synchronous) dom.executeScript API that can be called from a content script to run code in the main world. It sounds like this may be helpful? I know it isn't useful today but I bring it up since it is interesting to hear about additional use cases. I also want to make it clear that we genuinely do want to try and find solutions here. The discussion around that is here.

For getting a secret key, wOxxOm's links may be helpful. It sounds like you have a good understanding of the risks you are trying to mitigate and so I don't think there's much I can add to everything that has been said.

As a last thing, I would caution you about some of the things you have mentioned in your threat model. Malicious extensions are very hard to protect against since you don't necessarily have higher privileges than they do. I can understand wanting to make sure your events don't interfere with them, but I wouldn't ever expose any data to the main world you are concerned with them getting.

Also, note that there is only a small security boundary between the main world and content scripts. If the renderer process is compromised (rare, but not impossible) both are running inside and will have access to any data exposed to either.
Oliver Dunk | DevRel, Chrome Extensions | https://developer.chrome.com/ | London, GB

Particle

unread,
Feb 6, 2024, 9:20:49 AMFeb 6
to Chromium Extensions, Oliver Dunk, Chromium Extensions, Particle, wOxxOm
Since the extension is primarily a user experience focused enhancer it fortunately does not currently carry any sensitive data (nor will I ever intend to), or any valuable data the user and I could expect to keep integrity. The idea around the secret is to reduce the chance of rogue code clearing/changing the extension settings. This is not a priority, but a concern that I had since the beginning, although the notes by wOxxOm are certainly very valid and unavoidable.

As for the dom.executeScript discussion I can't help but think it sounds almost like Firefox's X-ray implementation, in which case it makes no sense to make the same thing as Firefox's but in a different way. Hope I am not mistaken.

In any case it really feels like this rush to push for MV3 at any cost without properly well thought out solutions for valid use cases that previously depended on certain permission/execution freedom that no longer exists now created a very hostile or even impossible environment for extensions that works with other browsers just fine.

I am sure this has been repeated many times already, but it is a harsh feeling we developers face when we want to be involved and contributing to the Chrome extension platform.

Oliver Dunk

unread,
Feb 6, 2024, 9:32:27 AMFeb 6
to Particle, Chromium Extensions, wOxxOm
As for the dom.executeScript discussion I can't help but think it sounds almost like Firefox's X-ray implementation, in which case it makes no sense to make the same thing as Firefox's but in a different way. Hope I am not mistaken.

I don't think this is quite the same. My experience using Firefox's X-Ray capabilities is that they allow you to directly access DOM elements and mutate them in a way which is visible in the main world. We don't want to do this, but are comfortable exposing the ability to run a script (which could do this indirectly) as an API. Firefox would likely implement the same API too meaning there would be a consistent way to do this. wOxxOm has a related issue.

In any case it really feels like this rush to push for MV3 at any cost without properly well thought out solutions for valid use cases that previously depended on certain permission/execution freedom that no longer exists now created a very hostile or even impossible environment for extensions that works with other browsers just fine.

Would you be able to say more about the solutions you had to specific problems in MV2 and what approaches you were using? I know there are a few cases where things are more difficult or no longer possible, but a lot of what we've discussed also applies to both. A lot of the time things get highlighted as regressions when really they were the case already so being clear about that is useful when passing on the feedback.
Oliver Dunk | DevRel, Chrome Extensions | https://developer.chrome.com/ | London, GB

Particle

unread,
Feb 6, 2024, 10:08:40 AMFeb 6
to Chromium Extensions, Oliver Dunk, Chromium Extensions, wOxxOm, Particle
> Firefox would likely implement the same API too meaning there would be a consistent way to do this.

That would be perfect. Hopefully it would not be async thought, the injection that is, otherwise it's another detrimental headache.

> Would you be able to say more about the solutions you had to specific problems in MV2 and what approaches you were using?

Quite certainly, simply checking the old version in the Chrome store and simply trying to migrate it to MV3 is enough to surface the issues that were not issues in MV2: https://chromewebstore.google.com/detail/iridium-for-youtube/gbjmgndncjkjfcnpfhgidhbgokofegbl

I could simply inject into a webpage a direct copy of a userscript and it would just work:
Screenshot 2024-02-06 095727.png

The equivalent today hits the CORS limitation.

Similarly, webRequestblocking is another one that was killed and no proper replacement was put in place. Chrome kept pushing DNR as a holy grail solution for this, but the reality is the declarativenetrequest can not even be called an alternative to the removed API, it is simply an extremely emaciated if-this-then-that rule based protocol that does nothing in regards to modifying intercepted content, so even then we can't produce anything worthwhile compared to what we could do before. It's like cutting our legs and expect us to keep running with nothing to help.

This last one is very clearly one of the most criticized changes Chrome did in MV3, to the point of the deadline having been postponed 3 times now - if I am not mistaken - and all the issues that stem from it are either struggling to be resolved (massive workarounds with overly complex fragile and intricate async logic), or outright ignored - in the sense of "we're not going to accommodate this scenario".

I would be really happy to have either of the two: ability to run content script with limited host access (like Firefox does) or have webRequestBlocking returned. With either of them I would be able to make my current extension work easily and with no issues, but the reality is currently this is not possible is what I am concluding.

I do appreciate all the information you guys have been providing.

Oliver Dunk

unread,
Feb 6, 2024, 10:16:01 AMFeb 6
to Particle, Chromium Extensions, wOxxOm
Hopefully it would not be async thought, the injection that is, otherwise it's another detrimental headache.

This is something that we haven't fully decided and would need to discuss more before implementing. We know there are use cases for it being synchronous, so it would definitely come up.

The equivalent today hits the CORS limitation.

Would you mind saying more about this? In particular, how was your content script able to synchronously load the dynamic configuration to use for the main world script?

Similarly, webRequestblocking is another one that was killed and no proper replacement was put in place.

Could you say some more about what you used webRequestBlocking for? Even with that API, there were some things you could modify like response headers and other things you couldn't (like the body). It would be useful to know exactly what you were doing.

In all of these cases, I appreciate that things have become harder for you, I just really want to understand the specifics.
Oliver Dunk | DevRel, Chrome Extensions | https://developer.chrome.com/ | London, GB

Particle

unread,
Feb 6, 2024, 10:44:08 AMFeb 6
to Chromium Extensions, Oliver Dunk, Chromium Extensions, wOxxOm, Particle
> how was your content script able to synchronously load the dynamic configuration to use for the main world script?

It depends, for some stuff it was using sync data:
````
chrome.i18n.getMessage(locale_request)
````

And for others, like user settings, it was still using async:
````
chrome.storage.local.get(this.id, this.main.bind(this));
````

But at that point it was so fast since it was already running in its own context that the result of it was the equivalent as doing this:
````
const script = document.createElement("script");
script.textContent = `static code with dynamic data embedded`;
document.documentElement.appendChild(script);
````


However, in the current version in Firefox the webRequestBlocking can use Promises, meaning we can pull async dynamic configuration and implement the changes in code when I need to, and not miss anything due to racing conditions.

The page itself would already load straightaway with the code and dynamic data injected in it. See an example in https://github.com/ParticleCore/Iridium/blob/c516be2c4eee7ddf4de26f8dad2ac9112be0c9e0/src/js/background.js#L96-L104

But after a quick check, Chrome never adopted Promise callbacks in that same scenario, nor ever implemented filterResponseData, so I might have mistakenly mixed that part in my previous discussion and assumed that at some time both had the same feature parity (it's been about 5 years since, I appologize).

Since that's not relevant please ignore that part about past webRequest capabilities in Chrome.

---

I believe that in the end, for my case (and I would like to think there are many in the same or in a similar spot as me) the most important aspects I require are
  1. be able to run code at a specific time (document_start) 
  2. and include dynamic data in it

That data might not have to be data pulled from async sources, but generated at runtime.

Particle

unread,
Feb 6, 2024, 10:45:38 AMFeb 6
to Chromium Extensions, Particle, Oliver Dunk, Chromium Extensions

Correction,
  1. be able to inject and run code at a specific time (document_start) in the MAIN world

Oliver Dunk

unread,
Feb 6, 2024, 11:52:14 AMFeb 6
to Particle, Chromium Extensions
That all makes sense. It sounds like a mix of existing challenges and some new ones, which is completely fair.

Thanks again for your feedback since there was a lot here and it is definitely useful.
Oliver Dunk | DevRel, Chrome Extensions | https://developer.chrome.com/ | London, GB

Alexei Miagkov

unread,
Feb 6, 2024, 12:22:39 PMFeb 6
to Oliver Dunk, Particle, Chromium Extensions
It feels like we are banging our heads against the wall. Extensions need to inject scripts into page contexts (MAIN_WORLD) before anything else happens on the page. Extension scripts should not be bound by CSPs, etc. We should be able to pass in configuration into these before-anything else happens scripts. Extensions developers have been telling you this for YEARS.

Yes, some of this stuff was broken in Chrome in Manifest V2, but Manifest V3 makes it worse (creating scripts via textContent no longer works, hacks like https://issues.chromium.org/issues/40181146#comment5 are bound by the page's CSP).

Oh and suggesting to use the userScripts or debugger APIs is not helpful!

Particle

unread,
Feb 7, 2024, 12:36:25 AMFeb 7
to Chromium Extensions, Oliver Dunk, Chromium Extensions, Particle
@Oliver

Thought I should add this one too:

One other limitation with the current Chrome implementation is because I cannot bundle data with a static content script that is loaded in the page via manifest, for example a map structure of Setting data (ids, default values, translations, etc.) I am forced to include that as a separate content script into the page in a MAIN world context, which puts it in the global scope of the page instead of inside the anonymous self-executing scope of the original content script.

Why can't I just bundle it inside the content script to begin with? Because the information in that Setting data file is used in various files to avoid redundancy and maintenance nightmare.

I wish there was some sort of content-script option that would allow us to bundle it with dynamic data (say settings.json) which we can update async during the extension lifecycle, but it is loaded and embedded synchronously like any other normal content script file is, something like this:

Screenshot 2024-02-07 003333.png

How that data would be passed to the content script is an afterthought, but the way it is done for executeScript args would not be a bad compromise.

Giorgio Maone

unread,
Feb 7, 2024, 5:46:08 AMFeb 7
to Particle, Chromium Extensions, Oliver Dunk
Hello,

it seems to me here we're all basically restating the concerns and requirements expressed in https://github.com/w3c/webextensions/issues/103.

Do you all agree that implementing the following proposals spawned from there would suffice to cover the use cases you're worried about?
  1. Proposal: RegisteredContentScript.func and RegisteredContentScript.args (similar to ScriptInjection)
  2. Proposal: RegisteredContentScript.workers property to inject WorkerScope(s)
  3. Proposal: RegisteredContentScript.tabIds and RegisteredContentScript.excludeTabIds properties to filter injection
Oliver, they don't seem harder to implement than browser.userScripts, neither more problematic than the existing browser.scripting APIs, are they?

Furthermore https://bugzilla.mozilla.org/show_bug.cgi?id=1736575 (MAIN world support in Firefox) would simplify a lot the cross-browser story.

Oliver Dunk

unread,
Feb 7, 2024, 6:19:54 AMFeb 7
to Giorgio Maone, Particle, Chromium Extensions
I wish there was some sort of content-script option that would allow us to bundle it with dynamic data (say settings.json) which we can update async during the extension lifecycle, but it is loaded and embedded synchronously like any other normal content script file is, something like this

That's an interesting idea. I could see this being a static way to declare params for a content script if we pursue the proposals we discussed higher up in this thread.

It seems to me here we're all basically restating the concerns and requirements expressed in https://github.com/w3c/webextensions/issues/103. Do you all agree that implementing the following proposals spawned from there would suffice to cover the use cases you're worried about?

From my understanding, adding arguments to content scripts would help a lot. I can also see how the other two would be useful in general, but they seem less related to the concerns here.

Oliver, they don't seem harder to implement than browser.userScripts, neither more problematic than the existing browser.scripting APIs, are they?

I can't speak too much to the implementation. Looking at the first one, it sounds feasible (and other members of the team have been supportive in the past) but also non-trivial, since I imagine it would require a number of changes plus the API design around it. I'd love to see it happen and will definitely keep raising it with the team but as I hope you can understand there is always only so much we can work on.
Oliver Dunk | DevRel, Chrome Extensions | https://developer.chrome.com/ | London, GB

wOxxOm

unread,
Feb 7, 2024, 6:23:10 AMFeb 7
to Chromium Extensions, Particle, Oliver Dunk, Chromium Extensions
>  One other limitation with the current Chrome implementation is because I cannot bundle data with a static content script that is loaded in the page via manifest

Excluding the MAIN world, one weird workaround might be to put the stringified data under "data" key in _locales/en/messages.json and then use it like JSON.parse(chrome.i18n.getMessage('data')) anywhere inside the extension. This key should be present only in "en" locale so it will be used as a fallback when the browser's current language is different.

> can't [...] bundle it [...] to avoid redundancy and maintenance nightmare.

FWIW, this should be possible to automate in rollup/webpack/parcel/vite, so there'll be only one definition in your source code to be bundled or imported separately where possible.

Particle

unread,
Feb 7, 2024, 7:31:19 AMFeb 7
to Chromium Extensions, wOxxOm, Particle, Oliver Dunk, Chromium Extensions
At this point, something as simple as "injectScriptIntoMainWorld(self executing function with dynamic data here)" available inside a ISOLATED world content script would make me super happy.

Regarding the trick with the data in a i18n, that would not work for my case since I would need it to be dynamic so I could change it when needed - like user settings behave, but I appreciate the interesting suggestion, and the bundlers as well. I just never liked having to use them, and prefer to keep it vanilla as much as possible.

On the topic at hand, I've had preliminary success in doing the following:
  1. The static code as a content script in manifest with world MAIN and document_start, once it is running in the host page it is actively listening for broadcast messages from the content script
  2. Next is another content script in manifest with world ISOLATED and document_start, once it is running the very first thing it does is get the stored settings (this is async) and then sends it through the same broadcast as the first content script

So far this seems to be working for my needs, it is async but does not seem to be running too slow to become a problem. Though, one thing to pay attention is to ensure the content scripts ordering in the manifest seem to affect which one runs first (and makes sense), so I have to keep the main page content script as the first script so it is already listening for the upcoming messages from the other content script that will execute afterwards.


So I have to work around it with redundancy, for now at least.

Gildas

unread,
Feb 8, 2024, 6:34:41 PMFeb 8
to Chromium Extensions, Particle, wOxxOm, Oliver Dunk, Chromium Extensions
I agree that the ability to inject scripts into the MAIN world asynchronously would also be desirable for security reasons related to privacy.

In the case of SingleFile (id: mpiodijhokgodhhofbcjdecpffjipkle), the FontFace API needs to be patched to offer the best user experience (i.e. save all fonts). However, this makes it much easier for a malicious page to fingerprint the user. Today, the only solution to this problem would be to publish a separate extension, which is quite daunting. If such an API existed, users could sacrifice fonts loaded via the FontFace API and avoid risking this kind of problem with a simple option in SingleFile.

Gildas

unread,
Feb 8, 2024, 6:45:55 PMFeb 8
to Chromium Extensions, Gildas, Particle, wOxxOm, Oliver Dunk, Chromium Extensions

To be more precise in relation to my previous message. The problem actually arose with SingleFile. For example, the website theverge.com used to load fonts with the FontFace API with inline scripts when the document was loaded, which is not a bad practice. In this case, it's technically impossible today to implement the option in SingleFile. to which I was referring
Reply all
Reply to author
Forward
0 new messages