Bug 755036 - Re-use the execution sandbox in marionette's executeScript and executeAsyncScript in chrome scope when requested. r=jgriffin
authorChris Manchester <cmanchester@mozilla.com>
Wed, 07 Jan 2015 14:52:07 -0500
changeset 248586 470bc082ec71f90bb1a5cff5c874db14ce95563e
parent 248585 238eeecce596d5158217b64a5ddda4a96918a66b
child 248587 0e842f11691faf166baeae55584aa4b9daed45c0
push id4489
push userraliiev@mozilla.com
push dateMon, 23 Feb 2015 15:17:55 +0000
treeherdermozilla-beta@fd7c3dc24146 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjgriffin
bugs755036
milestone37.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 755036 - Re-use the execution sandbox in marionette's executeScript and executeAsyncScript in chrome scope when requested. r=jgriffin
testing/marionette/client/marionette/tests/unit/test_execute_async_script.py
testing/marionette/client/marionette/tests/unit/test_execute_script.py
testing/marionette/marionette-server.js
--- a/testing/marionette/client/marionette/tests/unit/test_execute_async_script.py
+++ b/testing/marionette/client/marionette/tests/unit/test_execute_async_script.py
@@ -99,19 +99,13 @@ marionetteScriptFinished(4);
 class TestExecuteAsyncChrome(TestExecuteAsyncContent):
     def setUp(self):
         super(TestExecuteAsyncChrome, self).setUp()
         self.marionette.set_context("chrome")
 
     def test_execute_async_unload(self):
         pass
 
-    def test_same_context(self):
-        pass
-
     def test_execute_permission(self):
         self.assertEqual(5, self.marionette.execute_async_script("""
 var c = Components.classes;
 marionetteScriptFinished(5);
 """))
