Closed Bug 1397667 Opened 7 years ago Closed 3 years ago

"No matching message handler" error when tabs.update().then(tabs.executeScript())

Categories

(WebExtensions :: Frontend, defect, P3)

55 Branch
defect

Tracking

(Not tracked)

RESOLVED WORKSFORME

People

(Reporter: bibicron, Assigned: robwu)

References

Details

(Whiteboard: [tabs])

User Agent: Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:55.0) Gecko/20100101 Firefox/55.0
Build ID: 20170824053622

Steps to reproduce:

This could be related to Bug 1254003. Except that that bug manifested when tabs.create().then(tabs.executeScript()). Probably the fix is the same.

The error happens in the background script of an add-on. After the user clicks on the context menu, a new page will be displayed. The new page will be generated by a content script injected in a blank page. The new page can optionally open in:
- a new tab: tabs.create("about:blank").then(tabs.executeScript())
- the current tab: tabs.update("about:blank").then(tabs.executeScript())



Actual results:

In the first case (tabs.create) the code is executed correctly. In the second case (tabs.update) the code is just sometime executed correctly. When it fails, the Promise returned by the executeScript(), will be rejected with error "Error: No matching message handler".


Expected results:

The code should execute.
Component: Untriaged → WebExtensions: Frontend
Product: Firefox → Toolkit
The bug can be confirmed by the following code. The code can be run in a debug console of any add-on. The result should be "ok", not "fail: Error: No matching message handler";

var tabId = -1;
browser.tabs.onUpdated.addListener(
	function( tab_id, changeInfo, tab1 ) {
		if( tabId != -1 && tab_id == tabId && changeInfo.status == "complete" ) {
			tabId = -1;
			browser.tabs.update( tab_id, { url: "about:blank" } ).then(
				function( tab2 ) {
					browser.tabs.executeScript( tab2.id, { code: "alert(123);", matchAboutBlank: true } ).then(
						function() { console.log( "ok" ); },
						function( e ) { console.log( "fail: " + e ); }
					);
				}
			);
		}
	}
);
browser.tabs.create( { url: "http://www.google.com" } ).then( 
	function( tab ) {
		tabId = tab.id;
	}
);
The previous code will succeed if you replace:
    browser.tabs.update( tab_id, { url: "about:blank" } )
with:
    browser.tabs.create( { url: "about:blank" } )
Priority: -- → P3
Whiteboard: [tabs]
I'm running into the same issue just in the success handler of `browser.windows.create`

```js
browser.windows.create({
    incognito: true,
    url: "https://www.mozilla.org/fr/privacy/firefox/",
    state: "minimized",
    type: "detached_panel"
}).then((window) => {
    let tab = window.tabs[0];
    browser.tabs.executeScript(tab.id, {
        code: "alert('I am alert')"
    }).then(() => {
        console.log("I was alerted")
    }).catch(() => {
        console.error("I failed")
    });

})
```

Even adding a `browser.tabs.get(tab.id)` and executing a script in it doesn't work.

The firefox version I use is 58.0a1 (2017-10-20) (64-bit)
Same issue here.

It turns out that this problem happens when the tab still has status "loading". tabs.executeScript() parameter runAt defaults to "document_idle" (execute when everything is loaded) but this does not work (even when setting this property explicitly).

In my case, the problem is timing related and hard to reproduce as it does not happen every time. I was unable to reproduce it with Firefox Developer Edition, only with standard Firefox 57.

My workaround consists of waiting for the tab to get status "complete" before executing tabs.executeScript().

