javascriptbrowsersafariexecute-script

How to send a message from anonymous script executed with `browser.scripting.executeScript`?


I'm using browser.scripting.executeScript() and i need to send a message to webext background script/service worker script. Usually it means using browser.runtime.sendMessage(), but browser is reportedly undefined in anomymous script. I can pass browser as call script argument, but i believe it will be serialized and i'm not sure it survives the boundary passing.

Here is the code:

await browser.scripting.executeScript({
    target: details,
    world: "MAIN",
    injectImmediately: true,
    args: [tabId, frameId],
    func: (tabId, frameId) => {
      if (window.history.__pushState) {
        console.warn("Already injected");
        return;
      }
      window.history.__pushState = history.pushState;
      console.log("Injected pushState()");
      history.pushState = function(state, unused, url) {
        // eslint-disable-next-line no-console
        console.log("Intercepted pushState()", state, unused, url);
        window.history.__pushState(state, unused, url);

        console.log("debug", browser); // `browser` is undefined

        browser.runtime.sendMessage({
          type: "safari-onhistorystateupdated",
          state, unused, url
        });
      };
    }
  });

How can i access browser object in such anonymous script?


Solution

  • I was able to solve it by sending message from anonymous script to `window`:

    window.postMessage({
        type: "safari-onhistorystateupdated-content",
        state, unused, url
    });
    

    And listening in window with injected content-script and resending it to webext background script:

    window.addEventListener("message", (event) => {
        if (!event.isTrusted)
          return;
    
        if (event && event.data && event.data.type === "safari-onhistorystateupdated-content") {
          let {state, unused, url} = event.data;
          browser.runtime.sendMessage({
            type: "safari-onhistorystateupdated",
            state, unused, url
          });
        }
      },
      false
    );
    

    Bg script was listening it:

    async function handleMessage(message, sender) {
      switch (message.type) {
      ...
      case "safari-onhistorystateupdated":
          const {state, unused, url} = message;
          handleOnHistoryStateUpdated(
            sender.tab.id, sender.frameId, state, unused, url);
          break;
      ...
      }
    }
    
    function onMessage(message, sender) {
      if (typeof message == "object" && message != null && message.type) {
        return handleMessage(message, sender);
      }
      return false;
    }
    ...
    browser.runtime.onMessage.addListener(onMessage);