-
-    def test_sandbox_reuse(self):
-        pass
--- a/testing/marionette/client/marionette/tests/unit/test_execute_script.py
+++ b/testing/marionette/client/marionette/tests/unit/test_execute_script.py
@@ -99,11 +99,8 @@ class TestExecuteChrome(TestExecuteConte
         self.assertEqual(1, self.marionette.execute_script(
             "var c = Components.classes; return 1;"))
 
     def test_unmarshal_element_collection(self):
         expected = self.marionette.find_elements(By.TAG_NAME, "textbox")
         actual = self.marionette.execute_script(
             "return document.querySelectorAll('textbox')")
         self.assertEqual(expected, actual)
-
-    def test_sandbox_reuse(self):
-        pass
--- a/testing/marionette/marionette-server.js
+++ b/testing/marionette/marionette-server.js
@@ -158,16 +158,17 @@ function MarionetteServerConnection(aPre
   this.mainFrame = null; //topmost chrome frame
   this.curFrame = null; // chrome iframe that currently has focus
   this.mainContentFrameId = null;
   this.importedScripts = FileUtils.getFile('TmpD', ['marionetteChromeScripts']);
   this.importedScriptHashes = {"chrome" : [], "content": []};
   this.currentFrameElement = null;
   this.testName = null;
   this.mozBrowserClose = null;
+  this.sandbox = null;
   this.oopFrameId = null; // frame ID of current remote frame, used for mozbrowserclose events
   this.sessionCapabilities = {
     // Mandated capabilities
     "browserName": appName,
     "browserVersion": Services.appinfo.version,
     "platformName": Services.appinfo.OS.toUpperCase(),
     "platformVersion": Services.appinfo.platformVersion,
 
@@ -781,16 +782,17 @@ MarionetteServerConnection.prototype = {
     }
     catch(e) {
       this.sendError(e.message, e.code, e.stack, command_id);
       return;
     }
 
     let _chromeSandbox = new Cu.Sandbox(aWindow,
        { sandboxPrototype: aWindow, wantXrays: false, sandboxName: ''});
+    _chromeSandbox.global = _chromeSandbox;
     _chromeSandbox.__namedArgs = this.curBrowser.elementManager.applyNamedArgs(args);
     _chromeSandbox.__marionetteParams = args;
     _chromeSandbox.testUtils = utils;
 
     marionette.exports.forEach(function(fn) {
       try {
         _chromeSandbox[fn] = marionette[fn].bind(marionette);
       }
@@ -871,27 +873,28 @@ MarionetteServerConnection.prototype = {
    *        if true, it will be run directly and not as a
    *        function body
    */
   execute: function MDA_execute(aRequest, directInject) {
     let inactivityTimeout = aRequest.parameters.inactivityTimeout;
     let timeout = aRequest.parameters.scriptTimeout ? aRequest.parameters.scriptTimeout : this.scriptTimeout;
     let command_id = this.command_id = this.getCommandId();
     let script;
-    if (aRequest.parameters.newSandbox == undefined) {
+    let newSandbox = aRequest.parameters.newSandbox;
+    if (newSandbox == undefined) {
       //if client does not send a value in newSandbox,
       //then they expect the same behaviour as webdriver
-      aRequest.parameters.newSandbox = true;
+      newSandbox = true;
     }
     if (this.context == "content") {
       this.sendAsync("executeScript",
                      {
                        script: aRequest.parameters.script,
                        args: aRequest.parameters.args,
-                       newSandbox: aRequest.parameters.newSandbox,
+                       newSandbox: newSandbox,
                        timeout: timeout,
                        specialPowers: aRequest.parameters.specialPowers,
                        filename: aRequest.parameters.filename,
                        line: aRequest.parameters.line
                      },
                      command_id);
       return;
     }
@@ -914,46 +917,49 @@ MarionetteServerConnection.prototype = {
      }
      setTimer();
      this.heartbeatCallback = function resetInactivityTimer() {
       that.inactivityTimer.cancel();
       setTimer();
      }
     }
 
-    let curWindow = this.getCurrentWindow();
-    let marionette = new Marionette(this, curWindow, "chrome",
-                                    this.marionetteLog,
-                                    timeout, this.heartbeatCallback, this.testName);
-    let _chromeSandbox = this.createExecuteSandbox(curWindow,
-                                                   marionette,
-                                                   aRequest.parameters.args,
-                                                   aRequest.parameters.specialPowers,
-                                                   command_id);
-    if (!_chromeSandbox)
-      return;
+
+    if (!this.sandbox || newSandbox) {
+      let curWindow = this.getCurrentWindow();
+      let marionette = new Marionette(this, curWindow, "chrome",
+                                      this.marionetteLog,
+                                      timeout, this.heartbeatCallback, this.testName);
+      this.sandbox = this.createExecuteSandbox(curWindow,
+                                               marionette,
+                                               aRequest.parameters.args,
+                                               aRequest.parameters.specialPowers,
+                                               command_id);
+      if (!this.sandbox)
+        return;
+    }
 
     try {
-      _chromeSandbox.finish = function chromeSandbox_finish() {
+      this.sandbox.finish = function chromeSandbox_finish() {
         if (that.inactivityTimer != null) {
           that.inactivityTimer.cancel();
         }
-        return marionette.generate_results();
+        return that.sandbox.generate_results();
       };
 
       if (directInject) {
         script = aRequest.parameters.script;
       }
       else {
         script = "let func = function() {" +
                        aRequest.parameters.script +
                      "};" +
                      "func.apply(null, __marionetteParams);";
       }
-      this.executeScriptInSandbox(_chromeSandbox, script, directInject,
+      this.executeScriptInSandbox(this.sandbox, script, directInject,
                                   false, command_id, timeout);
     }
     catch (e) {
       let error = createStackMessage(e,
                                      "execute_script",
                                      aRequest.parameters.filename,
                                      aRequest.parameters.line,
                                      script);
@@ -1037,29 +1043,30 @@ MarionetteServerConnection.prototype = {
    *        if true, it will be run directly and not as a
    *        function body
    */
   executeWithCallback: function MDA_executeWithCallback(aRequest, directInject) {
     let inactivityTimeout = aRequest.parameters.inactivityTimeout;
     let timeout = aRequest.parameters.scriptTimeout ? aRequest.parameters.scriptTimeout : this.scriptTimeout;
     let command_id = this.command_id = this.getCommandId();
     let script;
-    if (aRequest.parameters.newSandbox == undefined) {
+    let newSandbox = aRequest.parameters.newSandbox;
+    if (newSandbox == undefined) {
       //if client does not send a value in newSandbox,
       //then they expect the same behaviour as webdriver
-      aRequest.parameters.newSandbox = true;
+      newSandbox = true;
     }
 
     if (this.context == "content") {
       this.sendAsync("executeAsyncScript",
                      {
                        script: aRequest.parameters.script,
                        args: aRequest.parameters.args,
                        id: this.command_id,
-                       newSandbox: aRequest.parameters.newSandbox,
+                       newSandbox: newSandbox,
                        timeout: timeout,
                        inactivityTimeout: inactivityTimeout,
                        specialPowers: aRequest.parameters.specialPowers,
                        filename: aRequest.parameters.filename,
                        line: aRequest.parameters.line
                      },
                      command_id);
       return;
@@ -1083,93 +1090,96 @@ MarionetteServerConnection.prototype = {
        }, inactivityTimeout, Ci.nsITimer.TYPE_ONE_SHOT);
       }
      }
     }
 
     let curWindow = this.getCurrentWindow();
     let original_onerror = curWindow.onerror;
     that.timeout = timeout;
-    let marionette = new Marionette(this, curWindow, "chrome",
-                                    this.marionetteLog,
-                                    timeout, this.heartbeatCallback, this.testName);
-    marionette.command_id = this.command_id;
 
     function chromeAsyncReturnFunc(value, status, stacktrace) {
       if (that._emu_cbs && Object.keys(that._emu_cbs).length) {
         value = "Emulator callback still pending when finish() called";
         status = 500;
         that._emu_cbs = null;
       }
 
       if (value == undefined)
         value = null;
-      if (that.command_id == marionette.command_id) {
+
+      if (command_id == that.command_id) {
         if (that.timer != null) {
           that.timer.cancel();
           that.timer = null;
         }
 
         curWindow.onerror = original_onerror;
 
         if (status == 0 || status == undefined) {
           that.sendToClient({from: that.actorID, value: that.curBrowser.elementManager.wrapValue(value), status: status},
-                            marionette.command_id);
+                            that.command_id);
         }
         else {
           let error_msg = {message: value, status: status, stacktrace: stacktrace};
           that.sendToClient({from: that.actorID, error: error_msg},
-                            marionette.command_id);
+                            that.command_id);
         }
       }
+
       if (that.inactivityTimer != null) {
         that.inactivityTimer.cancel();
       }
     }
 
     curWindow.onerror = function (errorMsg, url, lineNumber) {
       chromeAsyncReturnFunc(errorMsg + " at: " + url + " line: " + lineNumber, 17);
       return true;
     };
 
     function chromeAsyncFinish() {
-      chromeAsyncReturnFunc(marionette.generate_results(), 0);
+      chromeAsyncReturnFunc(that.sandbox.generate_results(), 0);
     }
 
-    let _chromeSandbox = this.createExecuteSandbox(curWindow,
-                                                   marionette,
-                                                   aRequest.parameters.args,
-                                                   aRequest.parameters.specialPowers,
-                                                   command_id);
-    if (!_chromeSandbox)
-      return;
+    if (!this.sandbox || newSandbox) {
+      let marionette = new Marionette(this, curWindow, "chrome",
+                                      this.marionetteLog,
+                                      timeout, this.heartbeatCallback, this.testName);
+      this.sandbox = this.createExecuteSandbox(curWindow,
+                                               marionette,
+                                               aRequest.parameters.args,
+                                               aRequest.parameters.specialPowers,
+                                               command_id);
+      if (!this.sandbox)
+        return;
+    }
 
     try {
 
       this.timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
       if (this.timer != null) {
         this.timer.initWithCallback(function() {
           chromeAsyncReturnFunc("timed out", 28);
         }, that.timeout, Ci.nsITimer.TYPE_ONE_SHOT);
       }
 
-      _chromeSandbox.returnFunc = chromeAsyncReturnFunc;
-      _chromeSandbox.finish = chromeAsyncFinish;
+      this.sandbox.returnFunc = chromeAsyncReturnFunc;
+      this.sandbox.finish = chromeAsyncFinish;
 
       if (directInject) {
         script = aRequest.parameters.script;
       }
       else {
         script =  '__marionetteParams.push(returnFunc);'
                 + 'let marionetteScriptFinished = returnFunc;'
                 + 'let __marionetteFunc = function() {' + aRequest.parameters.script + '};'
                 + '__marionetteFunc.apply(null, __marionetteParams);';
       }
 
-      this.executeScriptInSandbox(_chromeSandbox, script, directInject,
+      this.executeScriptInSandbox(this.sandbox, script, directInject,
                                   true, command_id, timeout);
     } catch (e) {
       let error = createStackMessage(e,
                                      "execute_async_script",
                                      aRequest.parameters.filename,
                                      aRequest.parameters.line,
                                      script);
       chromeAsyncReturnFunc(error[0], 17, error[1]);
@@ -1494,16 +1504,18 @@ MarionetteServerConnection.prototype = {
    */
   switchToWindow: function MDA_switchToWindow(aRequest) {
     let command_id = this.command_id = this.getCommandId();
 
     let checkWindow = function (win, outerId, contentWindowId, ind) {
       if (aRequest.parameters.name == win.name ||
           aRequest.parameters.name == contentWindowId ||
           aRequest.parameters.name == outerId) {
+        // As in content, switching to a new window invalidates a sandbox for reuse.
+        this.sandbox = null;
         if (this.browsers[outerId] === undefined) {
           //enable Marionette in that browser window
           this.startBrowser(win, false);
         } else {
           utils.window = win;
           this.curBrowser = this.browsers[outerId];
           if (contentWindowId) {
             // The updated id corresponds to switching to a new tab.