author | Philipp von Weitershausen <philipp@weitershausen.de> |
Wed, 09 May 2012 12:05:39 -0700 | |
changeset 93601 | b54871d38ed1ed0360cbcf2828aaf901f24fdab1 |
parent 93600 | b5765cc9da6cea73b65f9589a981ae3a79761877 |
child 93602 | d6b1898a8eb76b8b3e4d6445db4f2ca578a958d4 |
push id | 22650 |
push user | jgriffin@mozilla.com |
push date | Wed, 09 May 2012 20:23:33 +0000 |
treeherder | mozilla-central@b54871d38ed1 [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | jgriffin, mdas, DONTBUILD |
bugs | 751783 |
milestone | 15.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
|
--- a/testing/marionette/client/marionette/marionette.py +++ b/testing/marionette/client/marionette/marionette.py @@ -308,39 +308,48 @@ class Marionette(object): unwrapped = HTMLElement(self, value[key]) else: unwrapped[key] = self.unwrapValue(value[key]) else: unwrapped = value 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) response = self._send_message('executeJSScript', '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): kwargs = { 'value': target, 'using': method } if id: kwargs['element'] = id response = self._send_message('findElement', 'value', **kwargs) element = HTMLElement(self, response)
--- a/testing/marionette/client/marionette/tests/unit/test_execute_async_script.py +++ b/testing/marionette/client/marionette/tests/unit/test_execute_async_script.py @@ -66,21 +66,21 @@ class TestExecuteAsyncContent(Marionette self.assertRaises(JavascriptException, self.marionette.execute_async_script, unload) def test_check_window(self): self.assertTrue(self.marionette.execute_async_script("marionetteScriptFinished(window !=null && window != undefined);")) 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) def test_execute_js_exception(self): self.assertRaises(JavascriptException, self.marionette.execute_async_script, "foo(bar);") @@ -97,16 +97,29 @@ class TestExecuteAsyncContent(Marionette """)) def test_execute_permission(self): self.assertRaises(JavascriptException, self.marionette.execute_async_script, """ 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() self.marionette.set_context("chrome") def test_execute_async_unload(self): pass @@ -114,8 +127,11 @@ class TestExecuteAsyncChrome(TestExecute pass def test_execute_permission(self): self.assertEqual(1, self.marionette.execute_async_script(""" var c = Components.classes; marionetteScriptFinished(1); """)) + 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 @@ -59,17 +59,26 @@ class TestExecuteContent(MarionetteTestC self.assertEqual(self.marionette.execute_script("return [1, 2];"), [1, 2]) self.assertEqual(self.marionette.execute_script("return {'foo': 'bar', 'fizz': 'fazz'};"), {'foo': 'bar', 'fizz': 'fazz'}) self.assertEqual(self.marionette.execute_script("return [1, {'foo': 'bar'}, 2];"), [1, {'foo': 'bar'}, 2]) 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): super(TestExecuteChrome, self).setUp() self.marionette.set_context("chrome") def test_execute_permission(self): self.assertEqual(1, self.marionette.execute_script("var c = Components.classes;return 1;")) + def test_sandbox_reuse(self): + pass
--- a/testing/marionette/marionette-actors.js +++ b/testing/marionette/marionette-actors.js @@ -467,18 +467,26 @@ MarionetteDriverActor.prototype = { * @param object aRequest * 'value' member is the script to run * 'args' member holds the arguments to the script * @param boolean directInject * if true, it will be run directly and not as a * 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; } let curWindow = this.getCurrentWindow(); let marionette = new Marionette(false, curWindow, "chrome", this.marionetteLog); let _chromeSandbox = this.createExecuteSandbox(curWindow, marionette, aRequest.args); if (!_chromeSandbox) return; @@ -528,16 +536,21 @@ MarionetteDriverActor.prototype = { * * @param object aRequest * 'value' member holds the script to execute * 'args' member holds the arguments to the script * 'timeout' member will be used as the script timeout if it is given */ 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); } else { this.execute(aRequest, true); } } @@ -557,22 +570,28 @@ MarionetteDriverActor.prototype = { * @param object aRequest * 'value' member holds the script to execute * 'args' member holds the arguments for the script * @param boolean directInject * if true, it will be run directly and not as a * 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; } let curWindow = this.getCurrentWindow(); let original_onerror = curWindow.onerror; let that = this; let marionette = new Marionette(true, curWindow, "chrome", this.marionetteLog); marionette.command_id = this.command_id;
--- a/testing/marionette/marionette-listener.js +++ b/testing/marionette/marionette-listener.js @@ -24,16 +24,25 @@ let isB2G = false; let marionetteTimeout = null; let winUtil = content.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils); let listenerId = null; //unique ID of this listener 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. * If the actor returns an ID, we start the listeners. Otherwise, nothing happens. */ function registerSelf() { let register = sendSyncMessage("Marionette:register", {value: winUtil.outerWindowID, href: content.location.href}); @@ -173,16 +182,17 @@ function sendError(message, status, trac let error_msg = { message: message, status: status, stacktrace: trace }; sendToServer("Marionette:error", error_msg, command_id); } /** * Clear test values after completion of test */ function resetValues() { + sandbox = null; marionetteTimeout = null; curWin = content; } /** * send error when we detect an unload event during async scripts */ function errUnload() { @@ -192,70 +202,104 @@ function errUnload() { /* * Marionette Methods */ /** * 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; } /** * Execute the given script either as a function body (executeScript) * or directly (for 'mochitest' like JS Marionette tests) */ 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) { let res = Cu.evalInSandbox(script, sandbox, "1.8"); sendSyncMessage("Marionette:testLog", {value: elementManager.wrapValue(marionetteLogObj.getLogs())}); marionetteLogObj.clearLogs(); if (res == undefined || res.passed == undefined) { sendError("Marionette.finish() not called", 17, null); } else { sendResponse({value: elementManager.wrapValue(res)}); } } 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"); sendSyncMessage("Marionette:testLog", {value: elementManager.wrapValue(marionetteLogObj.getLogs())}); marionetteLogObj.clearLogs(); sendResponse({value: elementManager.wrapValue(res)}); } } @@ -297,77 +341,65 @@ function executeJSScript(msg) { * * For executeJSScript, it will return a message only when the finish() method is called. * For executeAsync, it will return a response when marionetteScriptFinished/arguments[arguments.length-1] * method is called, or if it times out. */ 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) { if (marionetteTimeout == null || marionetteTimeout == 0) { sendError("Please set a timeout", 21, null); } 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); } } /** * Function to set the timeout period for element searching @@ -616,19 +648,21 @@ function switchToFrame(msg) { } break; case "number": if (curWindow.frames[msg.json.value] != undefined) { foundFrame = msg.json.value; } 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 registerSelf();
--- a/testing/marionette/marionette-simpletest.js +++ b/testing/marionette/marionette-simpletest.js @@ -6,20 +6,21 @@ */ function Marionette(is_async, window, context, logObj) { this.is_async = is_async; this.window = window; 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"); this.tests.push(test); }, is: function Marionette__is(a, b, name) { let pass = (a == b); @@ -57,16 +58,18 @@ Marionette.prototype = { passed++; } else { failed++; failures.push({'name': this.tests[i].name, '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}; }, logToFile: function Marionette__logToFile(file) { //TODO }, logResult: function Marionette__logResult(test, passString, failString) {