serversideup / webext-bridge

💬 Messaging in Web Extensions made easy. Batteries included.
https://serversideup.net/open-source/webext-bridge
MIT License
553 stars 50 forks source link

Messages only received after page refresh #63

Open smeijer opened 1 year ago

smeijer commented 1 year ago

Hi,

I'm upgrading testing-playground to manifest v3, and moving from crx-bridge to webext-bridge. I've got things almost working, but one weird thing is bugging me.

Messages only seem to be received after an initial page refresh. So when I'd open a webpage, and open devtools, messages are sent, but not received. I confirm this with:

// devtools.js
import { sendMessage } from 'webext-bridge/devtools';

console.log('devtools loaded');

setInterval(() => {
  console.log('ping');
  sendMessage('PING', 'PONG', 'content-script');
}, 1_000);
// content-script.js
import { onMessage } from 'webext-bridge/content-script';

console.log('content script loaded');

onMessage('PING', () => {
  console.log('pong');
});

The loaded statements are logged, the ping is logged, but the pong never is. When I refresh the window with devtools open, the ping starts coming in and pong is logged as well.

Maybe related, but not sure, when I'd switch the direction, and send a PING fromcontent-script to devtools, I get the error: No handler registered in 'devtools' to accept messages with id 'PING'

For context, I've also setup window messaging, but didn't seem to be relevant for this case.

Any idea what's going on?

zikaari commented 1 year ago

Hi @smeijer can you please confirm how many content scripts are being loaded into the page by your extension that also have the webext-bridge/content-script import?

smeijer commented 1 year ago

Hi @zikaari ,

I have 3 content-scripts in total. Only one imports webext-bridge/content-script.

  1. A single content-script with world "isolated", with contents: (that's the whole file)

    // content-script/content-script.js
    import { allowWindowMessaging } from 'webext-bridge/content-script';
    
    allowWindowMessaging('com.testing-playground.devtools');
  2. I also inject a content-script on world "MAIN", with contents:

    // content-script/window-script.js
    import { sendMessage, onMessage, setNamespace } from 'webext-bridge/window';
    setNamespace('com.testing-playground.devtools');
    
    // truncated
  3. I inject a content-script on world MAIN that adds some methods to window. It does not include webext-bridge

I load all those scripts via the manifest:

  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["content-script/content-script.js"],
      "run_at": "document_start"
    },
    {
      "matches": ["<all_urls>"],
      "js": ["content-script/window-script.js"],
      "run_at": "document_start",
      "world": "MAIN"
    },
    {
      "matches": ["<all_urls>"],
      "js": ["window/testing-library.js"],
      "run_at": "document_start",
      "world": "MAIN"
    }
  ]

For full disclosure, I also have a background script (full contents):

// background.js
import 'webext-bridge/background';

And I need to communicate between content-script/window-script.js && devtools. Which again, works after page refresh, but not before 🤷‍♂️

smeijer commented 1 year ago

@zikaari, any thoughts? I'd appreciate any help you could offer me.

grinholz commented 11 months ago

I have a similar problem, also when I open a new tab. Also only fixed when I do a full page load. In my case I'm trying to send a message from my background script to the content script in the new tab.

Then as I type in the omnibox it autocompletes a page and prerenders it. My content script is injected and starts working just fine. But then when my background script detects that this new tab is the "active" one now and tries to send it a message, the content script never receives the message.

It seems related to this Stack Overflow article - where Chrome is automatically disconnecting the port for the content scripts of prerendered pages once they transition to their "active" lifecycle stage: https://stackoverflow.com/questions/76841137/in-chrome-extension-content-script-does-not-receive-message-from-background-on

The author of that problem solved the problem by detecting the disconnect and automatically reconnecting, queueing up and finally sending any unsent messages once reconnected.

zikaari commented 11 months ago

Folks, I apologize for delayed response. Unfortunately I no longer maintain this project due to lack of resources. @smeijer would you be willing to take on this project since your project heavily depends on it?

grinholz commented 10 months ago

No worries. I ended up solving by removing this package as a dependency and just writing my own simple messaging utility functions. I didn't dig in too deeply but pretty sure the problem is in the code that generates a unique key to identify the tab. Guessing that when a page is pre-rendered (when typing in a new tab) that key is different then when it transitions to active, and then the message is aborted due to key mismatch.

smeijer commented 8 months ago

@zikaari, I wish I could, but I share the same lack of resources (time). So it wouldn't get any better in my hands. Sorry.

@grinholz , can you share that "simple messaging utility"?

grinholz commented 8 months ago

Something like this, where you just use the native events.

export async function sendMessageToTab(tabId: number, type: string, payload: unknown) {
    return (await chrome.tabs
        .sendMessage(tabId, {
            to: "content",
            type,
            payload
        })
    );
}