// create window and get its tab
.then((tab)=>{
  if(tab.status=="loading") {
    return new Promise((resolve, reject) => {
      var timer = setTimeout(()=>{
        browser.tabs.onCreated.removeListener(onUpdated);
        reject(new Error("Tab did not complete"));
      },5000);
      function onUpdated(tabId,changeInfo,_tab) {
        if(tabId == tab.id && _tab.status=="complete") {
          clearTimeout(timer);
          browser.tabs.onUpdated.removeListener(onUpdated);
	  resolve(_tab);
        }
      }
      browser.tabs.onUpdated.addListener(onUpdated);
    });
  } else 
    return tab;
})
.then((tab)=>{
// it's now safe to call tabs.executeScript
Not only that, but new tabs always start out with about:blank before they load whatever you requested. And the about:blank itself at least sometimes reaches the "complete" status. So this elaboration of Michel's above code is necessary:

/**
 * Wait until the given tab reaches the "complete" status, then return the tab.
 *
 * This also deals with new tabs, which, before loading the requested page,
 * begin at about:blank, which itself reaches the "complete" status.
 */
async function tabCompletion(tab) {
    function isComplete(tab) {
        return tab.status === 'complete' && tab.url !== 'about:blank';
    }
    if (!isComplete(tab)) {
        return new Promise((resolve, reject) => {
            const timer = setTimeout(
                function giveUp() {
                    browser.tabs.onUpdated.removeListener(onUpdated);
                    if (isComplete(tab)()) {
                        // Give it one last chance to dodge race condition
                        // in which it completes between the initial test
                        // and installation of the update listener.
                        resolve(tab);
                    } else {
                        reject(new Error('Tab never reached the "complete" state, just ' + tab.status + ' on ' + tab.url));
                    }
                },
                5000);
            function onUpdated(tabId, changeInfo, updatedTab) {
                // Must use updatedTab below; using just `tab` seems to remain
                // stuck to about:blank.
                if (tabId === updatedTab.id && isComplete(updatedTab)) {
                    clearTimeout(timer);
                    browser.tabs.onUpdated.removeListener(onUpdated);
                    resolve(updatedTab);
                }
            }
            browser.tabs.onUpdated.addListener(onUpdated);
        });
    }
}
Product: Toolkit → WebExtensions
Can confirm. There is this explicit check (introduced by https://hg.mozilla.org/mozilla-central/rev/70f6eb26604a from bug 1260548) that may be causing the problem:
 https://searchfox.org/mozilla-central/rev/c3fef66a5b211ea8038c1c132706d02db408093a/browser/components/extensions/parent/ext-tabs.js#112

As a work-around, you could try repeating tabs.executeScript until the error changes (warning: internal changes are coming, so the error message might change within a couple of Firefox releases).


Alternatively, given the above line to blame, using a URL other than "about:blank" should help.
Have you tried to open "about:blank?" (with a question mark after "about:blank")?
Status: UNCONFIRMED → NEW
Ever confirmed: true
See Also: → 1260548
>Alternatively, given the above line to blame, using a URL other than "about:blank" should help.
Have you tried to open "about:blank?" (with a question mark after "about:blank")?

I'm never explicitly opening about:blank; the only time it arises is when Firefox itself decides to load it, implicitly, when it creates the new tab, before loading the page I requested. (If I misunderstood your suggestion, please let me know.)
I will look into this bug at the end of the 64 cycle.
Assignee: nobody → rob
Status: NEW → ASSIGNED
See Also: → 1418655

Still true as of FF 65.

This is something that seems to have become much more visible/frequent since Firefox 64. We have a browsing protection extension as part of our security suite, and one of its features is to add ratings to google/yahoo/bing search results, using tabs.onUpdated and tabs.executeScript(). Until recently we didn't have any problems with it, but now I noticed that around half of the cases in Google searches fail, and always the first search from Yahoo main page fails. Our problem is not (at least easily) reproducible with Firefox 61.0-63.0 nor 60.4.0esr, but it does reproduce always in 64.0, 65.0, 66.0b6 developer edition and 66.0b7. Error message is the same "No matching message handler". Waiting for the tab.status === 'complete' seems to help according to my first tests.

The other thing to be aware of is that, as of recent Firefoxes (65 at the latest), more onUpdated events are fired than before, notifying you of changes to secondary traits like "attention" (https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/tabs/onUpdated#changeInfo). Depending on what you're doing, it might behoove you to check the changeInfo param in your handler, as I had to: https://github.com/mozilla/fathom-fox/commit/0d4c4bd73170f2543ebdc1c218e2f9f582cacd67#diff-2df2bbad6da65a2f9750bb1c21d59ae6R45.

This affects also browser.tabs.duplicate API. In my case I was duplicating my own extension tab, then sending it message. This randomly failed even when I was waiting for "complete" status - yes, because there is "about:blank" first with "complete" status. Crazy!!!

Is this somewhere defined how it should behave? I mean, this is a bug, right?
I can't remember if I had this problem in Chrome as well or not...

Also the docs are not very helpful - for the duplicate API "...resolves as soon as the tab has been duplicated" (define "duplicated", right? :D).

(In reply to juraj.masiar from comment #12)

This affects also browser.tabs.duplicate API. In my case I was duplicating my own extension tab, then sending it message. This randomly failed even when I was waiting for "complete" status - yes, because there is "about:blank" first with "complete" status. Crazy!!!

Is this somewhere defined how it should behave? I mean, this is a bug, right?
I can't remember if I had this problem in Chrome as well or not...

Also the docs are not very helpful - for the duplicate API "...resolves as soon as the tab has been duplicated" (define "duplicated", right? :D).

browser.tabs.duplicate used to only resolve when the tab finished loading, but in bug 1394376 it changed to resolve as soon as the tab UI has been initialized. The implementation attempts to ensure that tabs.executeScript only runs when the tab is ready to handle tabs.executeScript calls (as of bug 1559216), but there is no such logic for browser.tabs.sendMessage.

(In reply to Rob Wu [:robwu] from comment #13)
Ah yes, you are right!
Sorry for the confusion... it's similar, but actually totally different use case :)

I ran comment 1's STR and it works as intended for me.

STR from comment 3 is covered by bug 1416087

Status: ASSIGNED → RESOLVED
Closed: 3 years ago
Resolution: --- → WORKSFORME

First, I would like to thank Erik Rose for his super workaround. I do however have one improvement I would suggest in the (sub) function isComplete(). One should also test for undefined. This occurred to me when developing an extension for Thunderbird. The function should now read:

    function isComplete(tab) {
        return tab.status === 'complete' && tab.url !== undefined && tab.url !== 'about:blank';
    }
You need to log in before you can comment on or make changes to this bug.