Send data from background to injected script (manifest version 3 and typescript)

1,039 views
Skip to first unread message

Uday prasad

unread,
Aug 25, 2023, 12:46:30 AM8/25/23
to Chromium Extensions
I want to send data from background script to injected script which is accessed using content script. I am using config file to build bundle.js files from src folder itself

Folder structure:
build
-> index.html
src
->background.tsx
->content.tsx
->injected.tsx
->manifest.json

background.tsx
...

content.tsx
...
function injectScript() {
  const script = document.createElement("script");
  script.src = chrome.runtime.getURL("injected.bundle.js");
  (document.head || document.documentElement).appendChild(script);
  }
injectScript()
...


injected.tsx

...

manifest.json

"manifest_version": 3
"background": {
    "service_worker": "background.bundle.js"
  },
  "content_scripts": [
    {
      "type": "module",
      "matches": [
        ...
      ],
      "js": ["content.bundle.js"],
      "run_at": "document_start"
    }
  ],
  "web_accessible_resources": [
    {
      "matches": ["<all_urls>"],
      "resources": ["injected.bundle.js"]
    }
  ],

When I used "chrome.runtime.onMessage.addListener" in background.tsx and "chrome.runtime.sendMessage" inside injected.tsx or content.tsx, I got an error showing  Uncaught TypeError: Cannot read properties of undefined (reading 'sendMessage')

Can anyone tell where I am going wrong? How to resolve this issue? Sending data from background and accessing it in injected script

wOxxOm

unread,
Aug 25, 2023, 2:06:19 AM8/25/23
to Chromium Extensions, Uday prasad
The "injected script" runs in a different JS context (in the unsafe "MAIN" world), so it doesn't have access to `chrome` API, which is exposed only in the safe content script's default "ISOLATED" world.

For a content script that runs in all sites you'll have to use CustomEvent messaging between the default content script and the injected script (example: https://stackoverflow.com/a/19312198/). The content script will use chrome.runtime.sendMessage to relay the message to the background script.

In case the content script runs only in a few specified sites you can use external messaging, but that doesn't apply to your case.

BTW since you use document_start your goal is probably to run the injected script before the page runs its scripts, but it's not what happens now because the script element you've added is loading asynchronously due to the use of `src`, so it will run after an inline <script>foo</script> in the page as well as possibly other small remote scripts. The solution is to declare the injected script separately in manifest.json so you don't need to inject it manually:

  "content_scripts": [{
    "matches": [...],
    "js": ["content.js"],
    "run_at": "document_start"
  }, {
    "matches": [...],
    "js": ["injected.js"],
    "run_at": "document_start",
    "world": "MAIN"
  }],

* remove injectScript function from content.js, add CustomEvent messaging
* remove "web_accessible_resources" from manifest.json
* remove "type": "module" from content_scripts in manifest.json as there's no such key and there's no way to declare a module content script.

Uday prasad

unread,
Aug 25, 2023, 4:46:12 AM8/25/23
to Chromium Extensions, wOxxOm, Uday prasad
Firstly thanks for quick response. I have followed the steps provided by you and (example: https://stackoverflow.com/a/19312198/) and added code in both content.tsx and injected.tsx . Now I tried to see the console for log statement result but Instead found 

DevTools failed to load source map: Could not load content for chrome-extension://kdpbgfpckkkgaedfimgiohdjdmfhcmfd/content.bundle.js.map: System error: net::ERR_BLOCKED_BY_CLIENT
DevTools failed to load source map: Could not load content for chrome-extension://kdpbgfpckkkgaedfimgiohdjdmfhcmfd/injected.bundle.js.map: System error: net::ERR_BLOCKED_BY_CLIENT

injected.tsx

document.addEventListener('yourCustomEvent', function (e) { var data = e.detail; console.log('received', data); });

content.tsx

var data = { allowedTypes: 'those supported by structured cloning, see the list below', inShort: 'no DOM elements or classes/functions', }; document.dispatchEvent(new CustomEvent('yourCustomEvent', { detail: data }));

manifest.json

* removed "web_accessible_resources" and 
"type": "module" from manifest.json

"content_scripts": [{
    "matches": [...],

    "js": ["content.bundle.js"],
    "run_at": "document_start"
  }, {
    "matches": [...],
    "js": ["injected.bundle.js"],
    "run_at": "document_start",
    "world": "MAIN"
  }],
  "content_security_policy": {
    "extension_pages": "script-src 'self'; object-src 'self'",
    "host_permissions": ["<all_urls>"]
  }


Where should I make updates?

wOxxOm

unread,
Aug 25, 2023, 4:49:41 AM8/25/23
to Chromium Extensions, Uday prasad, wOxxOm
Your content.js is declared first so when it sends a message there's no receiver because injected.js runs afterwards.

Uday prasad

unread,
Aug 25, 2023, 5:23:27 AM8/25/23
to Chromium Extensions, wOxxOm, Uday prasad
Thanks, that works fine for static data. One more thing, what if I want to make use of "chrome.storage.local" or react hooks? I keep getting data as null in injected.tsx but content.tsx returns value after some time.

wOxxOm

unread,
Aug 25, 2023, 5:31:42 AM8/25/23
to Chromium Extensions, Uday prasad, wOxxOm
It depends on how and where it runs, so maybe you can show your code.

Uday prasad

unread,
Aug 25, 2023, 5:38:17 AM8/25/23
to Chromium Extensions, wOxxOm, Uday prasad
chrome.storage.local seems to be working. Since it is an asynchronous function I have tried document.dispatchEvent function outside like this

Delayed output code: 
let result;
chrome.storage.local.get((res) => {
result = res
  console.log(res)
})
document.dispatchEvent(new CustomEvent('yourCustomEvent', { detail: [data, result] }));

Working code: 

chrome.storage.local.get((res) => {
  console.log(res)
  document.dispatchEvent(new CustomEvent('yourCustomEvent', { detail: [data, res] }));
})

With this I am able to access local storage code in injected.tsx file.

Will get back to you if any issues occur.

Thanks once again

wOxxOm

unread,
Aug 25, 2023, 5:51:00 AM8/25/23
to Chromium Extensions, Uday prasad, wOxxOm
Callback in `chrome` methods is asynchronous, which means it runs after the current execution context completes i.e. in your first code it runs after document.dispatchEvent. Your second code is correct. This is how asynchronous JS works, it's not unique to chrome extensions. 

Uday prasad

unread,
Sep 1, 2023, 6:39:28 AM9/1/23
to Chromium Extensions, wOxxOm, Uday prasad
Hi,

I would like to execute the same process but after processing a certain function.

Required functionality :

Create a button in sample website --> onclick check for extension instance using a window.custom from injected script

If present ? show an ui to take input from user and pass the same to extension

In extension validate input and inject another data to "MAIN"(i.e., accessible to all sites) world after validation.

I would like to get sample example to achieve this.
Reply all
Reply to author
Forward
0 new messages