From 8fa8928a940f1f93634e3ddc82538cd02b92683e Mon Sep 17 00:00:00 2001 From: Philipp von Weitershausen Date: Wed, 9 May 2012 12:05:39 -0700 Subject: [PATCH] Bug 751783 - Allow sandbox reuse between execute_script calls, r=jgriffin, r=mdas, DONTBUILD because NPOTB, --- .../client/marionette/marionette.py | 21 ++- .../tests/unit/test_execute_async_script.py | 22 ++- .../tests/unit/test_execute_script.py | 9 ++ testing/marionette/marionette-actors.js | 23 ++- testing/marionette/marionette-listener.js | 150 +++++++++++------- testing/marionette/marionette-simpletest.js | 5 +- 6 files changed, 160 insertions(+), 70 deletions(-) diff --git a/testing/marionette/client/marionette/marionette.py b/testing/marionette/client/marionette/marionette.py index da37302b4cf9..9756e74e7cc8 100644 --- a/testing/marionette/client/marionette/marionette.py +++ b/testing/marionette/client/marionette/marionette.py @@ -313,7 +313,7 @@ class Marionette(object): return unwrapped - def execute_js_script(self, script, script_args=None, timeout=True): + def execute_js_script(self, script, script_args=None, timeout=True, new_sandbox=True): if script_args is None: script_args = [] args = self.wrapArguments(script_args) @@ -321,21 +321,30 @@ class Marionette(object): 'value', value=script, args=args, - timeout=timeout) + timeout=timeout, + newSandbox=new_sandbox) return self.unwrapValue(response) - def execute_script(self, script, script_args=None): + def execute_script(self, script, script_args=None, new_sandbox=True): if script_args is None: script_args = [] args = self.wrapArguments(script_args) - response = self._send_message('executeScript', 'value', value=script, args=args) + response = self._send_message('executeScript', + 'value', + value=script, + args=args, + newSandbox=new_sandbox) return self.unwrapValue(response) - def execute_async_script(self, script, script_args=None): + def execute_async_script(self, script, script_args=None, new_sandbox=True): if script_args is None: script_args = [] args = self.wrapArguments(script_args) - response = self._send_message('executeAsyncScript', 'value', value=script, args=args) + response = self._send_message('executeAsyncScript', + 'value', + value=script, + args=args, + newSandbox=new_sandbox) return self.unwrapValue(response) def find_element(self, method, target, id=None): diff --git a/testing/marionette/client/marionette/tests/unit/test_execute_async_script.py b/testing/marionette/client/marionette/tests/unit/test_execute_async_script.py index 0bf2c9260da3..5d8aa974ab50 100644 --- a/testing/marionette/client/marionette/tests/unit/test_execute_async_script.py +++ b/testing/marionette/client/marionette/tests/unit/test_execute_async_script.py @@ -71,11 +71,11 @@ class TestExecuteAsyncContent(MarionetteTestCase): def test_same_context(self): var1 = 'testing' self.assertEqual(self.marionette.execute_script(""" - window.wrappedJSObject._testvar = '%s'; - return window.wrappedJSObject._testvar; + this.testvar = '%s'; + return this.testvar; """ % var1), var1) self.assertEqual(self.marionette.execute_async_script( - "marionetteScriptFinished(window.wrappedJSObject._testvar);"), var1) + "marionetteScriptFinished(this.testvar);", new_sandbox=False), var1) def test_execute_no_return(self): self.assertEqual(self.marionette.execute_async_script("marionetteScriptFinished()"), None) @@ -102,6 +102,19 @@ var c = Components.classes; marionetteScriptFinished(1); """) + def test_sandbox_reuse(self): + # Sandboxes between `execute_script()` invocations are shared. + self.marionette.execute_async_script("this.foobar = [23, 42];" + "marionetteScriptFinished();") + self.assertEqual(self.marionette.execute_async_script( + "marionetteScriptFinished(this.foobar);", new_sandbox=False), [23, 42]) + + self.marionette.execute_async_script("global.barfoo = [42, 23];" + "marionetteScriptFinished();") + self.assertEqual(self.marionette.execute_async_script( + "marionetteScriptFinished(global.barfoo);", new_sandbox=False), [42, 23]) + + class TestExecuteAsyncChrome(TestExecuteAsyncContent): def setUp(self): super(TestExecuteAsyncChrome, self).setUp() @@ -119,3 +132,6 @@ var c = Components.classes; marionetteScriptFinished(1); """)) + def test_sandbox_reuse(self): + pass + diff --git a/testing/marionette/client/marionette/tests/unit/test_execute_script.py b/testing/marionette/client/marionette/tests/unit/test_execute_script.py index a1a6d2abd28a..1f23733f2288 100644 --- a/testing/marionette/client/marionette/tests/unit/test_execute_script.py +++ b/testing/marionette/client/marionette/tests/unit/test_execute_script.py @@ -64,6 +64,13 @@ class TestExecuteContent(MarionetteTestCase): self.assertEqual(self.marionette.execute_script("return {'foo': [1, 'a', 2]};"), {'foo': [1, 'a', 2]}) + def test_sandbox_reuse(self): + # Sandboxes between `execute_script()` invocations are shared. + self.marionette.execute_script("this.foobar = [23, 42];") + self.assertEqual(self.marionette.execute_script("return this.foobar;", new_sandbox=False), [23, 42]) + + self.marionette.execute_script("global.barfoo = [42, 23];") + self.assertEqual(self.marionette.execute_script("return global.barfoo;", new_sandbox=False), [42, 23]) class TestExecuteChrome(TestExecuteContent): def setUp(self): @@ -73,3 +80,5 @@ class TestExecuteChrome(TestExecuteContent): def test_execute_permission(self): self.assertEqual(1, self.marionette.execute_script("var c = Components.classes;return 1;")) + def test_sandbox_reuse(self): + pass diff --git a/testing/marionette/marionette-actors.js b/testing/marionette/marionette-actors.js index e23143da4357..e229b2c8ffc7 100644 --- a/testing/marionette/marionette-actors.js +++ b/testing/marionette/marionette-actors.js @@ -472,8 +472,16 @@ MarionetteDriverActor.prototype = { * function body */ execute: function MDA_execute(aRequest, directInject) { + logger.info("newSandbox: " + aRequest.newSandbox); + if (aRequest.newSandbox == undefined) { + //if client does not send a value in newSandbox, + //then they expect the same behaviour as webdriver + aRequest.newSandbox = true; + } if (this.context == "content") { - this.sendAsync("executeScript", {value: aRequest.value, args: aRequest.args}); + this.sendAsync("executeScript", {value: aRequest.value, + args: aRequest.args, + newSandbox:aRequest.newSandbox}); return; } @@ -533,6 +541,11 @@ MarionetteDriverActor.prototype = { */ executeJSScript: function MDA_executeJSScript(aRequest) { //all pure JS scripts will need to call Marionette.finish() to complete the test. + if (aRequest.newSandbox == undefined) { + //if client does not send a value in newSandbox, + //then they expect the same behaviour as webdriver + aRequest.newSandbox = true; + } if (this.context == "chrome") { if (aRequest.timeout) { this.executeWithCallback(aRequest, aRequest.timeout); @@ -562,12 +575,18 @@ MarionetteDriverActor.prototype = { * function body */ executeWithCallback: function MDA_executeWithCallback(aRequest, directInject) { + if (aRequest.newSandbox == undefined) { + //if client does not send a value in newSandbox, + //then they expect the same behaviour as webdriver + aRequest.newSandbox = true; + } this.command_id = this.uuidGen.generateUUID().toString(); if (this.context == "content") { this.sendAsync("executeAsyncScript", {value: aRequest.value, args: aRequest.args, - id: this.command_id}); + id: this.command_id, + newSandbox: aRequest.newSandbox}); return; } diff --git a/testing/marionette/marionette-listener.js b/testing/marionette/marionette-listener.js index 9828e20aa47e..e0c7c900bdc8 100644 --- a/testing/marionette/marionette-listener.js +++ b/testing/marionette/marionette-listener.js @@ -29,6 +29,15 @@ let activeFrame = null; let curWindow = content; let elementManager = new ElementManager([]); +// The sandbox we execute test scripts in. Gets lazily created in +// createExecuteContentSandbox(). +let sandbox; + +// Flag to indicate whether an async script is currently running or not. +let asyncTestRunning = false; +let asyncTestCommandId; +let asyncTestTimeoutId; + /** * Called when listener is first started up. * The listener sends its unique window ID and its current URI to the actor. @@ -178,6 +187,7 @@ function sendError(message, status, trace, command_id) { * Clear test values after completion of test */ function resetValues() { + sandbox = null; marionetteTimeout = null; curWin = content; } @@ -197,28 +207,54 @@ function errUnload() { /** * Returns a content sandbox that can be used by the execute_foo functions. */ -function createExecuteContentSandbox(aWindow, marionette, args) { - try { - args = elementManager.convertWrappedArguments(args, aWindow); - } - catch(e) { - sendError(e.message, e.num, e.stack); - return; - } - +function createExecuteContentSandbox(aWindow) { let sandbox = new Cu.Sandbox(aWindow); + sandbox.global = sandbox; sandbox.window = aWindow; sandbox.document = sandbox.window.document; sandbox.navigator = sandbox.window.navigator; - sandbox.__namedArgs = elementManager.applyNamedArgs(args); - sandbox.__marionetteParams = args; sandbox.__proto__ = sandbox.window; sandbox.testUtils = utils; + let marionette = new Marionette(false, aWindow, "content", marionetteLogObj); + sandbox.marionette = marionette; marionette.exports.forEach(function(fn) { sandbox[fn] = marionette[fn].bind(marionette); }); + sandbox.asyncComplete = function sandbox_asyncComplete(value, status) { + curWindow.removeEventListener("unload", errUnload, false); + + /* clear all timeouts potentially generated by the script*/ + for (let i = 0; i <= asyncTestTimeoutId; i++) { + curWindow.clearTimeout(i); + } + + sendSyncMessage("Marionette:testLog", + {value: elementManager.wrapValue(marionetteLogObj.getLogs())}); + marionetteLogObj.clearLogs(); + if (status == 0){ + sendResponse({value: elementManager.wrapValue(value), status: status}, asyncTestCommandId); + } + else { + sendError(value, status, null, asyncTestCommandId); + } + + asyncTestRunning = false; + asyncTestTimeoutId = undefined; + asyncTestCommandId = undefined; + }; + sandbox.finish = function sandbox_finish() { + if (asyncTestRunning) { + sandbox.asyncComplete(marionette.generate_results(), 0); + } else { + return marionette.generate_results(); + } + }; + sandbox.marionetteScriptFinished = function sandbox_marionetteScriptFinished(value) { + return sandbox.asyncComplete(value, 0); + }; + return sandbox; } @@ -228,15 +264,14 @@ function createExecuteContentSandbox(aWindow, marionette, args) { */ function executeScript(msg, directInject) { let script = msg.json.value; - let marionette = new Marionette(false, curWindow, "content", marionetteLogObj); - let sandbox = createExecuteContentSandbox(curWindow, marionette, msg.json.args); - if (!sandbox) - return; - - sandbox.finish = function sandbox_finish() { - return marionette.generate_results(); - }; + if (msg.json.newSandbox || !sandbox) { + sandbox = createExecuteContentSandbox(curWindow); + if (!sandbox) { + sendError("Could not create sandbox!"); + return; + } + } try { if (directInject) { @@ -251,6 +286,15 @@ function executeScript(msg, directInject) { } } else { + try { + sandbox.__marionetteParams = elementManager.convertWrappedArguments( + msg.json.args, curWindow); + } + catch(e) { + sendError(e.message, e.num, e.stack); + return; + } + let scriptSrc = "let __marionetteFunc = function(){" + script + "};" + "__marionetteFunc.apply(null, __marionetteParams);"; let res = Cu.evalInSandbox(scriptSrc, sandbox, "1.8"); @@ -302,39 +346,29 @@ function executeJSScript(msg) { function executeWithCallback(msg, timeout) { curWindow.addEventListener("unload", errUnload, false); let script = msg.json.value; - let command_id = msg.json.id; + asyncTestCommandId = msg.json.id; // Error code 28 is scriptTimeout, but spec says execute_async should return 21 (Timeout), // see http://code.google.com/p/selenium/wiki/JsonWireProtocol#/session/:sessionId/execute_async. // However Selenium code returns 28, see // http://code.google.com/p/selenium/source/browse/trunk/javascript/firefox-driver/js/evaluate.js. // We'll stay compatible with the Selenium code. - let timeoutId = curWindow.setTimeout(function() { - contentAsyncReturnFunc('timed out', 28); + asyncTestTimeoutId = curWindow.setTimeout(function() { + sandbox.asyncComplete('timed out', 28); }, marionetteTimeout); curWindow.addEventListener('error', function win__onerror(evt) { curWindow.removeEventListener('error', win__onerror, true); - contentAsyncReturnFunc(evt, 17); + sandbox.asyncComplete(evt, 17); return true; }, true); - function contentAsyncReturnFunc(value, status) { - curWindow.removeEventListener("unload", errUnload, false); - - /* clear all timeouts potentially generated by the script*/ - for(let i=0; i<=timeoutId; i++) { - curWindow.clearTimeout(i); + if (msg.json.newSandbox || !sandbox) { + sandbox = createExecuteContentSandbox(curWindow); + if (!sandbox) { + sendError("Could not create sandbox!"); + return; } - - sendSyncMessage("Marionette:testLog", {value: elementManager.wrapValue(marionetteLogObj.getLogs())}); - marionetteLogObj.clearLogs(); - if (status == 0){ - sendResponse({value: elementManager.wrapValue(value), status: status}, command_id); - } - else { - sendError(value, status, null, command_id); - } - }; + } let scriptSrc; if (timeout) { @@ -344,25 +378,23 @@ function executeWithCallback(msg, timeout) { scriptSrc = script; } else { - scriptSrc = "let marionetteScriptFinished = function(value) { return asyncComplete(value,0);};" + - "__marionetteParams.push(marionetteScriptFinished);" + + try { + sandbox.__marionetteParams = elementManager.convertWrappedArguments( + msg.json.args, curWindow); + } + catch(e) { + sendError(e.message, e.num, e.stack); + return; + } + + scriptSrc = "__marionetteParams.push(marionetteScriptFinished);" + "let __marionetteFunc = function() { " + script + "};" + "__marionetteFunc.apply(null, __marionetteParams); "; } - let marionette = new Marionette(true, curWindow, "content", marionetteLogObj); - - let sandbox = createExecuteContentSandbox(curWindow, marionette, msg.json.args); - if (!sandbox) - return; - - sandbox.asyncComplete = contentAsyncReturnFunc; - sandbox.finish = function sandbox_finish() { - contentAsyncReturnFunc(marionette.generate_results(), 0); - }; - try { - Cu.evalInSandbox(scriptSrc, sandbox, "1.8"); + asyncTestRunning = true; + Cu.evalInSandbox(scriptSrc, sandbox, "1.8"); } catch (e) { // 17 = JavascriptException sendError(e.name + ': ' + e.message, 17, e.stack); @@ -621,13 +653,15 @@ function switchToFrame(msg) { } break; } - if (foundFrame != null) { - curWindow = curWindow.frames[foundFrame]; - curWindow.focus(); - sendOk(); - } else { + if (foundFrame == null) { sendError("Unable to locate frame: " + msg.json.value, 8, null); + return; } + curWindow = curWindow.frames[foundFrame]; + curWindow.focus(); + sendOk(); + + sandbox = null; } //call register self when we get loaded diff --git a/testing/marionette/marionette-simpletest.js b/testing/marionette/marionette-simpletest.js index 8e69add72ea9..b20c063f4438 100644 --- a/testing/marionette/marionette-simpletest.js +++ b/testing/marionette/marionette-simpletest.js @@ -11,10 +11,11 @@ function Marionette(is_async, window, context, logObj) { this.tests = []; this.logObj = logObj; this.context = context; - this.exports = ['ok', 'is', 'isnot', 'log', 'getLogs', 'generate_results', 'waitFor']; } Marionette.prototype = { + exports: ['ok', 'is', 'isnot', 'log', 'getLogs', 'generate_results', 'waitFor'], + ok: function Marionette__ok(condition, name, diag) { let test = {'result': !!condition, 'name': name, 'diag': diag}; this.logResult(test, "TEST-PASS", "TEST-UNEXPECTED-FAIL"); @@ -62,6 +63,8 @@ Marionette.prototype = { 'diag': this.tests[i].diag}); } } + // Reset state in case this object is reused for more tests. + this.tests = []; return {"passed": passed, "failed": failed, "failures": failures}; },