Using ES module in Chrome browser extension content script

Stephen Cow Chau
2 min readJul 1, 2022

Note: This is intended to be a short note for my future self or to whoever found useful, I wish I would have time to organize it in future.

Background

I am trying to include an UI library from others into browser extension’s content script (which would run on the target page)

Failures encountered

1. Getting Content Security Policy violation

As content script is used at target page, so including the script from https://unpkg.com would likely violate the target page policy. Tried to include the content_security_policy in the manifest.json, but just does not see any difference in behavior, so I suspect the manifest.json’s policy only affect background script (or maybe pop up scripts), but not content script.

I too follow the article “How Browser Extensions Routinely Bypass a CSP (Content Security Policy)”, but instead using a blocking webRequest handler, I have to use declarativeNetRequest as I use manifest v3, still not working. ( I don’t trust myself for this one and I didn’t check the v2 approach as well)

2. Copy the script locally, inject it through creating script element and inject

The idea as following:

I copied the module dist js file, and put it inside the module’s /js/ folder named “copied_es_module.js”, then create an HTML element and refer to it.

const scriptElem = document.createElement("script");scriptElem.src = chrome.runtime.getURL("/js/copied_es_module.js");scriptElem.type = "module";document.head.appendChild(scriptElem);sciptElem.addEventListener("load", action);function action(e) {  // code using the module...}

I do set break point inside copied_es_module.js, which I see some webpack code line stating:

root[“XXX”] = factory();

I checked the root object(which is window object inside the module scope), and I see the key XXX appear (which is a class implementation)

And back to the action() callback function, when I access window object, the key XXX not there (which is expected as module scope is just different to global scope)

So tried some other idea like having a middle man module script to instantiate a global variable from the “copied_es_module.js”, still no luck (and I feel this act just not logical to me at all as it’s yet another module scope…, but that’w how people said it worked for them).

2. What does work — put the local “copied_es_module.js” before the actual content script in manifest.json in the content_scripts[“js”] array

Just like this below, and I believe it just got load, and I don’t even need to create a script element to inject the module.

Credits goes to:

--

--