Embedding (flash) video players via UserScript (GM 2.2 / FF32+)
(continued...)
That *works*, but I think you'll agree, the security sandbox doesn't make it easy.
Have I missed another solution in the MUCH EASIER category?
(Yes, I'm aware that I could use window.postMesage() instead of window.dispatchEvent(), but it's not structurally *simpler* as far as I can tell -- ie: that method seems to have the same sandbox security issues).
Have you tried unsafeWindow? The usage changed in GM 2.0
https://blog.mozilla.org/addons/2014/04/10/changes-to-unsafewindow-for-the-add-on-sdk/
@Couchy said:
Have you tried unsafeWindow? The usage changed in GM 2.0
I *had* been using unsafeWindow prior to Firefox 32.0 on Scriptish, but had never gotten it to work on GM 2.0+. I was using the bi-directional form that has since been deprecated in Firefox 32.0. Scriptish is now broken as hell.
If I understand correctly, the remaining unsafeWindow functionality let addon content reach into the page arbitrarily, but bans all but primitive property resolution from the page into addon content.
The newer way for addon content to give page-content access to privileged code is to use 'cloneInto', 'exportFunction' and 'Components.utils.createObjectIn'. They talk about 'structural cloning' of data/functions... not sure how that's going to work with event binding.
That means I can probably cut down the SCRIPT tag injection stuff a bit, but I'm still unclear on how the Flash-JavaScript bridge (used by SFW players) is supposed to take advantage of this.
I don't think I can avoid the four-argument form of addEventListener and the serialisation required for that.
Can you clarify how you think it could work?
Ok, I've done some testing.
Neither of 'cloneInto' and 'Components.utils.createObjectIn' are helpful because the "structural cloning" is essentially just serialisation so you can't close over privileged code/variables from object defined using those APIs.
'exportFunction' is much more interesting (and could have saved me a *lot* of time in some of my previous addon work)... but it has severe limitations. UserScript functions "exported" to the page:
* don't survive async transitions (setTimeout, setImmediate, requestAnimationFrom, etc).
* don't work at all for dispatching events (still need to serialise/unserialise)
* are NOT naiively callable by the Flash-JavaScript bridge (something fails in property name resolution).
Looks like the GreaseMonkey developers will have to do a *lot* of work to restore this kind of functionality.
Doing a bunch more research, it seems like the way I'm doing it is actually sane. (i.e: there are well recognised limits on what can be structurally cloned).
The advice being given by very smart people is to inject your whole script into the page (running unprivilileged) and only export a small set of privileged functions (that can call GM_*) into the page to be used by your script. Until mozilla (and then GM) provide an easier way, the most general mechanism (that works for everything) is dispatchEvent and/or postMessage. For the "privileged" exports, that requires serialisation (JSON) of the messages between contexts.
I found a much more organised/generalised library for this approach using requirejs:
Embedding (flash) video players via UserScript (GM 2.2 / FF32+)
Since discovering that the folks at dailymotion have (intentionally or not) un-deprecated access to their older (lighter, and arguably more capable) flash-based player API (which was pretty much a clone of YouTube's) -- I eagerly started trying to port my Dailymotion: "Playback Quality Control" UserScript to the old API.
And there I ran into trouble straight away... I'm not sure if embedding the NEW player API via GM *ever* worked security-wise because I had been inadvertently testing on Scriptish only. In any case, the upshot is that most of the JavaScript machinery used by the OLD player API runs afoul of the FF32+ security changes.
Basically, when using the OLD Flash API, you use SWFobject to inject
into the page with a URI parameter (playerId) that tells Flash which DOM element will be *replaced* when the object is instantiated (with the same "id"). It then calls a presumed window-global function
to inform the host page that the Flash object is ready to take API calls (via JavaScript).
Ordinarily, this global (name-based) callback is just inconvenient for encapsulation, but not too difficult to deal with.
Via GM 2.2 / FF32+ however, it seems there are a few more hurdles. If you load SWFobject via UserScript require
and embed the Flash player like
then you immediately run into sandbox security errors if 'window.onDailymotionPlayerReady' is defined from the UserScript and closes over UserScript-defined variables/functions. (Sometimes the errors are extremely terse NSIComponent "failure" chrome-level errors that are very hard to debug unless you recognise them from FF addon development).
The obvious (to me) solution is to have the UserScript inject a SCRIPT tag into the body of the "unsafe" window like so:
The CustomEvent dispatch is pretty standard stuff, except that the "detail" is JSON stringified to avoid sandbox security errors (in t his case because it closes over "playerId" defined in the "unsafe" window scope).
You then need to use the four-argument form of EventTarget.addEventListener to "listen" for those events on the UserScript side:
The fourth 'true' (boolean) argument says "yes, please listen to events from unsafe contexts", and the event handlers also need to JSON.parse the event.detail field.
Nothing earth shattering there, but what about *calling* the Flash object's JS API functions from the UserScript side? Yep, same sandbox security errors. :-(
So.... we do the same in reverse. The following is added to the SCRIPT tag injected into the "unsafe" window's DOM:
... where "callers" expecting a response also pass a "ticket" via event.detail; a single-use unique value per call that allow's the dma.commandReturn event to be matched to the caller's callback function -- which has been registered in advance on the UserScript side:
(continuing in next post)