Use BroadcastChannel to allow WebExtensions to communicate with Test Pilot#1022
Conversation
|
I've got my stuff working with the old iframe postMessage branch for today's meeting/demo. But I'll look at switching to this tomorrow. |
0f38165 to
506f838
Compare
|
@groovecoder Yeah, I think this is what we're going to end up going with. It's way simpler, and doesn't require an iframe page - or even necessarily having a local Test Pilot server working. |
| nestedData: { | ||
| intData: 10, | ||
| }, | ||
| }); |
There was a problem hiding this comment.
Could use some elaboration on what the postMessage payload should contain. Just a JSON blob of data? Any required properties?
There was a problem hiding this comment.
Same as the other mechanism - just a JS object with what you want included in the payload of the Telemetry ping. Test Pilot itself is agnostic on this, so it's more a concern between you & the metrics team to spec out.
There was a problem hiding this comment.
That is to say, what you send here is the ... in the example ping shown in README-METRICS.md for the testpilottest ping - Test Pilot wraps that in the other envelope info and the full client environment data.
|
FWIW, this is largely based on @rpl's work experimenting with BroadcastChannel |
506f838 to
a66c3b9
Compare
|
I ran Probably because I have/had no other installed add-ons when I installed this Test Pilot one? |
|
Got the same error when I disabled and re-enabled this Test Pilot build while I had my add-on installed. 😞 I also tried adding a breakpoint at My new code is: https://github.com/mozilla/blok/compare/telemetry-ping-6?expand=1 |
a66c3b9 to
bb6f723
Compare
|
Ugh, yeah, I have a general bad habit of not trying things in the blank slate state with a fresh profile. Just wrapped that code in a check whether the list of installed experiments is even defined yet, hopefully that improves things. |
|
I don't get that error anymore, but I get a different error (twice) when loading the Test Pilot add-on now: I tried adding But I don't see this messages in the browser toolbox console when I install the |
|
Hmm, that error is confusing - doesn't mention any Test Pilot code anywhere, and I can't repro on a fresh Firefox profile. But it does look like it happens during the doorhanger to warn of an add-on installation, maybe? As for |
|
I tossed those |
|
@groovecoder all that said, your WebExtension code looks correct and a WebExtension shouldn't be able to cause these errors... so there's still probably something weird happening on the Test Pilot side 😞 |
|
I didn't get the error in a fresh profile. Do I need to do something with my add-on code to make sure it's registered as a testpilot experiment? So I can make sure it's causing |
Oh, right! Yeah, you do: In your local |
|
This is also getting me thinking about coming up with a way to make this process simpler - because you shouldn't have to have a full local instance of the Test Pilot site just to develop a WebExtension. Will probably take me some time, but we might be able to come up with just a stub JSON file to feed the Test Pilot add-on in the future. Edit: #1029 |
|
Are there docs to add & enable my web extension as an experiment in |
|
No docs. You go to /admin/, login as admin with password admin, and add an Experiment record with an add-on id that matches your add-on |
| const docShell = addonChromeWebNav.QueryInterface(Ci.nsIInterfaceRequestor) // eslint-disable-line new-cap | ||
| .getInterface(Ci.nsIDocShell); | ||
| docShell.createAboutBlankContentViewer(principal); | ||
| const window = docShell.contentViewer.DOMDocument.defaultView; |
There was a problem hiding this comment.
@lmorchard Is this (lines 28-38) the best way to get a DOM window pointer for the given moz-extension URL? I guess I'm wondering if you found this snippet somewhere, or if it was suggested by someone on the FF team.
I asked about safely getting a DOM Window in #fx-team but got no response. I'm wondering if there might be a simpler or safer way--usually Gecko has N ways to do something, but only 1-3 ways that are considered good ways. I'll look further into it.
There was a problem hiding this comment.
@6a68 This is mostly not my code, it comes from experiments with BroadcastChannel over here. Honestly, I didn't really know you could get a DOM window pointer for another origin from add-on code :)
There was a problem hiding this comment.
Honestly, I didn't really know you could get a DOM window pointer for another origin from add-on code :)
@lmorchard Ah yeah, once you require('chrome') you effectively have rootly powers, and a lot of weird stuff becomes possible ^_^
After looking at this snippet for a while, and reading through https://bugzil.la/1186732, I'm much less scared of it. But it might be nice to document it for the sake of our future selves. Here's an attempt, take whatever seems useful, or skip it if it seems to make things more confusing overall:
function createChannelForAddonId(name, addonId) {
// The BroadcastChannel API allows messaging between different windows that
// share the same origin. Bug 1186732 extended this to WebExtensions (which
// may not have an origin) by adding a special URL that loads an about:blank
// page at the (generalized) "origin" of the extension.
//
// Load that about:blank page, and use its `window` to get a BroadcastChannel
// that allows two-way communication between the main Test Pilot extension and
// individual experiment extensions.
// Note: the `targetExtensionUUID` is different for each copy of Firefox,
// so that malicious websites can't guess the special URL associated with
// an extension.
const targetExtensionUUID = getExtensionUUID(addonId);
// Create the special about:blank URL for the extension.
const baseURI = Services.io
.newURI(`moz-extension://${targetExtensionUUID}/_blank.html`, null, null);
// Create a principal (security context) for the generalized origin given
// by the extension's special URL and its `addonId`.
const principal = Services.scriptSecurityManager
.createCodebasePrincipal(baseURI, { addonId });
// Create a hidden window and open the special about:blank page for the
// extension.
const addonChromeWebNav = Services.appShell.createWindowlessBrowser(true);
const docShell = addonChromeWebNav.QueryInterface(Ci.nsIInterfaceRequestor) // eslint-disable-line new-cap
.getInterface(Ci.nsIDocShell);
docShell.createAboutBlankContentViewer(principal);
const window = docShell.contentViewer.DOMDocument.defaultView;
// Finally, get the BroadcastChannel associated with the extension.
const addonBroadcastChannel = new window.BroadcastChannel(name);
// Callers need to keep the pointer to the window, otherwise the window's
// BroadcastChannel will get garbage collected.
return {
addonChromeWebNav,
addonBroadcastChannel
};
} There was a problem hiding this comment.
Think I'll just plonk that on in - verbose commenting of this magic is a great addition, thanks!
|
@lmorchard I'm still trying to get docker sorted, but I read through the code and added some comments. I'll verify that stuff works tomorrow. |
|
@6a68 I didn't write most of this stuff dealing with BroadcastChannel, so I mostly wanted to see if it works for our purposes. I don't really have my head totally wrapped around what all it does, so some patches / advice are welcome - because you probably know way more than I do :) |
| this.pingListeners.add(callback); | ||
|
|
||
| if (this.pingListeners.size >= 0) { | ||
| this.addonBroadcastChannel.addEventListener('message', this); |
There was a problem hiding this comment.
Confused by this ...
When the WebExtensionChannel has pingListener, the code here adds this as the message event listener. Does something automatically send/forward/bubble the message event to this.handleEvent, which actually calls notifyPing?
There was a problem hiding this comment.
@groovecoder handleEvent is indeed a less-known, but standard, DOM API that I've seen used in Gecko code quite a bit:
https://developer.mozilla.org/en-US/docs/Web/API/EventListener
https://dxr.mozilla.org/mozilla-central/search?q=handleEvent&redirect=false
bb6f723 to
0c1b4b7
Compare
|
Updated to close BroadcastChannel while disposing the WebExtensionChannel |
0c1b4b7 to
156c9bc
Compare
|
Could have sworn that this all worked when I tried it locally. But, looking at the event handling, I'm not sure how it did... hmm. Updated with a new commit to add/remove event handler with a bound function. Haven't tried this locally yet because meetings, but pushing my changes to the PR so other folks can look. |
|
YES! 🎉 |
|
I think the last open issue might be what @6a68 raised about creating the DOM window pointer in the WebExtension's origin. If that's more a nit than a gotcha, we can merge this for next push Good thing about this approach is we should be able to extend it to support @chuckharmston's new variant testing framework in WebExtensions, next. |
| this.addonChromeWebNav = addonChromeWebNav; | ||
| this.addonBroadcastChannel = addonBroadcastChannel; | ||
|
|
||
| this.handleEventBound = ev => this.handleEvent(ev); |
There was a problem hiding this comment.
This seems weird to me. I'll look into it this evening.
There was a problem hiding this comment.
I could probably just do this.handleEvent.bind(this), but thought this looked clearer. I think the add/remove event methods need a consistent bound function ref to deal with. Though I did think handleEvent and passing this was enough.
There was a problem hiding this comment.
I agree, there's probably something fishy here: the BroadcastChannel implementation of add/removeEventListener just wraps the default implementation. That said, it does work and it's readable, so I think we're fine to just leave it and move on.
…Pilot - New lib/webextension-channels module to manage channels for every enabled experiment - Each "channel" creates a background page in the WebExtension's origin and listens for BroadcastChannel messages from the WebExtension - Example WebExtension that uses the BroadcastChannel Fixes mozilla#433
156c9bc to
7738ed4
Compare
|
@lmorchard I think this is good to land. I ran out of time doing server setup and wasn't able to get the addons communicating, but it sounds like you and @groovecoder both saw it working, so it's probably fine. The channel creation snippet seems sane, and the rest of the code LGTM. 🍻 |
|
Seems like we've beaten this enough to merge it, so I'll go ahead & do that... now |

enabled experiment
and listens for BroadcastChannel messages from the WebExtension
Fixes #433