Bug 1038620 - Add --nested_oop option to mach test commands, r=ted.mielczarek
authorKershaw Chang <kechang@mozilla.com>
Tue, 13 Jan 2015 02:07:00 +0100
changeset 223584 f397b355946761ebc137d080166ab09642ec00f8
parent 223583 b5300132d105323848af8323e21a1314c885b44f
child 223585 2ecbda2b89b03fd3f3ee748c13bd7d796213c0f7
push id28098
push userkwierso@gmail.com
push dateWed, 14 Jan 2015 00:52:19 +0000
treeherdermozilla-central@e978b8bc5c45 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersted.mielczarek
bugs1038620
milestone38.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1038620 - Add --nested_oop option to mach test commands, r=ted.mielczarek
testing/mochitest/jar.mn
testing/mochitest/mach_commands.py
testing/mochitest/mochitest_options.py
testing/mochitest/moz.build
testing/mochitest/nested_setup.js
testing/mochitest/runtests.py
testing/mochitest/server.js
testing/specialpowers/content/specialpowers.js
testing/specialpowers/content/specialpowersAPI.js
--- a/testing/mochitest/jar.mn
+++ b/testing/mochitest/jar.mn
@@ -10,16 +10,17 @@ mochikit.jar:
   content/cc-analyzer.js (cc-analyzer.js)
   content/chrome-harness.js (chrome-harness.js)
   content/mochitest-e10s-utils.js (mochitest-e10s-utils.js)
   content/harness.xul (harness.xul)
   content/redirect.html (redirect.html)
   content/server.js (server.js)
   content/chunkifyTests.js (chunkifyTests.js)
   content/manifestLibrary.js (manifestLibrary.js)
+  content/nested_setup.js (nested_setup.js)
   content/dynamic/getMyDirectory.sjs (dynamic/getMyDirectory.sjs)
   content/static/harness.css (static/harness.css)
   content/tests/SimpleTest/ChromePowers.js (tests/SimpleTest/ChromePowers.js)
   content/tests/SimpleTest/EventUtils.js (tests/SimpleTest/EventUtils.js)
   content/tests/SimpleTest/ChromeUtils.js (tests/SimpleTest/ChromeUtils.js)
   content/tests/SimpleTest/LogController.js (tests/SimpleTest/LogController.js)
   content/tests/SimpleTest/MemoryStats.js (tests/SimpleTest/MemoryStats.js)
   content/tests/SimpleTest/MozillaLogger.js (../specialpowers/content/MozillaLogger.js)
