Executing code at page-level from Background.js and returning the value

You are quite right in understanding the 3-layer context separation.

  • A background page is a separate page and therefore doesn”t share JS or DOM with visible pages.
  • Content scripts are isolated from the webpage’s JS context, but share DOM.
  • You can inject code into the page’s context using the shared DOM. It has access to the JS context, but not to Chrome APIs.

To communicate, those layers use different methods:

Background <-> Content talk through Chrome API.
The most primitive is the callback of executeScript, but it’s impractical for anything but one-liners.
The common way is to use Messaging.
Uncommon, but it’s possible to communicate using chrome.storage and its onChanged event.

Page <-> Extension cannot use the same techniques.
Since injected page-context scripts do not technically differ from page’s own scripts, you’re looking for methods for a webpage to talk to an extension. There are 2 methods available:

  1. While pages have very, very limited access to chrome.* APIs, they can nevertheless use Messaging to contact the extension. This is achieved through "externally_connectable" method.

    I have recently described it in detail this answer. In short, if your extension declared that a domain is allowed to communicate with it, and the domain knows the extension’s ID, it can send an external message to the extension.

    The upside is directly talking to the extension, but the downside is the requirement to specifically whitelist domains you’re using this from, and you need to keep track of your extension ID (but since you’re injecting the code, you can supply the code with the ID). If you need to use it on any domain, this is unsuitable.

  2. Another solution is to use DOM Events. Since the DOM is shared between the content script and the page script, an event generated by one will be visible to another.

    The documentation demonstrates how to use window.postMessage for this effect; using Custom Events is conceptually more clear.

    Again, I answered about this before.

    The downside of this method is the requirement for a content script to act as a proxy. Something along these lines must be present in the content script:

    window.addEventListener("PassToBackground", function(evt) {
      chrome.runtime.sendMessage(evt.detail);
    }, false);
    

    while the background script processes this with a chrome.runtime.onMessage listener.

I encourage you to write a separate content script and invoke executeScript with a file attribute instead of code, and not rely on its callback. Messaging is cleaner and allows to return data to background script more than once.

Leave a Comment