Bug 728893 - Allow mochitest iframe to go fullscreen

Bug 728893 was encountered while implementing PointerLock.

A Quick Overview on Mochitests

By default an iframe is not allowed to go fullscreen mode, and since all the mochitests are run inside and iframe, if one of the mochitest needed to go fullscreen it would not work (pointerlock for example)

There are two different ways to run mochitests. With or mochitest-plain(ends up running

  1. python ./obj-dir/_tests/testing/mochitest/ –test-path=dom/tests/mochitest/pointerlock
  2. TEST_PATH=/dom/test/mochitest/pointerlock make -C obj-dir/ mochitest-plain

The first one calls explicit the, that is the script that creates the http server
The second one will run mochitest-plain.
Now what is mochitest-plain?
First we need to look at the root’s Makefile
Line 129 is including, that’s where mochitest-plain is defined

127 ifdef ENABLE_TESTS
128 # Additional makefile targets to call automated test suites
129 include $(topsrcdir)/testing/
130 endif

Here is where mochitest-plain is defined, it calls $(RUN_MOCHITEST)

128 mochitest-plain:
130 $(CHECK

In the end is ran with some default flags

65 rm -f ./$@.log &&
66 $(PYTHON) _tests/testing/mochitest/ –autorun –close-when-done
67 –console-level=INFO –log-file=./$@.log –file-level=INFO
68 –failure-file=$(call core

Running the tests with mochitest-plain will add some flags to the execution of

[diogogmt@rome mozilla-central-diogogmt]$ TESTPATH=dom/tests/mochitest/pointerlock/ make -C ff-dbg/ mochitest-plain
make: Entering directory `/home/diogogmt/mozilla-central-diogogmt/ff-dbg’
rm -f ./mochitest-plain.log && /usr/bin/python2.7 _tests/testing/mochitest/ –autorun **–close-when-done**–console-level=INFO –log-file=./mochitest-plain.log –file-level=INFO –failure-file=/home/diogogmt/mozilla-central-diogogmt/ff-dbg/
tests/testing/mochitest/makefailures.json –symbols-path=./dist/crashreporter-symbols –test-path=dom/tests/mochitest/pointerlock/

The flag –close-when-done is set by default. However sometimes you may want to leave the browser window open for debug purposes. I’m not sure if there is a way to tell mochitest-plain not run the with the –close-when-done flag not set. What I usually do is run the tests calling directly, since it runs withouth any flags, giving me more flexbility.

When not specifying a file, the mochitest will load all the files of the dir and run them inside the harness

For example:
The mochitest Makefile contains the list of the dirs of some tests

45 DIRS +=
46 dom-level0
47 dom-level1-core
48 dom-level2-core
49 dom-level2-html
50 ajax
51 bugs
52 chrome
53 general
54 whatwg
55 geolocation
56 localstorage
57 orientation
58 sessionstorage
59 storageevent
60 $(NULL

In the geolocation Makefile all the tests for that module are listed

41 relativesrcdir = dom/tests/mochitest/geolocation
43 include $(DEPTH)/config/
45 include $(topsrcdir)/config/
48 testallowCurrent.html
49 test
50 testcancelCurrent.html
51 test
52 testclearWatch.html
53 test
54 test
55 testmanyCurrentSerial.html
56 test
57 testmanyWatchSerial.html
58 test
59 testoptionalapiparams.html
60 test
61 testwindowClose.html
62 test
63 testworseAccuracyDoesNotBlockCallback.html
64 geolocation.html
65 geolocation
66 networkgeolocation.sjs
67 windowTest.html
68 $(NULL)
70 libs:: $(
71 $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/

From line 48 to 67 are listed all the test files
Line 41 is defined the path where the files are located
Line 71 is the path where the files will be copied after compiling firefox, for example:

Running single tests
When running the test as a stand alone, there is no need for an iframe, the test is loaded in the parent window, so the element is allowed to go fullscreen, different from when the tests are run inside the harness.

The Problem

When we first started writing mochitests for pointerlock we had to find a way to run the tests inside the harness while setting them to fullscreen.
The solution was to use the same approach as the fullscreen tests. To create a harness inside the harness, for example


The solution was simple, we just had to add the attribute mozallowfullscreen=true to the mochitest iframe.
However, there are three places where the iframe gets defined:

server.js is used for mochitest-plain
browser_harness.xul is used for mochitest-chrome and mochitest-browser-chrome
plain-loop remains a mystery :P

For server.js:

 IFRAME({scrolling: "no", id: "testframe", width: "500", height: "300", mozallowfullscreen: "true"})  

For browser_harness.xul

 <iframe id="testframe" scrolling="no" width="550" height="350"></iframe>  

You can find the complete patch here

Sanity tests

To make sure the mochitest framework works, before running all the tests a list of sanity tests is executed to test the framework itself.
You can see the list of the tests in the /testing/mochitest/tests/

43 relativesrcdir = testing/mochitest/tests
45 include $(DEPTH)/config/
48 MochiKit-1.4.2
49 SimpleTest
50 browser
51 $(NULL)
53 include $(topsrcdir)/config/
55 _TEST
56 testsanity.html
57 test
58 testsanityException2.html
59 test
60 testSpecialPowersExtension.html
61 test
62 fileSpecialPowersFrame1.html
63 $(NULL)
65 ifneq ($(OS
66 # Disabled on Android for permaorange, see bug 688052
68 testsanityEventUtils.html
69 test
70 endif
71 # Copy the sanity tests into a subdirectory, so the top level is all dirs
72 # in the test screen.
73 libs:: $(TESTFILES)
74 $(INSTALL) $(foreach f,$^,"$f") $(DEPTH)/tests/$(relativesrcdir)/Harnesssanity

One thing to notice is the folder the sanity tests are copied to: Harness_sanity

After Firefox is compiled, all the tests are copied to /obj-dir/_tests/testing/mochitest/tests
So to run the geolocation tests for example:

  • TEST_PATH=dom/tests/mochitest/geolocation

The Harness_sanity on the other hand:

  • TESTPATH=Harnesssanity

Since allowing the iframe to go fullscreen mode could break other tests we decided to add a check on SimpleTest.finish() to cancel fullscreen mode if the test forgets to cancel it

 + // Cancel element fullscreen mode due to Bug 728893  
 + if (document && document.mozFullScreenElement) {  
 + document.mozCancelFullScreen();  
 + }  

So now we needed a test to make sure SimpleTest.finish was actually cancelling fullscreen.
My first thought was to have two test files, testfullscreen1.html and testfullscreen2.html. On testfullscreen1.html the element would be set to fullscreen mode and without cancelling call SimpleTest.finish(), then on testfullscreen2.html would check the fullscreen status of the iframe, it should be false since SimpleTest.finish() supposed to cancel it.
However, there was an easier way to write the test, with just one file instead of two.



 + /** Test for Bug 728893  
 + Checks if SimpleTest.finish cancels fullscreen mode  
 + The assertion happens after calling SimpleTest.finish  
 + Running the test without the harness won’t work  
 + **/  
 + SpecialPowers.setBoolPref("full-screen-api.enabled", true);  
 + SpecialPowers.setBoolPref("full-screen-api.allow-trusted-requests-only",  
 + false);  
 + SimpleTest.waitForExplicitFinish();  
 + var div = document.getElementById("div");  
 + document.addEventListener("mozfullscreenchange", function (e) {  
 + if (document.mozFullScreenElement === div) {  
 + SimpleTest.finish();  
 + }  
 + else {  
 + is(false, document.mozFullScreen,  
 + "SimpleTest.finish should cancel fullscreen mode");  
 + }  
 + }, false);  
 + function start() {  
 + SimpleTest.waitForFocus(function() {  
 + div.mozRequestFullScreen();  
 + });  
 + }  


The assertion “is(false, document.mozFullScreen, “SimpleTest.finish should cancel fullscreen mode”);” is happening after SimpleTest.finish is called, the reason that it is possible to still make assertions after calling SimpleTest.finish is because the test itself is being run inside the mochitest harness


 678 if (parentRunner) {  
 679 /* We’re running in an iframe, and the parent has a TestRunner */  
 680 parentRunner.testFinished(SimpleTest._tests);  
 681 } else {  
 682 SimpleTest.showReport();  
 683 }  

Line 678 checks if the test is being run inside a harness, if true it goes to the next test, but it doesn’t terminates the current one.
Different from line 682 that call showReport, terminating the current test and displaying the results.

**Big thans to Clint(ctalbert) for all the help with this bug

PointerLock API Updates

A quick update on the Firefox PointerLock API implementation

Lets start with mochitests. While writing mochitests for pointerlock we stumbled on two problems

  1. Not being able to specify how many tests should run (different platforms were running different number of tests)
  2. Mochitest iframe not allowed to go fullscreen, making us run all the tests on a different window

David Humphrey came up with a solution for our first problem and added an “expect” functionality to the mochitest framework.
So now we can specify how many tests should occur when making asynchronous tests, for example:
Bug 724578

For our second problem, I added the attribute mozallowfullscreen=true to the mochitest iframe that runs all tests.
I’m not sure if there was a specific reason for not allowing fullscreen on the mochitest iframe, but if it wasn’t it will simplify a lot writing tests for pointerlock
Bug 728893

Spec Updates

The spec had two major changes

  1. Switching from callbacks to events
  2. Moving functionality to the Document and Element

For example:
Everytime the pointer is locked/unlocked a mozpointerchange event will be dispatched to the document
A mozpointererror event will be dispatched if there are any errors while locking the pointer
Now It’s possible to access the element with the pointer locked via the document

 var div = document.createElement("div");

document.addEventListener("mozpointerlockchange", function (e) {  
 if (document.mozPointerLockElement === div) {  
 // Pointer is locked  
 }, false);

document.addEventListener("mozpointerlockerror", function (e) {

}, false);

document.addEventListener("mozfullscreenchange", function (e) {  
 if (document.mozFullScreen &&  
 document.mozFullScreenElement === div) {  
 }, false);


Instead of something like this:

 var div = document.createElement("div");

div.addEventListener("mozpointerlocklost", function (e) {  
 // Dispatched when pointer is unlocked  
 }, false);

document.addEventListener("mozfullscreenchange", function (e) {  
 if (document.mozFullScreen &&  
 document.mozFullScreenElement === div) {  
 div, // Element  
 function () {  
 // Success callback  
 function () {  
 // Failure callback  
 }, false);