[EDITED] Error: Permission denied to access property "then"

I have a working mv3 chrome extension. When the content script receives a message it dispatches a custom event. The custom event is handled by a class that is injected in the target page window.

Inject the class:

window.test = new Test()

Dispatch event (in content script):

document.dispatchEvent(
    new CustomEvent('myevent', { detail: data })
)

Handle the event (in Test class):

return new Promise(resolve => {
  const listener = (event: any) => {
    document.removeEventListener('myevent', listener)
    console.log('resolving', event.detail)
    resolve(event.detail)
  }
  document.addEventListener('myevent', listener)
})

In chrome I have no problems, but in firefox the promise doesn’t get resolved. I get the ‘resolving’ print statement, but then the error occurs.

Any idea what could be causing the issue? Is it some firefox setting? Could it be my manifest?
Chrome

"permissions": ["tabs", "background", "storage"],
  "action": {
    "default_popup": "index.html",
    "default_icon": "icon_128.png"
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content.js"],
      "run_at": "document_start"
    }
  ],
  "background": {
    "service_worker": "background.js",
    "type": "module"
  },

Firefox

"permissions": ["tabs", "scripting", "storage"],
  "action": {
    "default_popup": "index.html",
    "default_icon": "icon_128.png"
  },
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content.js"]
    }
  ],
  "background": {
    "scripts": ["background.js"],
    "type": "module"
  },

The problem apparently was that the content of event.detail was an object.
To solve it, instead of dispatching the event with { detail: data } I dispatched it with { detail: JSON.stringify(data) }. Then this can be parsed in the listener and the promise resolves correctly resolve(JSON.parse(event.detail)).

1 Like

not sure if it applies to you, but this sounds exactly like mine problem - with sharing data between content script and page script - you have to use cloneInto() to allow page script access objects from content script context, if you want to use promise or encode to json, remember to use cloneFunctions: true.

cloneInto()

Whole guide:

(sorry for necro)

1 Like

Thanks! Just hit this today.

cloneInto() looks to be Firefox-only at this point :sweat_smile: . While Chrome allows the original object to be used.
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Content_scripts/cloneInto#browser_compatibility

IMO cloneInto() function is unlikely to show up in other browsers because it’s closely related to how Firefox implements the separation between page scripts and an extension’s content scripts.

To oversimplify a bit, Chrome executes content scripts in “isolated worlds,” which are basically distinct JavaScript environments that operate on the same DOM tree. Firefox’s content scripts are executed in privileged sandboxes. This approach shares more underlying infrastructure between the page and content scripts, which is why Firefox extensions can use Xray vision to expose data created in one security context to another.

Strictly speaking, Chrome does not “[allow] the original object to be used.” Rather, when a script creates a CustomEvent, Chrome creates and dispatches a copy of that event (and it’s detail value) in every other world running on that page. Firefox doesn’t have that same level of separation, which is why the extension needs to use cloneInto() to expose the CustomEvent’s detail value to page scripts.

1 Like