Electrolysis/e10s test tips

From MozillaWiki
Jump to: navigation, search

There isn't a standard formula to use to fix broken tests for e10s. Instead, one has to read the test and look for potential problem areas. Sometimes, it's pretty obvious (there are a lot of browser-chrome tests that use contentWindow or contentDocument). Other times it's much more subtle.

Here are a few tips to look for.

About CPOWs

CPOWs are allowed in tests but tend to be a little flaky (search for "dead CPOW" in bugzilla) so it's probably worth spending a little extra time to use message passing and setting things up "correctly".

Mochitest (plain)

  • Setting prefs
    • Prefs should only be set using SpecialPowers (pushPrefEnv).
    • Getting prefs is fine.
  • Setting permissions
    • SpecialPowers.addPermission() is racy, so use SpecialPowers.pushPermissions() instead.
  • Observers:
    • Only receive observer notifications for the content process (possibly OK).
    • SpecialPowers.addObserver does the Right Thing (TM).
  • Services
    • Some services are only available in the parent process (form history, window watcher, etc).
    • SpecialPowers.loadChromeScript allows you to have a "worker" in the parent process with which to communicate.
      • One nice thing for porting synchronous tests is that the chrome script is run synchronously.
      • Since bug 1251139, you can pass a function to run in the parent instead of a URL (since data URIs don't work with loadChromeScript).
      • bug 1153128 added sendSyncMessage to the return value of loadChromeScript. Currently its sendAsyncMessage method is sync even though it's labelled as Async (bug 1251141). Please use the appropriately named function for what you need.
    • SpecialPowers.importInMainProcess("resource://gre/modules/SomeService.jsm") will call Cu.import in the main process with the given URI, which is useful when your test needs to start some main process service.
  • SpecialPowers.wrap
    • Sometimes OK (getting window utils, controller, etc.)
    • Sometimes not (getting the chrome window).
    • Fixing might require something like loadChromeScript.
  • SpecialPowers.Cc
    • Big red flag. Especially for windowwatcher!

Browser-chrome

Most issues with tests will be in browser-chrome tests. Some non browser-chrome tests will need to be converted into browser-chrome tests where a child process context is available.

You can run a browser-chrome test in a multi-process environment with:

 mach mochitest --e10s <testname>

BrowserTestUtils

The BrowserTestUtils object is imported automatically into browser-chrome tests. It contains various utilities that make writing browser tests easier and they work in single process mode as well. These include:

  • Use openNewForegroundTab or withNewTab. These (optionally) wait for a url to load in a new tab and for it to be switched to. Don't try to roll your own loading method if possible.
  • Use switchTab to change tabs. Don't use focus events for this as extraneous events can occur.
  • Use browserLoaded to wait for a url to load in a page. This should work for all non-about page URLs.
  • Use waitForEvent is a convenient helper to wait for an event to fire once.

All of the methods return Promises, so it is convenient to write tests using add_task and the yield keyword. For example:

 add_task(function* () {
   let tab = yield openNewForegroundTab(gBrowser, "http://www.example.com");
   ...
 }

Look at BrowserTestUtils.jsm for additional methods and documentation.

There are lots of examples of the use of BrowserTestUtils in tests in the tree.

If you have a test that is failing intermittently, look to ensure that BrowserTestUtils is used to open, load and switch tabs. While you do, remove any custom loading and tab switching or focusing code in tests and use BrowserTestUtils instead. Sometimes this will be located in a head.js file, shared between tests in a directory.

The browser loading methods don't tend to work well with some cases such as error pages or "about:blank" URLs. You may instead just use waitForEvent(browser, "load"), or in some cases, just pass false for the third argument to not wait for the url to load.

Synthesizing mouse events

EventUtils.synthesizeMouse should be used only for events fired at the parent process as it takes a node as an argument. If this was a child process, this would be a CPOW object. Instead, BrowserTestUtils contains a different synthesizeMouse that takes a selector, and the browser element. This variation will contact the child process which will retrieve the event target by passing the selector to querySelector. Then, synthesizeMouse will be invoked within the child process. This version of synthesizeMouse will resolve when the operation is complete.

One caveat is that BrowserTestUtils.synthesizeMouse currently only synthesizes the mouse in the child process; the real control flow would be to process the mouse event in the parent process and retarget to the child as necessary. This can cause some subtle differences if the child isn't focused. Most tests won't be affects by this. Bug 1240052 is about this issue.

Note that we don't currently have equivalent functions for mousewheel events or touch events. Once these are available, we can convert and fix tests that use these events.

Synthesizing key events

You can use EventUtils.synthesizeKey as normal. Similar for sendChar.

Some existing tests add the window argument:

 EventUtils.synthesizeKey(key, {}, browser.contentWindow);

This is actually written incorrectly as the window argument shouldn't be supplied (it should always be a top-level window). To fix this CPOW usage, just remove the window argument.

There is a BrowserTestUtils.sendChar that resolves when the keypress operation is complete, although not all tests need this. It also synthesizes the event in the child process, with the same issue as synthesizeMouse, so for now, make sure the content window is focused first.

ContentTask

ContentTask is a useful object when you want to retrieve information from the child process or perform an operation in the child process, without having to do message management yourself, and also avoids using unsafe wrappers. It is imported automatically in browser-chrome tests.

 let text = yield ContentTask.spawn(browser, "link", function* (arg) {
   // do something
   return content.document.getElementById(arg).textContent;
 }

This example retrieves the text content of a node in the child process page and returns it to the parent test. In this case "link" is passed through the argument to the child and used in a getElementById call.

Remember to yield from ContentTask.spawn so that the test correctly waits for the task to complete.

You can also just use the Assert.jsm testing functions directly within the ContentTask body, which is useful if you want to check multiple results or perform multiple steps. For example:

 yield ContentTask.spawn(browser, "link", function* (arg) {
   Assert.ok(String(content.document.getSelection()), "Hello");
 }

A ContentTask should not return nodes or other complex objects; instead return strings, arrays and so on. If the ContentTask body returns a Promise, the result will not be returned until it has resolved.

A ContentTask works the same in single-process mode so you can use the same code.

Focus

There is a separate focus manager in each process. When a child process is focused, the parent's focus manager will have its current focus on on that child's corresponding <browser> element. You can check document.activeElement in the parent process for this (or Services.focus.focusedElement). An existing test may be trying to retrieve Services.focus.focusedElement or using the commandDispatcher and these usages may need to be changed. To retrieve the focused element in a child process, use a ContentTask and check it from within there.

SimpleTest.waitForFocus and SimpleTest.promiseFocus also accept a <browser> element as the second argument. This can be used to focus a child tab and wait for it to focused.

Preferences

Use SpecialPowers.pushPrefEnv to set preferences. When this is used, modified preferences will automatically get reset when the test has finished running, avoiding the need to reset them manually using a registerCleanupFunction. Other ways of setting preferences also don't work in a multi-process environment.

loadFrameScript

You can also use browser.messageManager.loadFrameScript("data:,(" + fn.toString() + ")()") to load a script and execute it in the child process. In addition, you can use the message manager to pass messages from the child to the parent and vice versa. Newer tests tend to use ContentTask instead for better readability, but there are some specific cases where message passing may be needed. Note: the opposite of loadFrameScript is SpecialPowers.loadChromeScript but you need to pass a URL or function, not a data URI.

browser.contentWindow/contentDocument

Avoid accessing the contentWindow or the contentDocument from the test. These are CPOW objects that are often unsafe to use, and slower. Sometimes tests will work with these, but usage is strongly discouraged. It is however better to re-enable a test that works with these for better test coverage than to leave the test disabled.

Many test fixes will involve removing usage of these that access child objects within the parent process. To replace usages, use the techniques listed above, such as using a ContentTask.