--- a/testing/mochitest/mach_commands.py
+++ b/testing/mochitest/mach_commands.py
@@ -190,17 +190,17 @@ class MochitestRunner(MozbuildObject):
         options.chrome = chrome
         return mochitest.run_remote_mochitests(parser, options)
 
     def run_desktop_test(self, context, suite=None, test_paths=None, debugger=None,
         debugger_args=None, slowscript=False, screenshot_on_fail = False, shuffle=False, closure_behaviour='auto',
         rerun_failures=False, no_autorun=False, repeat=0, run_until_failure=False,
         slow=False, chunk_by_dir=0, total_chunks=None, this_chunk=None, extraPrefs=[],
         jsdebugger=False, debug_on_failure=False, start_at=None, end_at=None,
-        e10s=False, strict_content_sandbox=False, dmd=False, dump_output_directory=None,
+        e10s=False, strict_content_sandbox=False, nested_oop=False, dmd=False, dump_output_directory=None,
         dump_about_memory_after_test=False, dump_dmd_after_test=False,
         install_extension=None, quiet=False, environment=[], app_override=None, bisectChunk=None, runByDir=False,
         useTestMediaDevices=False, timeout=None, **kwargs):
         """Runs a mochitest.
 
         test_paths are path to tests. They can be a relative path from the
         top source directory, an absolute filename, or a directory containing
         test files.
@@ -313,16 +313,17 @@ class MochitestRunner(MozbuildObject):
         options.totalChunks = total_chunks
         options.thisChunk = this_chunk
         options.jsdebugger = jsdebugger
         options.debugOnFailure = debug_on_failure
         options.startAt = start_at
         options.endAt = end_at
         options.e10s = e10s
         options.strictContentSandbox = strict_content_sandbox
+        options.nested_oop = nested_oop
         options.dumpAboutMemoryAfterTest = dump_about_memory_after_test
         options.dumpDMDAfterTest = dump_dmd_after_test
         options.dumpOutputDirectory = dump_output_directory
         options.quiet = quiet
         options.environment = environment
         options.extraPrefs = extraPrefs
         options.bisectChunk = bisectChunk
         options.runByDir = runByDir
@@ -507,16 +508,20 @@ def MochitestCommand(func):
     e10s = CommandArgument('--e10s', action='store_true',
         help='Run tests with electrolysis preferences and test filtering enabled.')
     func = e10s(func)
 
     strict_content_sandbox = CommandArgument('--strict-content-sandbox', action='store_true',
         help='Run tests with a more strict content sandbox (Windows only).')
     func = strict_content_sandbox(func)
 
+    this_chunk = CommandArgument('--nested_oop', action='store_true',
+        help='Run tests with nested oop preferences and test filtering enabled.')
+    func = this_chunk(func)
+
     dmd = CommandArgument('--dmd', action='store_true',
         help='Run tests with DMD active.')
     func = dmd(func)
 
     dumpAboutMemory = CommandArgument('--dump-about-memory-after-test', action='store_true',
         help='Dump an about:memory log after every test.')
     func = dumpAboutMemory(func)
 
--- a/testing/mochitest/mochitest_options.py
+++ b/testing/mochitest/mochitest_options.py
@@ -371,16 +371,22 @@ class MochitestOptions(optparse.OptionPa
           "help": "Run tests with electrolysis preferences and test filtering enabled.",
         }],
         [["--strict-content-sandbox"],
         { "action": "store_true",
           "default": False,
           "dest": "strictContentSandbox",
           "help": "Run tests with a more strict content sandbox (Windows only).",
         }],
+        [["--nested_oop"],
+        { "action": "store_true",
+          "default": False,
+          "dest": "nested_oop",
+          "help": "Run tests with nested_oop preferences and test filtering enabled.",
+        }],
         [["--dmd-path"],
          { "action": "store",
            "default": None,
            "dest": "dmdPath",
            "help": "Specifies the path to the directory containing the shared library for DMD.",
         }],
         [["--dump-output-directory"],
          { "action": "store",
@@ -483,16 +489,17 @@ class MochitestOptions(optparse.OptionPa
             self.add_option(*option, **value)
         self.set_usage(self.__doc__)
 
     def verifyOptions(self, options, mochitest):
         """ verify correct options and cleanup paths """
 
         mozinfo.update({"e10s": options.e10s}) # for test manifest parsing.
         mozinfo.update({"strictContentSandbox": options.strictContentSandbox}) # for test manifest parsing.
+        mozinfo.update({"nested_oop": options.nested_oop}) # for test manifest parsing.
 
         if options.app is None:
             if build_obj is not None:
                 options.app = build_obj.get_binary_path()
             else:
                 self.error("could not find the application path, --appname must be specified")
 
         if options.totalChunks is not None and options.thisChunk is None:
@@ -637,16 +644,20 @@ class MochitestOptions(optparse.OptionPa
 
         if options.useTestMediaDevices:
             if not mozinfo.isLinux:
                 self.error('--use-test-media-devices is only supported on Linux currently')
             for f in ['/usr/bin/gst-launch-0.10', '/usr/bin/pactl']:
                 if not os.path.isfile(f):
                     self.error('Missing binary %s required for --use-test-media-devices')
 
+        if options.nested_oop:
+          if not options.e10s:
+            options.e10s = True
+
         options.leakThresholds = {
             "default": options.defaultLeakThreshold,
             "tab": 2000000, # See dependencies of bug 1051230.
             "geckomediaplugin": 20000, # GMP rarely gets a log, but when it does, it leaks a little.
         }
 
         # Bug 1051230 - Leak logging does not yet work for tab processes on desktop.
         # Bug 1065098 - The geckomediaplugin process fails to produce a leak log for some reason.
--- a/testing/mochitest/moz.build
+++ b/testing/mochitest/moz.build
@@ -56,16 +56,17 @@ TEST_HARNESS_FILES.testing.mochitest += 
     'harness.xul',
     'jetpack-addon-harness.js',
     'jetpack-addon-overlay.xul',
     'jetpack-package-harness.js',
     'jetpack-package-overlay.xul',
     'manifest.webapp',
     'manifestLibrary.js',
     'mochitest_options.py',
+    'nested_setup.js',
     'pywebsocket_wrapper.py',
     'redirect.html',
     'runtests.py',
     'runtestsb2g.py',
     'runtestsremote.py',
     'server.js',
 ]
 
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/nested_setup.js
@@ -0,0 +1,31 @@
+
+var gTestURL = '';
+
+function addPermissions()
+{
+  SpecialPowers.pushPermissions(
+    [{ type: "browser", allow: true, context: document }],
+    addPreferences);
+}
+
+function addPreferences()
+{
+  SpecialPowers.pushPrefEnv(
+    {"set": [["dom.mozBrowserFramesEnabled", true]]},
+    insertFrame);
+}
+
+function insertFrame()
+{
+  SpecialPowers.nestedFrameSetup();
+
+  var iframe = document.createElement('iframe');
+  iframe.id = 'nested-parent-frame';
+  iframe.width = "100%";
+  iframe.height = "100%";
+  iframe.scoring = "no";
+  iframe.setAttribute("remote", "true");
+  iframe.setAttribute("mozbrowser", "true");
+  iframe.src = gTestURL;
+  document.getElementById("holder-div").appendChild(iframe);
+}
\ No newline at end of file
--- a/testing/mochitest/runtests.py
+++ b/testing/mochitest/runtests.py
@@ -433,16 +433,17 @@ class MochitestUtilsMixin(object):
   #      on top of mozbase. Each of the functions in here should
   #      probably live somewhere in mozbase
 
   oldcwd = os.getcwd()
   jarDir = 'mochijar'
 
   # Path to the test script on the server
   TEST_PATH = "tests"
+  NESTED_OOP_TEST_PATH = "nested_oop"
   CHROME_PATH = "redirect.html"
   urlOpts = []
   log = None
 
   def __init__(self, logger_options):
     self.update_mozinfo()
     self.server = None
     self.wsserver = None
@@ -664,16 +665,18 @@ class MochitestUtilsMixin(object):
     testPath = self.getTestPath(options)
     testURL = "/".join([testHost, self.TEST_PATH, testPath])
     if os.path.isfile(os.path.join(self.oldcwd, os.path.dirname(__file__), self.TEST_PATH, testPath)) and options.repeat > 0:
       testURL = "/".join([testHost, self.TEST_PATH, os.path.dirname(testPath)])
     if options.chrome or options.a11y:
       testURL = "/".join([testHost, self.CHROME_PATH])
     elif options.browserChrome or options.jetpackPackage or options.jetpackAddon:
       testURL = "about:blank"
+    if options.nested_oop:
+      testURL = "/".join([testHost, self.NESTED_OOP_TEST_PATH])
     return testURL
 
   def buildTestPath(self, options, testsToFilter=None, disabled=True):
     """ Build the url path to the specific test harness and test file or directory
         Build a manifest of tests to run and write out a json file for the harness to read
         testsToFilter option is used to filter/keep the tests provided in the list
 
         disabled -- This allows to add all disabled tests on the build side
