Bug 1324467 - Make copy of data to send to listener; r=automatedtester

The payload sent to the listener through `GeckoDriver#sendAsync` is
sometimes mutated if a `commandID` parameter is given.  Because `data`
is sometimes a reference to an object, the original object gets modified
with an additional `command_id` field.

To avoid this we copy the object before mutating it and pass it through
to the message manager.

MozReview-Commit-ID: HM2tnPqbAge

--HG--
extra : rebase_source : 6aa593973d82bdf4addd8003ce68df9ad3179a4a
This commit is contained in:
Andreas Tolfsen 2016-12-19 19:08:46 +00:00
Родитель 91321b54b4
Коммит 8ff4e83b8c
2 изменённых файлов: 60 добавлений и 38 удалений

Просмотреть файл

@ -209,48 +209,61 @@ GeckoDriver.prototype.switchToGlobalMessageManager = function() {
/**
* Helper method to send async messages to the content listener.
* Correct usage is to pass in the name of a function in listener.js,
* a message object consisting of JSON serialisable primitives,
* and the current command's ID.
* a serialisable object, and optionally the current command's ID
* when not using the modern dispatching technique.
*
* @param {string} name
* Suffix of the targetted message listener ({@code Marionette:<suffix>}).
* Suffix of the targetted message listener
* ({@code Marionette:<suffix>}).
* @param {Object=} msg
* JSON serialisable object to send to the listener.
* @param {number=} cmdId
* Command ID to ensure synchronisity.
* Optional JSON serialisable object to send to the listener.
* @param {number=} commandID
* Optional command ID to ensure synchronisity.
*/
GeckoDriver.prototype.sendAsync = function (name, msg, cmdId) {
let curRemoteFrame = this.curBrowser.frameManager.currentRemoteFrame;
GeckoDriver.prototype.sendAsync = function (name, data, commandID) {
name = "Marionette:" + name;
let payload = copy(data);
// TODO(ato): When proxy.AsyncMessageChannel
// is used for all chrome <-> content communication
// this can be removed.
if (cmdId) {
msg.command_id = cmdId;
if (commandID) {
payload.command_id = commandID;
}
if (curRemoteFrame === null) {
this.curBrowser.executeWhenReady(() => {
if (this.curBrowser.curFrameId) {
this.mm.broadcastAsyncMessage(name + this.curBrowser.curFrameId, msg);
} else {
throw new NoSuchWindowError(
"No such content frame; perhaps the listener was not registered?");
}
});
if (!this.curBrowser.frameManager.currentRemoteFrame) {
this.broadcastDelayedAsyncMessage_(name, payload);
} else {
let remoteFrameId = curRemoteFrame.targetFrameId;
try {
this.mm.sendAsyncMessage(name + remoteFrameId, msg);
} catch (e) {
switch(e.result) {
case Cr.NS_ERROR_FAILURE:
case Cr.NS_ERROR_NOT_INITIALIZED:
throw new NoSuchWindowError();
default:
throw new WebDriverError(e.toString());
}
this.sendTargettedAsyncMessage_(name, payload);
}
};
GeckoDriver.prototype.broadcastDelayedAsyncMessage_ = function (name, payload) {
this.curBrowser.executeWhenReady(() => {
if (this.curBrowser.curFrameId) {
const target = name + this.curBrowser.curFrameId;
this.mm.broadcastAsyncMessage(target, payload);
} else {
throw new NoSuchWindowError(
"No such content frame; perhaps the listener was not registered?");
}
});
};
GeckoDriver.prototype.sendTargettedAsyncMessage_ = function (name, payload) {
const curRemoteFrame = this.curBrowser.frameManager.currentRemoteFrame;
const target = name + curRemoteFrame.targetFrameId;
try {
this.mm.sendAsyncMessage(target, payload);
} catch (e) {
switch (e.result) {
case Cr.NS_ERROR_FAILURE:
case Cr.NS_ERROR_NOT_INITIALIZED:
throw new NoSuchWindowError();
default:
throw new WebDriverError(e);
}
}
};
@ -462,8 +475,8 @@ GeckoDriver.prototype.registerBrowser = function (id, be) {
GeckoDriver.prototype.registerPromise = function() {
const li = "Marionette:register";
return new Promise((resolve) => {
let cb = (msg) => {
return new Promise(resolve => {
let cb = msg => {
let wid = msg.json.value;
let be = msg.target;
let rv = this.registerBrowser(wid, be);
@ -486,7 +499,7 @@ GeckoDriver.prototype.registerPromise = function() {
GeckoDriver.prototype.listeningPromise = function() {
const li = "Marionette:listenersAttached";
return new Promise((resolve) => {
return new Promise(resolve => {
let cb = () => {
this.mm.removeMessageListener(li, cb);
resolve();
@ -584,8 +597,10 @@ GeckoDriver.prototype.newSession = function*(cmd, resp) {
yield registerBrowsers;
yield browserListening;
resp.body.sessionId = this.sessionId;
resp.body.capabilities = this.sessionCapabilities;
return {
sessionId: this.sessionId,
capabilities: this.sessionCapabilities,
};
};
/**
@ -671,7 +686,6 @@ GeckoDriver.prototype.setSessionCapabilities = function (newCaps) {
let caps = copy(this.sessionCapabilities);
caps = copy(newCaps, caps);
logger.config("Changing capabilities: " + JSON.stringify(caps));
this.sessionCapabilities = caps;
};
@ -2895,3 +2909,12 @@ GeckoDriver.prototype.commands = {
"addon:install": GeckoDriver.prototype.installAddon,
"addon:uninstall": GeckoDriver.prototype.uninstallAddon,
};
function copy (obj) {
if (Array.isArray(obj)) {
return obj.slice();
} else if (typeof obj == "object") {
return Object.assign({}, obj);
}
return obj;
}

Просмотреть файл

@ -123,11 +123,10 @@ function registerSelf() {
if (register[0]) {
let {id, remotenessChange} = register[0][0];
capabilities = register[0][2];
isB2G = capabilities.platformName == "b2g";
listenerId = id;
if (typeof id != "undefined") {
// check if we're the main process
if (register[0][1] == true) {
if (register[0][1]) {
addMessageListener("MarionetteMainListener:emitTouchEvent", emitTouchEventForIFrame);
}
startListeners();