Closed Bug 914633 Opened 11 years ago Closed 11 years ago

Ease chrome interaction in mochitest-plain

Categories

(Testing :: Mochitest, defect)

defect
Not set
normal

Tracking

(Not tracked)

RESOLVED FIXED
mozilla26

People

(Reporter: ochameau, Assigned: ochameau)

References

(Depends on 1 open bug)

Details

Attachments

(2 files, 2 obsolete files)

Often, when writting a mochitest-plain, you need some limited and shared piece of chrome code. So far, the practice has been to add some specifics to SpecialPowers, like setAllAppsLaunchable, autoConfirmAppInstall, addAutoCompletePopupEventListener, attachFormFillControllerTo, getMaxLineBoxWidth ... and so on.

Each of these methods are specific to one particular kind of tests. That doesn't really scale.

One alternative, is to use Cc and Ci exposed by SpecialPowers, but then you get some proxy wrappers that introduce many hard to debug and unexpected issues.

So for the sake of testing properly webapps actor and after many iterations I ended up implementing an easy way to evaluate chrome scripts and communicate with them from the mochitest.

### mochitest.html

// Load a javascript file with chrome priviledges
// and instanciate a communication channel with it
var script = SpecialPowers.loadChromeScript(window, "myChromeScript.js");

// Listen for messages sent by the chrome script
script.addMessageListener("bar", function (message) {
  // message == { bar: "foo" }
  script.destroy();
  SimpleTest.finish();
});

// Send a message to the chrome script
script.sendAsyncMessage("from-mochitest-to-chrome", {foo: "bar"});

### myChromeScript.js
// Listen for message sent by the mochitest
addMessageListener("from-mochitest-to-chrome", function (message) {
  // Send a message to the mochitest
  sendAsyncMessage("bar", {bar: "foo"});
});


And the very cool thing is that as it uses messageManager and asynchronous messages, it does even work on b2g!!
Attached patch Implement SpecialPowers.loadChromeScript (obsolete) (deleted) β€” β€” Splinter Review
Attachment #802309 - Flags: review?(jmaher)
Blocks: 914604
That ends up being easier passing an absolute URL to SpecialPowers.loadChromeScript.
(Instead of letting it call SimpleTest.getTestFile) as specialpowersAPI doesn't have
access to SimpleTest.
Attachment #802309 - Attachment is obsolete: true
Attachment #802309 - Flags: review?(jmaher)
Attachment #802360 - Flags: review?(jmaher)
So instead of:
var script = SpecialPowers.loadChromeScript(window, "myChromeScript.js");

we should now do:
var url = SimpleTest.getTestFileURL("myChromeScript.js");
var script = SpecialPowers.loadChromeScript(url);
This is an interesting idea. My only worry is that by creating a new type of chrome scope specifically for this it people will be a bit confused about what they can or can't do in that scope.
I've sometimes used SpecialPowers.Cu to create a chrome-privileged sandbox. This looks similar, but with a better API.

I don't think it'll be too confusing though. It looks nicely sandboxed, so it's basically just an async channel to a chrome actor that can't otherwise interact with the content scope.
Comment on attachment 802360 [details] [diff] [review]
Switched from SpecialPowers.getTestFile to SimpleTest.getTestFile.

