2

I'm using a Firefox WebExtension that makes the link between a web page and a native executable (using native messaging API).

So far, I've been using a pair of content / background scripts, and I send / receive messages using window.PostMessage like this:

Page Script

// Method used to communicate with content sript of the Firefox WebExtension

var callExtension = function(JSONmessage, timeout, callbackFnk) {

    var promiseRequest = new Promise((resolve, reject) => {
      const listener = (event) => {
        if (
          event.source == window && event.data.direction
          && event.data.direction == "my-extension-from-content-script"
        ) {
          resolve(event.data.message);
        }
      }

      setTimeout(() => {
        window.removeEventListener("message", listener);
        resolve(false); // Timeout 
      }, timeout);

      window.addEventListener("message", listener);

      window.postMessage({
        direction: "my-extension-from-page-script",
        message: JSONmessage
        }, "*");
    });

    promiseRequest.then((result) => { 

         // now we are calling our own callback function
          if(typeof callbackFnk == 'function'){
            callbackFnk.call(this, result);
          }

    });

};

// Checks the presence of the extension

callExtension("areYouThere", 200, function(result) { 
    if(result) {
        $("#result").text("Extension installed");
    } else {
        $("#result").text("Extension not installed");
    }
});

Content Script

window.addEventListener("message", function(event) {
  if (event.source == window &&
      event.data.direction &&
      event.data.direction == "my-extension-from-page-script") {

    if(event.data.message == "areYouThere") { 

        /** Checks the presence of the extension **/

        window.postMessage({
        direction: "my-extension-from-content-script",
        message: "OK"
      }, "*");
    } 
  } 
});

The code works fine on a simple web page, but when I try to make it work on a page that already uses a window.postMessage and window.addEventListener ("message", ...), the message sent from the page is not captured by the extension, and so my extension cannot work.

  1. Is there a way to send a message from the page script to the content script that does not use window.postMessage and window.addEventListener?

  2. If not, how to be sure the message that is sent from window.postMessage from the page will be sent only to my extension and will not be captured by another listener?

Makyen
  • 31,849
  • 12
  • 86
  • 121
Thordax
  • 1,673
  • 4
  • 28
  • 54
  • To be able to answer this (without just guessing as to why it is occurring), we are going to need a *complete* [mcve] (including a *manifset.json* file) along with the actual URL on which you are trying to accomplish this and it is failing. – Makyen Mar 10 '17 at 17:00

2 Answers2

2

It appears that the web page that you are working with is not playing nice with window.postMessage. This is understandable, as such a web page would normally be expecting to be the only thing using it in the page.

You have four alternatives:

  1. Install your message event listener on the window before the web page scritps install any listeners so that you receive the event first. This will most likely be done by injecting at document_start and installing your listener prior to the scripts for the page being loaded. In addition to your call to .addEventListener() being prior to any script on the page acting (i.e. injecting at document_start), you should indicate that your listener will useCapture. This will cause your listener to be called prior to any listener added by page scripts. You will need to uniquely identify in the message content that messages are to/from your extension code. You will need to cancel the event (.stopImmediatePropagation() and .stopPropagation()) for those messages which are yours, and allow the event to propagate for those that are not.
  2. Use a custom event just for your extension.
  3. Directly manipulate variables/functions and/or call functions in the page context by inserting additional <script> elements.
  4. Dynamically change the code of the web page's scripts so they play nice. This will only work on a single page/domain and could easily break if the web page changes its code. While possible, this is not a good option.
Makyen
  • 31,849
  • 12
  • 86
  • 121
  • Thanks a lot for your quick and exhaustive answer! – Thordax Mar 10 '17 at 23:29
  • @Makyen I tried number 1 for another problem I have, but it doesn't work. Should it? I thought a webextension is sandboxed and only has a clean view of the DOM, but no access to its javascript? – Sebastian Blask May 10 '17 at 10:13
  • @SebastianBlask #1 does not rely on access to the page's JavaScript, only the DOM. If you need access to the page's JavaScript you have to [insert a script into the page's context](http://stackoverflow.com/questions/9515704/insert-code-into-the-page-context-using-a-content-script/9517879#9517879). There is no way for me to respond to your question without seeing the code and knowing the page against which it is intended to be run. – Makyen May 10 '17 at 22:08
  • @Makyen For me, `stopImmediatePropagation` and `stopPropagation` did not work. I thought they would not effect the page's script because of the sandboxing. But they do. It did not work for me because the page's code ran before mine. Using `document_start` and `useCapture` in `addListener` solved the problem. – Sebastian Blask May 11 '17 at 09:45
  • 1
    @SebastianBlask, Yes, you will need to install your listener in the capture phase, not the bubbling phase. Thank you for pointing out that I had left that requirement as implicit rather than explicitly stating that need. If you don't install your listener in the capture phase, then it will be called after all other added listeners, other than those that are also on the `window` and in the bubbling phase which were added after yours. I have updated this answer to explicitly state that you need to use capture (Using capture is a general default assumption on my part). – Makyen May 11 '17 at 23:05
0

Export a function from your content script, and then can call that from the page.

Content Script

var myContentScriptFunction = function(myVariable) {
    alert(myVariable);
}

exportFunction(myContentScriptFunction, window, {defineAs: 'myContentScriptFunction'});

Page Script

if (window.myContentScriptFunction) window.myContentScriptFunction('Working');