@@ -1178,16 +1181,17 @@ class Mochitest(MochitestUtilsMixin):
 
   def buildProfile(self, options):
     """ create the profile and add optional chrome bits and files if requested """
     if options.browserChrome and options.timeout:
       options.extraPrefs.append("testing.browserTestHarness.timeout=%d" % options.timeout)
     options.extraPrefs.append("browser.tabs.remote.autostart=%s" % ('true' if options.e10s else 'false'))
     if options.strictContentSandbox:
         options.extraPrefs.append("security.sandbox.windows.content.moreStrict=true")
+    options.extraPrefs.append("dom.ipc.tabs.nested.enabled=%s" % ('true' if options.nested_oop else 'false'))
 
     # get extensions to install
     extensions = self.getExtensionsToInstall(options)
 
     # web apps
     appsPath = os.path.join(SCRIPT_DIR, 'profile_data', 'webapps_mochitest.json')
     if os.path.exists(appsPath):
       with open(appsPath) as apps_file:
--- a/testing/mochitest/server.js
+++ b/testing/mochitest/server.js
@@ -204,16 +204,17 @@ function runServer()
 /** Creates and returns an HTTP server configured to serve Mochitests. */
 function createMochitestServer(serverBasePath)
 {
   var server = new nsHttpServer();
 
   server.registerDirectory("/", serverBasePath);
   server.registerPathHandler("/server/shutdown", serverShutdown);
   server.registerPathHandler("/server/debug", serverDebug);
+  server.registerPathHandler("/nested_oop", nestedTest);
   server.registerContentType("sjs", "sjs"); // .sjs == CGI-like functionality
   server.registerContentType("jar", "application/x-jar");
   server.registerContentType("ogg", "application/ogg");
   server.registerContentType("pdf", "application/pdf");
   server.registerContentType("ogv", "video/ogg");
   server.registerContentType("oga", "audio/ogg");
   server.registerContentType("opus", "audio/ogg; codecs=opus");
   server.registerContentType("dat", "text/plain; charset=utf-8");
@@ -598,16 +599,41 @@ function convertManifestToTestLinks(root
                                                                manifestStream.available()));
   var paths = manifestObj.tests;
   var pathPrefix = '/' + root + '/'
   return [paths.reduce(function(t, p) { t[pathPrefix + p.path] = true; return t; }, {}),
           paths.length];
 }
 
 /**
+ * Produce a test harness page that has one remote iframe
+ */
+function nestedTest(metadata, response)
+{
+  response.setStatusLine("1.1", 200, "OK");
+  response.setHeader("Content-type", "text/html;charset=utf-8", false);
+  response.write(
+    HTML(
+      HEAD(
+        TITLE("Mochitest | ", metadata.path),
+        LINK({rel: "stylesheet",
+              type: "text/css", href: "/static/harness.css"}),
+        SCRIPT({type: "text/javascript",
+                src: "/nested_setup.js"}),
+        SCRIPT({type: "text/javascript"},
+               "window.onload = addPermissions; gTestURL = '/tests?" + metadata.queryString + "';")
+        ),
+      BODY(
+        DIV({class: "container"},
+          DIV({class: "frameholder", id: "holder-div"})
+        )
+        )));
+}
+
+/**
  * Produce a test harness page containing all the test cases
  * below it, recursively.
  */
 function testListing(metadata, response)
 {
   var links = {};
   var count = 0;
   if (metadata.queryString.indexOf('manifestFile') == -1) {
--- a/testing/specialpowers/content/specialpowers.js
+++ b/testing/specialpowers/content/specialpowers.js
@@ -18,16 +18,30 @@ function SpecialPowers(window) {
       configurable: true, enumerable: true, get: function() {
           var win = this.window.get();
           if (!win)
               return null;
           return getRawComponents(win);
       }});
   this._pongHandlers = [];
   this._messageListener = this._messageReceived.bind(this);
+  this._grandChildFrameMM = null;
+  this.SP_SYNC_MESSAGES = ["SPChromeScriptMessage",
+                           "SPLoadChromeScript",
+                           "SPObserverService",
+                           "SPPermissionManager",
+                           "SPPrefService",
+                           "SPProcessCrashService",
+                           "SPSetTestPluginEnabledState",
+                           "SPWebAppService"];
+
+  this.SP_ASYNC_MESSAGES = ["SpecialPowers.Focus",
+                            "SpecialPowers.Quit",
+                            "SPPingService",
+                            "SPQuotaManager"];
   addMessageListener("SPPingService", this._messageListener);
   let self = this;
   Services.obs.addObserver(function onInnerWindowDestroyed(subject, topic, data) {
     var id = subject.QueryInterface(Components.interfaces.nsISupportsPRUint64).data;
     if (self._windowID === id) {
       Services.obs.removeObserver(onInnerWindowDestroyed, "inner-window-destroyed");
       try {
         removeMessageListener("SPPingService", self._messageListener);
@@ -44,25 +58,32 @@ SpecialPowers.prototype = new SpecialPow
 SpecialPowers.prototype.toString = function() { return "[SpecialPowers]"; };
 SpecialPowers.prototype.sanityCheck = function() { return "foo"; };
 
 // This gets filled in in the constructor.
 SpecialPowers.prototype.DOMWindowUtils = undefined;
 SpecialPowers.prototype.Components = undefined;
 
 SpecialPowers.prototype._sendSyncMessage = function(msgname, msg) {
+  if (this.SP_SYNC_MESSAGES.indexOf(msgname) == -1) {
+    dump("TEST-INFO | specialpowers.js |  Unexpected SP message: " + msgname + "\n");
+  }
   return sendSyncMessage(msgname, msg);
 };
 
 SpecialPowers.prototype._sendAsyncMessage = function(msgname, msg) {
+  if (this.SP_ASYNC_MESSAGES.indexOf(msgname) == -1) {
+    dump("TEST-INFO | specialpowers.js |  Unexpected SP message: " + msgname + "\n");
+  }
   sendAsyncMessage(msgname, msg);
 };
 
 SpecialPowers.prototype._addMessageListener = function(msgname, listener) {
   addMessageListener(msgname, listener);
+  sendAsyncMessage("SPPAddNestedMessageListener", { name: msgname });
 };
 
 SpecialPowers.prototype._removeMessageListener = function(msgname, listener) {
   removeMessageListener(msgname, listener);
 };
 
 SpecialPowers.prototype.registerProcessCrashObservers = function() {
   addMessageListener("SPProcessCrashService", this._messageListener);
@@ -85,31 +106,72 @@ SpecialPowers.prototype._messageReceived
       break;
 
     case "SPPingService":
       if (aMessage.json.op == "pong") {
         var handler = this._pongHandlers.shift();
         if (handler) {
           handler();
         }
+        if (this._grandChildFrameMM) {
+          this._grandChildFrameMM.sendAsyncMessage("SPPingService", { op: "pong" });
+        }
       }
       break;
   }
   return true;
 };
 
 SpecialPowers.prototype.quit = function() {
   sendAsyncMessage("SpecialPowers.Quit", {});
 };
 
 SpecialPowers.prototype.executeAfterFlushingMessageQueue = function(aCallback) {
   this._pongHandlers.push(aCallback);
   sendAsyncMessage("SPPingService", { op: "ping" });
 };
 
+SpecialPowers.prototype.nestedFrameSetup = function() {
+  let self = this;
+  Services.obs.addObserver(function onRemoteBrowserShown(subject, topic, data) {
+    let frameLoader = subject;
+    // get a ref to the app <iframe>
+    frameLoader.QueryInterface(Components.interfaces.nsIFrameLoader);
+    let frame = frameLoader.ownerElement;
+    let frameId = frame.getAttribute('id');
+    if (frameId === "nested-parent-frame") {
+      Services.obs.removeObserver(onRemoteBrowserShown, "remote-browser-shown");
+
+      let mm = frame.QueryInterface(Components.interfaces.nsIFrameLoaderOwner).frameLoader.messageManager;
+      self._grandChildFrameMM = mm;
+
+      self.SP_SYNC_MESSAGES.forEach(function (msgname) {
+        mm.addMessageListener(msgname, function (msg) {
+          return self._sendSyncMessage(msgname, msg.data)[0];
+        });
+      });
+      self.SP_ASYNC_MESSAGES.forEach(function (msgname) {
+        mm.addMessageListener(msgname, function (msg) {
+          self._sendAsyncMessage(msgname, msg.data);
+        });
+      });
+      mm.addMessageListener("SPPAddNestedMessageListener", function(msg) {
+        self._addMessageListener(msg.json.name, function(aMsg) {
+          mm.sendAsyncMessage(aMsg.name, aMsg.data);
+          });
+      });
+
+      let specialPowersBase = "chrome://specialpowers/content/";
+      mm.loadFrameScript(specialPowersBase + "MozillaLogger.js", false);
+      mm.loadFrameScript(specialPowersBase + "specialpowersAPI.js", false);
+      mm.loadFrameScript(specialPowersBase + "specialpowers.js", false);
+    }
+  }, "remote-browser-shown", false);
+};
+
 // Attach our API to the window.
 function attachSpecialPowersToWindow(aWindow) {
   try {
     if ((aWindow !== null) &&
         (aWindow !== undefined) &&
         (aWindow.wrappedJSObject) &&
         !(aWindow.wrappedJSObject.SpecialPowers)) {
       aWindow.wrappedJSObject.SpecialPowers = new SpecialPowers(aWindow);
--- a/testing/specialpowers/content/specialpowersAPI.js
+++ b/testing/specialpowers/content/specialpowersAPI.js
@@ -208,18 +208,19 @@ function unwrapPrivileged(x) {
   // we can on an unwrapped object.
   if (!isWrappable(x))
     return x;
 
   // If we have a wrappable type, make sure it's wrapped.
   if (!isWrapper(x))
     throw "Trying to unwrap a non-wrapped object!";
 
-  // Unwrap.
-  return x.SpecialPowers_wrappedObject;
+  var obj = x.SpecialPowers_wrappedObject;
+  // unwrapped.
+  return obj;
 };
 
 function crawlProtoChain(obj, fn) {
   var rv = fn(obj);
   if (rv !== undefined)
     return rv;
   // Follow the prototype chain of the underlying object in cases where it differs
   // from the Xray prototype chain. This is important for things like Opaque Xray
@@ -657,20 +658,20 @@ SpecialPowersAPI.prototype = {
                                                       : Components;
   },
 
   /*
    * Convenient shortcuts to the standard Components abbreviations. Note that
    * we don't SpecialPowers-wrap Components.interfaces, because it's available
    * to untrusted content, and wrapping it confuses QI and identity checks.
    */
-  get Cc() { return wrapPrivileged(this.getFullComponents()).classes; },
+  get Cc() { return wrapPrivileged(this.getFullComponents().classes); },
   get Ci() { return this.Components.interfaces; },
-  get Cu() { return wrapPrivileged(this.getFullComponents()).utils; },
-  get Cr() { return wrapPrivileged(this.Components).results; },
+  get Cu() { return wrapPrivileged(this.getFullComponents().utils); },
+  get Cr() { return wrapPrivileged(this.Components.results); },
 
   /*
    * SpecialPowers.getRawComponents() allows content to get a reference to a
    * naked (and, in certain automation configurations, privileged) Components
    * object for its scope.
    *
    * SpecialPowers.getRawComponents(window) is defined as the global property
    * window.SpecialPowers.Components for convenience.