Bobby, would you be able to review this patch? (Joel told me he isn't confident reviewing this one)

(I'm trying to get this patch and the related ones in F26, so I'm looking forward a review sometimes soon :o)
Attachment #802360 - Flags: review?(jmaher) → review?(bobbyholley+bmo)
Comment on attachment 802360 [details] [diff] [review]
Switched from SpecialPowers.getTestFile to SimpleTest.getTestFile.

Review of attachment 802360 [details] [diff] [review]:
-----------------------------------------------------------------

The Sandbox stuff looks good. For the rest of the API design though I think we need an automation peer. Flagging ted.

I think it would also be nice to give the chrome scripts a convenience mechanism to call ok() and is() (perhaps asynchronously over the message channel).
Attachment #802360 - Flags: review?(ted)
Attachment #802360 - Flags: review?(bobbyholley+bmo)
Attachment #802360 - Flags: feedback+
(In reply to Bobby Holley (:bholley) from comment #7)
> Comment on attachment 802360 [details] [diff] [review]
> Switched from SpecialPowers.getTestFile to SimpleTest.getTestFile.
> 
> Review of attachment 802360 [details] [diff] [review]:
> -----------------------------------------------------------------
> I think it would also be nice to give the chrome scripts a convenience
> mechanism to call ok() and is() (perhaps asynchronously over the message
> channel).

I was tempted to do that in that patch, but I didn't. I first wanted to address the issue of SpecialPowers being polluted by more and more test specific helpers. I'd be happy to add such additional helper, for me it is common to have to test both content and chrome sides at the same time.
(In reply to Ted Mielczarek [:ted.mielczarek] from comment #4)
> This is an interesting idea. My only worry is that by creating a new type of
> chrome scope specifically for this it people will be a bit confused about
> what they can or can't do in that scope.

In its current shape, it is mostly to stop adding helpers directly into SpecialPowers.
But as Bobby highlighted, it can also be a handy way to execute and also do some sanity checks on chrome side, while being compatible with b2g and OOP platforms.

Otherwise, the context of these sandboxes is quite simple. It is an empty chrome sandbox, living in parent process, that has regular access to Components.* and only two specifics: addMessageListener and sendAsyncMessage.
Comment on attachment 802360 [details] [diff] [review]
Switched from SpecialPowers.getTestFile to SimpleTest.getTestFile.

Review of attachment 802360 [details] [diff] [review]:
-----------------------------------------------------------------

This mostly looks good, I just have a few nits and a qustion.

::: testing/specialpowers/content/SpecialPowersObserverAPI.js
@@ +295,5 @@
> +                                 .getService(Ci.nsIScriptableInputStream);
> +        var channel = Services.io.newChannel(url, null, null);
> +        var input = channel.open();
> +        scriptableStream.init(input);
> +        var jsScript = scriptableStream.read(input.available());

Yuck, sync I/O. :-(

@@ +330,5 @@
> +        var name = aMessage.json.name;
> +        var message = aMessage.json.message;
> +        this._chromeScriptListeners
> +            .filter(function (o) o.name == name && o.id == id)
> +            .forEach(function (o) o.listener(message));

=> ?

::: testing/specialpowers/content/specialpowersAPI.js
@@ +513,5 @@
> +      },
> +
> +      removeMessageListener: (name, listener) => {
> +        listeners = listeners.filter(
> +          function (o) (o.name != name || o.listener != listener)

Any reason you didn't use a => here as well?

@@ +518,5 @@
> +        );
> +      },
> +
> +      sendAsyncMessage: (name, message) => {
> +        this._sendSyncMessage("SPChromeScriptMessage",

How is "this" the right thing here? Don't you need to be saving it in a different name from above?

@@ +524,5 @@
> +      },
> +
> +      destroy: () => {
> +        listeners = [];
> +        this._removeMessageListener("SPChromeScriptMessage", chromeScript);

Also the "this" here.

@@ +536,5 @@
> +        if (messageId != id)
> +          return;
> +
> +        listeners.filter(function (o) o.name == name)
> +                 .forEach(function (o) o.listener(this.wrap(message)),

=> here?
Attachment #802360 - Flags: review?(ted)
Attached patch interdiff (deleted) β€” β€” Splinter Review
Attached patch Convert all closures to arrow functions. (deleted) β€” β€” Splinter Review
Attachment #802360 - Attachment is obsolete: true
(In reply to Ted Mielczarek [:ted.mielczarek] from comment #10)
> Comment on attachment 802360 [details] [diff] [review]
> Switched from SpecialPowers.getTestFile to SimpleTest.getTestFile.
> 
> Review of attachment 802360 [details] [diff] [review]:
> -----------------------------------------------------------------
> 
> This mostly looks good, I just have a few nits and a qustion.
> 
> ::: testing/specialpowers/content/SpecialPowersObserverAPI.js
> @@ +295,5 @@
> > +                                 .getService(Ci.nsIScriptableInputStream);
> > +        var channel = Services.io.newChannel(url, null, null);
> > +        var input = channel.open();
> > +        scriptableStream.init(input);
> > +        var jsScript = scriptableStream.read(input.available());
> 
> Yuck, sync I/O. :-(

I kept it, otherwise I would have to complexify the final API.
I assume that when I call SpecialPowers.loadChromeScript, I can immediatly send messages to it.
If we load the script asynchronously, we would either had to save incoming messages received during script load, or complexify the API by dispatching a "ready" or "load" event, that would have to be listen for before sending any message.
If that's something you feel very wrong, would you be ok going for the "save incoming messages during load" in a followup?

> 
> ::: testing/specialpowers/content/specialpowersAPI.js
> @@ +513,5 @@
> > +      },
> > +
> > +      removeMessageListener: (name, listener) => {
> > +        listeners = listeners.filter(
> > +          function (o) (o.name != name || o.listener != listener)
> 
> Any reason you didn't use a => here as well?

No particular reason, I tend to use arrow function mainly when we need to bind its `this`.
(see next comment)

> 
> @@ +518,5 @@
> > +        );
> > +      },
> > +
> > +      sendAsyncMessage: (name, message) => {
> > +        this._sendSyncMessage("SPChromeScriptMessage",
> 
> How is "this" the right thing here? Don't you need to be saving it in a
> different name from above?

Arrow functions aren't just about an even shorter way to write closures,
it also ease this typical usecase. For each arrow function, its `this` is automagically
binded to the current lexical scope `this`.
So `() => {}` is similar to `(function () {}).bind(this)`
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/arrow_functions#Lexical_this
So that, here, `this` from `this._sendSyncMessage` will be equal to the SpecialPowerAPI instance.
Attachment #804367 - Flags: review?(ted)
Comment on attachment 804367 [details] [diff] [review]
Convert all closures to arrow functions.

Review of attachment 804367 [details] [diff] [review]:
-----------------------------------------------------------------

I didn't know that about arrow functions. Nice!

http://stream1.gifsoup.com/view/204031/the-more-you-know-o.gif

You don't have to fix the sync I/O problem, it's just a visceral reaction. This is test-only code, it's not the end of the world.
Attachment #804367 - Flags: review?(ted) → review+
Thanks for the fast review cycles!

A try run, just to be sure before landing:
https://tbpl.mozilla.org/?tree=Try&rev=24c677b706da
Keywords: checkin-needed
https://hg.mozilla.org/integration/fx-team/rev/986c3e80530c
Flags: in-testsuite+
Keywords: checkin-needed
Whiteboard: [fixed-in-fx-team]
https://hg.mozilla.org/mozilla-central/rev/986c3e80530c
Status: NEW → RESOLVED
Closed: 11 years ago
Resolution: --- → FIXED
Whiteboard: [fixed-in-fx-team]
Target Milestone: --- → mozilla26
I would love to use loadChromeScript for bug 871323, but I need a reference to the window in the chrome script. That doesn't seem possible, is there a way to have that supported?
Never mind, found a solution.
Blocks: 965257
Depends on: 1251139
Depends on: 1251141
You need to log in before you can comment on or make changes to this bug.

Attachment

General

Created:
Updated:
Size: