Bug 751783 - Allow sandbox reuse between execute_script calls, r=jgriffin, r=mdas, DONTBUILD because NPOTB,

This commit is contained in:
Philipp von Weitershausen 2012-05-09 12:05:39 -07:00
parent f008076292
commit 3025c86b7f
6 changed files with 160 additions and 70 deletions

View File

@ -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):

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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

View File

@ -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};
},