Bug 779284 - Implement Modal dialog handling to Marionette, r=jgriffin

This commit is contained in:
Malini Das 2013-08-16 16:54:41 -04:00
parent e63bb8b399
commit 1c5e0ab176
5 changed files with 441 additions and 238 deletions

View File

@ -46,6 +46,36 @@ class TestSwitchRemoteFrame(MarionetteTestCase):
""")
self.assertFalse(main_process)
def test_remote_frame_revisit(self):
# test if we can revisit a remote frame (this takes a different codepath)
self.marionette.navigate(self.marionette.absolute_url("test.html"))
self.marionette.execute_script("SpecialPowers.addPermission('browser', true, document)")
self.marionette.execute_script("""
let iframe = document.createElement("iframe");
SpecialPowers.wrap(iframe).mozbrowser = true;
SpecialPowers.wrap(iframe).remote = true;
iframe.id = "remote_iframe";
iframe.style.height = "100px";
iframe.style.width = "100%%";
iframe.src = "%s";
document.body.appendChild(iframe);
""" % self.marionette.absolute_url("test.html"))
self.marionette.switch_to_frame("remote_iframe")
main_process = self.marionette.execute_script("""
return SpecialPowers.isMainProcess();
""")
self.assertFalse(main_process)
self.marionette.switch_to_frame()
main_process = self.marionette.execute_script("""
return SpecialPowers.isMainProcess();
""")
self.assertTrue(main_process)
self.marionette.switch_to_frame("remote_iframe")
main_process = self.marionette.execute_script("""
return SpecialPowers.isMainProcess();
""")
self.assertFalse(main_process)
def tearDown(self):
if self.oop_by_default is None:
self.marionette.execute_script("""

View File

@ -10,6 +10,7 @@ marionette.jar:
content/marionette-sendkeys.js (marionette-sendkeys.js)
content/marionette-common.js (marionette-common.js)
content/marionette-simpletest.js (marionette-simpletest.js)
content/marionette-frame-manager.js (marionette-frame-manager.js)
content/EventUtils.js (EventUtils.js)
content/ChromeUtils.js (ChromeUtils.js)
#ifdef ENABLE_TESTS

View File

@ -0,0 +1,180 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
this.EXPORTED_SYMBOLS = [
"FrameManager"
];
let FRAME_SCRIPT = "chrome://marionette/content/marionette-listener.js";
Cu.import("resource://gre/modules/Services.jsm");
/**
* An object representing a frame that Marionette has loaded a
* frame script in.
*/
function MarionetteRemoteFrame(windowId, frameId) {
this.windowId = windowId; //outerWindowId relative to main process
this.frameId = frameId ? frameId : null; //actual frame relative to windowId's frames list
this.targetFrameId = this.frameId; //assigned FrameId, used for messaging
this.messageManager = null;
};
/**
* The FrameManager will maintain the list of Out Of Process (OOP) frames and will handle
* frame switching between them.
* It handles explicit frame switching (switchToRemoteFrame), and implicit frame switching, which
* occurs when a modal dialog is triggered in B2G.
*
*/
this.FrameManager = function FrameManager(server) {
//messageManager maintains the messageManager for the current process' chrome frame or the global message manager
this.messageManager = Cc["@mozilla.org/globalmessagemanager;1"]
.getService(Ci.nsIMessageBroadcaster);
this.currentRemoteFrame = null; //holds a member of remoteFrames (for an OOP frame) or null (for the main process)
this.previousRemoteFrame = null; //frame we'll need to restore once interrupt is gone
this.handledModal = false; //set to true when we have been interrupted by a modal
this.remoteFrames = []; //list of OOP frames that has the frame script loaded
this.server = server; // a reference to the marionette server
};
FrameManager.prototype = {
/**
* Receives all messages from content messageManager
*/
receiveMessage: function FM_receiveMessage(message) {
switch (message.name) {
case "MarionetteFrame:getInterruptedState":
// This will return true if the calling frame was interrupted by a modal dialog
if (this.previousRemoteFrame) {
let interruptedFrame = Services.wm.getOuterWindowWithId(this.previousRemoteFrame.windowId);//get the frame window of the interrupted frame
if (this.previousRemoteFrame.frameId != null) {
interruptedFrame = interruptedFrame.document.getElementsByTagName("iframe")[this.previousRemoteFrame.frameId]; //find the OOP frame
}
//check if the interrupted frame is the same as the calling frame
if (interruptedFrame.src == message.target.src) {
return {value: this.handledModal};
}
}
else if (this.currentRemoteFrame == null) {
// we get here if previousRemoteFrame and currentRemoteFrame are null, ie: if we're in a non-OOP process, or we haven't switched into an OOP frame, in which case, handledModal can't be set to true.
return {value: this.handledModal};
}
return {value: false};
case "MarionetteFrame:handleModal":
/*
* handleModal is called when we need to switch frames to the main process due to a modal dialog interrupt.
* If previousRemoteFrame was set, that means we switched into a remote frame.
* If this is the case, then we want to switch back into the system frame.
* If it isn't the case, then we're in a non-OOP environment, so we don't need to handle remote frames
*/
let isLocal = true;
if (this.currentRemoteFrame != null) {
isLocal = false;
this.removeMessageManagerListeners(this.currentRemoteFrame.messageManager);
//store the previous frame so we can switch back to it when the modal is dismissed
this.previousRemoteFrame = this.currentRemoteFrame;
//by setting currentRemoteFrame to null, it signifies we're in the main process
this.currentRemoteFrame = null;
this.server.messageManager = Cc["@mozilla.org/globalmessagemanager;1"]
.getService(Ci.nsIMessageBroadcaster);
}
this.handledModal = true;
this.server.sendOk(this.server.command_id);
return {value: isLocal};
}
},
//This is just 'switch to OOP frame'. We're handling this here so we can maintain a list of remoteFrames.
switchToRemoteFrame: function FM_switchToRemoteFrame(message) {
// Switch to a remote frame.
let frameWindow = Services.wm.getOuterWindowWithId(message.json.win); //get the original frame window
let oopFrame = frameWindow.document.getElementsByTagName("iframe")[message.json.frame]; //find the OOP frame
let mm = oopFrame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager; //get the OOP frame's mm
// See if this frame already has our frame script loaded in it; if so,
// just wake it up.
for (let i = 0; i < this.remoteFrames.length; i++) {
let frame = this.remoteFrames[i];
if (frame.messageManager == mm) {
this.currentRemoteFrame = frame;
mm = frame.messageManager;
this.addMessageManagerListeners(mm);
mm.sendAsyncMessage("Marionette:restart", {});
return;
}
}
// If we get here, then we need to load the frame script in this frame,
// and set the frame's ChromeMessageSender as the active message manager the server will listen to
this.addMessageManagerListeners(mm);
mm.loadFrameScript(FRAME_SCRIPT, true);
let aFrame = new MarionetteRemoteFrame(message.json.win, message.json.frame);
aFrame.messageManager = mm;
this.remoteFrames.push(aFrame);
this.currentRemoteFrame = aFrame;
},
/*
* This function handles switching back to the frame that was interrupted by the modal dialog.
* This function gets called by the interrupted frame once the dialog is dismissed and the frame resumes its process
*/
switchToModalOrigin: function FM_switchToModalOrigin() {
//only handle this if we indeed switched out of the modal's originating frame
if (this.previousRemoteFrame != null) {
this.currentRemoteFrame = this.previousRemoteFrame;
this.addMessageManagerListeners(this.currentRemoteFrame.messageManager);
}
this.handledModal = false;
},
/**
* Adds message listeners to the server, listening for messages from content frame scripts.
* It also adds a "MarionetteFrame:getInterruptedState" message listener to the FrameManager,
* so the frame manager's state can be checked by the frame
*
* @param object messageManager
* The messageManager object (ChromeMessageBroadcaster or ChromeMessageSender)
* to which the listeners should be added.
*/
addMessageManagerListeners: function MDA_addMessageManagerListeners(messageManager) {
messageManager.addMessageListener("Marionette:ok", this.server);
messageManager.addMessageListener("Marionette:done", this.server);
messageManager.addMessageListener("Marionette:error", this.server);
messageManager.addMessageListener("Marionette:log", this.server);
messageManager.addMessageListener("Marionette:shareData", this.server);
messageManager.addMessageListener("Marionette:register", this.server);
messageManager.addMessageListener("Marionette:runEmulatorCmd", this.server);
messageManager.addMessageListener("Marionette:switchToModalOrigin", this.server);
messageManager.addMessageListener("Marionette:switchToFrame", this.server);
messageManager.addMessageListener("Marionette:switchedToFrame", this.server);
messageManager.addMessageListener("MarionetteFrame:handleModal", this);
messageManager.addMessageListener("MarionetteFrame:getInterruptedState", this);
},
/**
* Removes listeners for messages from content frame scripts.
* We do not remove the "MarionetteFrame:getInterruptedState" or the
* "Marioentte:switchToModalOrigin" message listener,
* because we want to allow all known frames to contact the frame manager so that
* it can check if it was interrupted, and if so, it will call switchToModalOrigin
* when its process gets resumed.
*
* @param object messageManager
* The messageManager object (ChromeMessageBroadcaster or ChromeMessageSender)
* from which the listeners should be removed.
*/
removeMessageManagerListeners: function MDA_removeMessageManagerListeners(messageManager) {
messageManager.removeMessageListener("Marionette:ok", this.server);
messageManager.removeMessageListener("Marionette:done", this.server);
messageManager.removeMessageListener("Marionette:error", this.server);
messageManager.removeMessageListener("Marionette:log", this.server);
messageManager.removeMessageListener("Marionette:shareData", this.server);
messageManager.removeMessageListener("Marionette:register", this.server);
messageManager.removeMessageListener("Marionette:runEmulatorCmd", this.server);
messageManager.removeMessageListener("Marionette:switchToFrame", this.server);
messageManager.removeMessageListener("Marionette:switchedToFrame", this.server);
messageManager.removeMessageListener("MarionetteFrame:handleModal", this);
},
};

View File

@ -36,8 +36,8 @@ let marionetteTestName;
let winUtil = content.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils);
let listenerId = null; //unique ID of this listener
let activeFrame = null;
let curWindow = content;
let curFrame = content;
let previousFrame = null;
let elementManager = new ElementManager([]);
let importedScripts = null;
@ -72,6 +72,24 @@ Cu.import("resource://gre/modules/services-common/log4moz.js");
let logger = Log4Moz.repository.getLogger("Marionette");
logger.info("loaded marionette-listener.js");
/*
* This is a callback used with the 'mozbrowsershowmodalprompt' event
* and is used to handle modal dialogs in B2G.
* When a modal dialog is triggered in B2G, we want to switch into
* the frame of the modal dialog instead of remaining in the current
* frame, since the current frame's process will be frozen until the
* modal dialog is dismissed.
*/
let modalHandler = function() {
sendSyncMessage("Marionette:switchedToFrame", { frameValue: null, storePrevious: true });
let isLocal = sendSyncMessage("MarionetteFrame:handleModal", {})[0].value;
if (isLocal) {
previousFrame = curFrame;
}
curFrame = content;
sandbox = null;
};
/**
* Called when listener is first started up.
* The listener sends its unique window ID and its current URI to the actor.
@ -156,6 +174,7 @@ function startListeners() {
function newSession(msg) {
isB2G = msg.json.B2G;
resetValues();
content.addEventListener("mozbrowsershowmodalprompt", modalHandler, false);
}
/**
@ -173,6 +192,7 @@ function sleepSession(msg) {
*/
function restart(msg) {
removeMessageListener("Marionette:restart", restart);
content.addEventListener("mozbrowsershowmodalprompt", modalHandler, false);
registerSelf();
}
@ -220,10 +240,11 @@ function deleteSession(msg) {
removeMessageListenerId("Marionette:getAllCookies", getAllCookies);
removeMessageListenerId("Marionette:deleteAllCookies", deleteAllCookies);
removeMessageListenerId("Marionette:deleteCookie", deleteCookie);
content.removeEventListener("mozbrowsershowmodalprompt", modalHandler, false);
this.elementManager.reset();
// reset frame to the top-most frame
curWindow = content;
curWindow.focus();
curFrame = content;
curFrame.focus();
touchIds = {};
}
@ -275,10 +296,25 @@ function sendError(message, status, trace, command_id) {
*/
function resetValues() {
sandbox = null;
curWindow = content;
curFrame = content;
mouseEventsOnly = false;
}
/**
* Check if our context was interrupted
*/
function wasInterrupted() {
if (previousFrame) {
let element = content.document.elementFromPoint((content.innerWidth/2), (content.innerHeight/2));
if (element.id.indexOf("modal-dialog") == -1) {
return true;
}
else {
return false;
}
}
return sendSyncMessage("MarionetteFrame:getInterruptedState", {})[0].value;
}
/*
* Marionette Methods
@ -315,8 +351,8 @@ function createExecuteContentSandbox(aWindow, timeout) {
sandbox.asyncComplete = function sandbox_asyncComplete(value, status, stack, commandId) {
if (commandId == asyncTestCommandId) {
curWindow.removeEventListener("unload", onunload, false);
curWindow.clearTimeout(asyncTestTimeoutId);
curFrame.removeEventListener("unload", onunload, false);
curFrame.clearTimeout(asyncTestTimeoutId);
sendSyncMessage("Marionette:shareData",
{log: elementManager.wrapValue(marionetteLogObj.getLogs())});
@ -365,7 +401,7 @@ function executeScript(msg, directInject) {
let script = msg.json.value;
if (msg.json.newSandbox || !sandbox) {
sandbox = createExecuteContentSandbox(curWindow,
sandbox = createExecuteContentSandbox(curFrame,
msg.json.timeout);
if (!sandbox) {
sendError("Could not create sandbox!", 500, null, asyncTestCommandId);
@ -400,7 +436,7 @@ function executeScript(msg, directInject) {
else {
try {
sandbox.__marionetteParams = elementManager.convertWrappedArguments(
msg.json.args, curWindow);
msg.json.args, curFrame);
}
catch(e) {
sendError(e.message, e.code, e.stack, asyncTestCommandId);
@ -476,10 +512,10 @@ function executeWithCallback(msg, useFinish) {
onunload = function() {
sendError("unload was called", 17, null, asyncTestCommandId);
};
curWindow.addEventListener("unload", onunload, false);
curFrame.addEventListener("unload", onunload, false);
if (msg.json.newSandbox || !sandbox) {
sandbox = createExecuteContentSandbox(curWindow,
sandbox = createExecuteContentSandbox(curFrame,
msg.json.timeout);
if (!sandbox) {
sendError("Could not create sandbox!", 17, null, asyncTestCommandId);
@ -496,14 +532,14 @@ function executeWithCallback(msg, useFinish) {
// 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.
asyncTestTimeoutId = curWindow.setTimeout(function() {
asyncTestTimeoutId = curFrame.setTimeout(function() {
sandbox.asyncComplete('timed out', 28, null, asyncTestCommandId);
}, msg.json.timeout);
originalOnError = curWindow.onerror;
curWindow.onerror = function errHandler(errMsg, url, line) {
originalOnError = curFrame.onerror;
curFrame.onerror = function errHandler(errMsg, url, line) {
sandbox.asyncComplete(errMsg, 17, "@" + url + ", line " + line, asyncTestCommandId);
curWindow.onerror = originalOnError;
curFrame.onerror = originalOnError;
};
let scriptSrc;
@ -516,7 +552,7 @@ function executeWithCallback(msg, useFinish) {
else {
try {
sandbox.__marionetteParams = elementManager.convertWrappedArguments(
msg.json.args, curWindow);
msg.json.args, curFrame);
}
catch(e) {
sendError(e.message, e.code, e.stack, asyncTestCommandId);
@ -553,17 +589,19 @@ function executeWithCallback(msg, useFinish) {
* This function creates a touch event given a touch type and a touch
*/
function emitTouchEvent(type, touch) {
let loggingInfo = "Marionette: emitting Touch event of type " + type + " to element with id: " + touch.target.id + " and tag name: " + touch.target.tagName + " at coordinates (" + touch.clientX + ", " + touch.clientY + ") relative to the viewport";
dump(loggingInfo);
/*
Disabled per bug 888303
marionetteLogObj.log(loggingInfo, "TRACE");
sendSyncMessage("Marionette:shareData",
{log: elementManager.wrapValue(marionetteLogObj.getLogs())});
marionetteLogObj.clearLogs();
*/
let domWindowUtils = curWindow.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils);
domWindowUtils.sendTouchEvent(type, [touch.identifier], [touch.screenX], [touch.screenY], [touch.radiusX], [touch.radiusY], [touch.rotationAngle], [touch.force], 1, 0);
if (!wasInterrupted()) {
let loggingInfo = "Marionette: emitting Touch event of type " + type + " to element with id: " + touch.target.id + " and tag name: " + touch.target.tagName + " at coordinates (" + touch.clientX + ", " + touch.clientY + ") relative to the viewport";
dump(loggingInfo);
/*
Disabled per bug 888303
marionetteLogObj.log(loggingInfo, "TRACE");
sendSyncMessage("Marionette:shareData",
{log: elementManager.wrapValue(marionetteLogObj.getLogs())});
marionetteLogObj.clearLogs();
*/
let domWindowUtils = curFrame.QueryInterface(Components.interfaces.nsIInterfaceRequestor).getInterface(Components.interfaces.nsIDOMWindowUtils);
domWindowUtils.sendTouchEvent(type, [touch.identifier], [touch.screenX], [touch.screenY], [touch.radiusX], [touch.radiusY], [touch.rotationAngle], [touch.force], 1, 0);
}
}
/**
@ -574,20 +612,22 @@ function emitTouchEvent(type, touch) {
* elClientX and elClientY are the coordinates of the mouse relative to the viewport
*/
function emitMouseEvent(doc, type, elClientX, elClientY, detail, button) {
let loggingInfo = "Marionette: emitting Mouse event of type " + type + " at coordinates (" + elClientX + ", " + elClientY + ") relative to the viewport";
dump(loggingInfo);
/*
Disabled per bug 888303
marionetteLogObj.log(loggingInfo, "TRACE");
sendSyncMessage("Marionette:shareData",
{log: elementManager.wrapValue(marionetteLogObj.getLogs())});
marionetteLogObj.clearLogs();
*/
detail = detail || 1;
button = button || 0;
let win = doc.defaultView;
// Figure out the element the mouse would be over at (x, y)
utils.synthesizeMouseAtPoint(elClientX, elClientY, {type: type, button: button, clickCount: detail}, win);
if (!wasInterrupted()) {
let loggingInfo = "Marionette: emitting Mouse event of type " + type + " at coordinates (" + elClientX + ", " + elClientY + ") relative to the viewport";
dump(loggingInfo);
/*
Disabled per bug 888303
marionetteLogObj.log(loggingInfo, "TRACE");
sendSyncMessage("Marionette:shareData",
{log: elementManager.wrapValue(marionetteLogObj.getLogs())});
marionetteLogObj.clearLogs();
*/
detail = detail || 1;
button = button || 0;
let win = doc.defaultView;
// Figure out the element the mouse would be over at (x, y)
utils.synthesizeMouseAtPoint(elClientX, elClientY, {type: type, button: button, clickCount: detail}, win);
}
}
/**
@ -626,30 +666,30 @@ function coordinates(target, x, y) {
function elementInViewport(el) {
let rect = el.getBoundingClientRect();
return (/* Top left corner is in view */
(rect.top >= curWindow.pageYOffset &&
rect.top <= (curWindow.pageYOffset + curWindow.innerHeight) &&
rect.left >= curWindow.pageXOffset &&
rect.left <= (curWindow.pageXOffset + curWindow.innerWidth)) ||
(rect.top >= curFrame.pageYOffset &&
rect.top <= (curFrame.pageYOffset + curFrame.innerHeight) &&
rect.left >= curFrame.pageXOffset &&
rect.left <= (curFrame.pageXOffset + curFrame.innerWidth)) ||
/* Top right corner is in view */
(rect.top >= curWindow.pageYOffset &&
rect.top <= (curWindow.pageYOffset + curWindow.innerHeight) &&
rect.right >= curWindow.pageXOffset &&
rect.right <= (curWindow.pageXOffset + curWindow.innerWidth)) ||
(rect.top >= curFrame.pageYOffset &&
rect.top <= (curFrame.pageYOffset + curFrame.innerHeight) &&
rect.right >= curFrame.pageXOffset &&
rect.right <= (curFrame.pageXOffset + curFrame.innerWidth)) ||
/* Bottom right corner is in view */
(rect.bottom >= curWindow.pageYOffset &&
rect.bottom <= (curWindow.pageYOffset + curWindow.innerHeight) &&
rect.right >= curWindow.pageXOffset &&
rect.right <= (curWindow.pageXOffset + curWindow.innerWidth)) ||
(rect.bottom >= curFrame.pageYOffset &&
rect.bottom <= (curFrame.pageYOffset + curFrame.innerHeight) &&
rect.right >= curFrame.pageXOffset &&
rect.right <= (curFrame.pageXOffset + curFrame.innerWidth)) ||
/* Bottom left corner is in view */
(rect.bottom >= curWindow.pageYOffset &&
rect.bottom <= (curWindow.pageYOffset + curWindow.innerHeight) &&
rect.left >= curWindow.pageXOffset &&
rect.left <= (curWindow.pageXOffset + curWindow.innerWidth)) ||
(rect.bottom >= curFrame.pageYOffset &&
rect.bottom <= (curFrame.pageYOffset + curFrame.innerHeight) &&
rect.left >= curFrame.pageXOffset &&
rect.left <= (curFrame.pageXOffset + curFrame.innerWidth)) ||
/* Center of the element is in view if element larger than viewport */
((rect.top + (rect.height/2)) <= curWindow.pageYOffset &&
(rect.top + (rect.height/2)) >= (curWindow.pageYOffset + curWindow.innerHeight) &&
(rect.left + (rect.width/2)) <= curWindow.pageXOffset &&
(rect.left + (rect.width/2)) >= (curWindow.pageXOffset + curWindow.innerWidth))
((rect.top + (rect.height/2)) <= curFrame.pageYOffset &&
(rect.top + (rect.height/2)) >= (curFrame.pageYOffset + curFrame.innerHeight) &&
(rect.left + (rect.width/2)) <= curFrame.pageXOffset &&
(rect.left + (rect.width/2)) >= (curFrame.pageXOffset + curFrame.innerWidth))
);
}
@ -683,7 +723,7 @@ function checkVisible(el) {
//x and y are coordinates relative to the viewport
function generateEvents(type, x, y, touchId, target) {
lastCoordinates = [x, y];
let doc = curWindow.document;
let doc = curFrame.document;
switch (type) {
case 'tap':
if (mouseEventsOnly) {
@ -752,7 +792,7 @@ function generateEvents(type, x, y, touchId, target) {
break;
case 'contextmenu':
isTap = false;
let event = curWindow.document.createEvent('HTMLEvents');
let event = curFrame.document.createEvent('HTMLEvents');
event.initEvent('contextmenu', true, true);
if (mouseEventsOnly) {
target = doc.elementFromPoint(lastCoordinates[0], lastCoordinates[1]);
@ -765,6 +805,19 @@ function generateEvents(type, x, y, touchId, target) {
default:
throw {message:"Unknown event type: " + type, code: 500, stack:null};
}
if (wasInterrupted()) {
if (previousFrame) {
//if previousFrame is set, then we're in a single process environment
curFrame = previousFrame;
previousFrame = null;
sandbox = null;
}
else {
//else we're in OOP environment, so we'll switch to the original OOP frame
sendSyncMessage("Marionette:switchToModalOrigin");
}
sendSyncMessage("Marionette:switchedToFrame", { restorePrevious: true });
}
}
/**
@ -773,13 +826,13 @@ function generateEvents(type, x, y, touchId, target) {
function singleTap(msg) {
let command_id = msg.json.command_id;
try {
let el = elementManager.getKnownElement(msg.json.value, curWindow);
let el = elementManager.getKnownElement(msg.json.value, curFrame);
// after this block, the element will be scrolled into view
if (!checkVisible(el)) {
sendError("Element is not currently visible and may not be manipulated", 11, null, command_id);
return;
}
if (!curWindow.document.createTouch) {
if (!curFrame.document.createTouch) {
mouseEventsOnly = true;
}
let c = coordinates(el, msg.json.corx, msg.json.cory);
@ -839,7 +892,7 @@ function actions(chain, touchId, command_id, i) {
sendError("Invalid Command: press cannot follow an active touch event", 500, null, command_id);
return;
}
el = elementManager.getKnownElement(pack[1], curWindow);
el = elementManager.getKnownElement(pack[1], curFrame);
if (!checkVisible(el)) {
sendError("Element is not currently visible and may not be manipulated", 11, null, command_id);
return;
@ -853,7 +906,7 @@ function actions(chain, touchId, command_id, i) {
actions(chain, null, command_id, i);
break;
case 'move':
el = elementManager.getKnownElement(pack[1], curWindow);
el = elementManager.getKnownElement(pack[1], curFrame);
c = coordinates(el);
generateEvents('move', c.x, c.y, touchId);
actions(chain, touchId, command_id, i);
@ -900,12 +953,12 @@ function actionChain(msg) {
let args = msg.json.chain;
let touchId = msg.json.nextId;
try {
let commandArray = elementManager.convertWrappedArguments(args, curWindow);
let commandArray = elementManager.convertWrappedArguments(args, curFrame);
// loop the action array [ ['press', id], ['move', id], ['release', id] ]
if (touchId == null) {
touchId = nextTouchId++;
}
if (!curWindow.document.createTouch) {
if (!curFrame.document.createTouch) {
mouseEventsOnly = true;
}
actions(commandArray, touchId, command_id);
@ -934,7 +987,7 @@ function emitMultiEvents(type, touch, touches) {
// Create changed touches
let changedTouches = doc.createTouchList(touch);
// Create the event object
let event = curWindow.document.createEvent('TouchEvent');
let event = curFrame.document.createEvent('TouchEvent');
event.initTouchEvent(type,
true,
true,
@ -987,7 +1040,7 @@ function setDispatch(batches, touches, command_id, batchIndex) {
command = pack[1];
switch (command) {
case 'press':
el = elementManager.getKnownElement(pack[2], curWindow);
el = elementManager.getKnownElement(pack[2], curFrame);
c = coordinates(el, pack[3], pack[4]);
touch = createATouch(el, c.x, c.y, touchId);
multiLast[touchId] = touch;
@ -1002,7 +1055,7 @@ function setDispatch(batches, touches, command_id, batchIndex) {
emitMultiEvents('touchend', touch, touches);
break;
case 'move':
el = elementManager.getKnownElement(pack[2], curWindow);
el = elementManager.getKnownElement(pack[2], curFrame);
c = coordinates(el);
touch = createATouch(multiLast[touchId].target, c.x, c.y, touchId);
touchIndex = touches.indexOf(lastTouch);
@ -1056,7 +1109,7 @@ function multiAction(msg) {
let maxlen = msg.json.maxlen;
try {
// unwrap the original nested array
let commandArray = elementManager.convertWrappedArguments(args, curWindow);
let commandArray = elementManager.convertWrappedArguments(args, curFrame);
let concurrentEvent = [];
let temp;
for (let i = 0; i < maxlen; i++) {
@ -1098,11 +1151,11 @@ function goUrl(msg) {
let errorRegex = /about:.+(error)|(blocked)\?/;
let elapse = end - start;
if (msg.json.pageTimeout == null || elapse <= msg.json.pageTimeout){
if (curWindow.document.readyState == "complete"){
if (curFrame.document.readyState == "complete"){
removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
sendOk(command_id);
}
else if (curWindow.document.readyState == "interactive" && errorRegex.exec(curWindow.document.baseURI)){
else if (curFrame.document.readyState == "interactive" && errorRegex.exec(curFrame.document.baseURI)){
removeEventListener("DOMContentLoaded", onDOMContentLoaded, false);
sendError("Error loading page", 13, null, command_id);
}
@ -1120,7 +1173,7 @@ function goUrl(msg) {
// window (i.e., someone has used switch_to_frame).
let onDOMContentLoaded = function onDOMContentLoaded(event){
if (!event.originalTarget.defaultView.frameElement ||
event.originalTarget.defaultView.frameElement == curWindow.frameElement) {
event.originalTarget.defaultView.frameElement == curFrame.frameElement) {
checkLoad();
}
};
@ -1133,29 +1186,29 @@ function goUrl(msg) {
checkTimer.initWithCallback(timerFunc, msg.json.pageTimeout, Ci.nsITimer.TYPE_ONE_SHOT);
}
addEventListener("DOMContentLoaded", onDOMContentLoaded, false);
curWindow.location = msg.json.value;
curFrame.location = msg.json.value;
}
/**
* Get the current URI
*/
function getUrl(msg) {
sendResponse({value: curWindow.location.href}, msg.json.command_id);
sendResponse({value: curFrame.location.href}, msg.json.command_id);
}
/**
* Get the current Title of the window
*/
function getTitle(msg) {
sendResponse({value: curWindow.top.document.title}, msg.json.command_id);
sendResponse({value: curFrame.top.document.title}, msg.json.command_id);
}
/**
* Get the current page source
*/
function getPageSource(msg) {
var XMLSerializer = curWindow.XMLSerializer;
var pageSource = new XMLSerializer().serializeToString(curWindow.document);
var XMLSerializer = curFrame.XMLSerializer;
var pageSource = new XMLSerializer().serializeToString(curFrame.document);
sendResponse({value: pageSource}, msg.json.command_id);
}
@ -1163,7 +1216,7 @@ function getPageSource(msg) {
* Go back in history
*/
function goBack(msg) {
curWindow.history.back();
curFrame.history.back();
sendOk(msg.json.command_id);
}
@ -1171,7 +1224,7 @@ function goBack(msg) {
* Go forward in history
*/
function goForward(msg) {
curWindow.history.forward();
curFrame.history.forward();
sendOk(msg.json.command_id);
}
@ -1180,7 +1233,7 @@ function goForward(msg) {
*/
function refresh(msg) {
let command_id = msg.json.command_id;
curWindow.location.reload(true);
curFrame.location.reload(true);
let listen = function() {
removeEventListener("DOMContentLoaded", arguments.callee, false);
sendOk(command_id);
@ -1196,7 +1249,7 @@ function findElementContent(msg) {
try {
let on_success = function(id, cmd_id) { sendResponse({value:id}, cmd_id); };
let on_error = sendError;
elementManager.find(curWindow, msg.json, msg.json.searchTimeout,
elementManager.find(curFrame, msg.json, msg.json.searchTimeout,
on_success, on_error, false, command_id);
}
catch (e) {
@ -1212,7 +1265,7 @@ function findElementsContent(msg) {
try {
let on_success = function(id, cmd_id) { sendResponse({value:id}, cmd_id); };
let on_error = sendError;
elementManager.find(curWindow, msg.json, msg.json.searchTimeout,
elementManager.find(curFrame, msg.json, msg.json.searchTimeout,
on_success, on_error, true, command_id);
}
catch (e) {
@ -1225,7 +1278,7 @@ function findElementsContent(msg) {
*/
function getActiveElement(msg) {
let command_id = msg.json.command_id;
var element = curWindow.document.activeElement;
var element = curFrame.document.activeElement;
var id = elementManager.addToKnownElements(element);
sendResponse({value: id}, command_id);
}
@ -1237,7 +1290,7 @@ function clickElement(msg) {
let command_id = msg.json.command_id;
let el;
try {
el = elementManager.getKnownElement(msg.json.element, curWindow);
el = elementManager.getKnownElement(msg.json.element, curFrame);
if (checkVisible(el)) {
if (utils.isElementEnabled(el)) {
utils.synthesizeMouseAtCenter(el, {}, el.ownerDocument.defaultView)
@ -1262,7 +1315,7 @@ function clickElement(msg) {
function getElementAttribute(msg) {
let command_id = msg.json.command_id;
try {
let el = elementManager.getKnownElement(msg.json.element, curWindow);
let el = elementManager.getKnownElement(msg.json.element, curFrame);
sendResponse({value: utils.getElementAttribute(el, msg.json.name)},
command_id);
}
@ -1277,7 +1330,7 @@ function getElementAttribute(msg) {
function getElementText(msg) {
let command_id = msg.json.command_id;
try {
let el = elementManager.getKnownElement(msg.json.element, curWindow);
let el = elementManager.getKnownElement(msg.json.element, curFrame);
sendResponse({value: utils.getElementText(el)}, command_id);
}
catch (e) {
@ -1291,7 +1344,7 @@ function getElementText(msg) {
function getElementTagName(msg) {
let command_id = msg.json.command_id;
try {
let el = elementManager.getKnownElement(msg.json.element, curWindow);
let el = elementManager.getKnownElement(msg.json.element, curFrame);
sendResponse({value: el.tagName.toLowerCase()}, command_id);
}
catch (e) {
@ -1305,7 +1358,7 @@ function getElementTagName(msg) {
function isElementDisplayed(msg) {
let command_id = msg.json.command_id;
try {
let el = elementManager.getKnownElement(msg.json.element, curWindow);
let el = elementManager.getKnownElement(msg.json.element, curFrame);
sendResponse({value: utils.isElementDisplayed(el)}, command_id);
}
catch (e) {
@ -1325,8 +1378,8 @@ function getElementValueOfCssProperty(msg){
let command_id = msg.json.command_id;
let propertyName = msg.json.propertyName;
try {
let el = elementManager.getKnownElement(msg.json.element, curWindow);
sendResponse({value: curWindow.document.defaultView.getComputedStyle(el, null).getPropertyValue(propertyName)},
let el = elementManager.getKnownElement(msg.json.element, curFrame);
sendResponse({value: curFrame.document.defaultView.getComputedStyle(el, null).getPropertyValue(propertyName)},
command_id);
}
catch (e) {
@ -1340,7 +1393,7 @@ function getElementValueOfCssProperty(msg){
function getElementSize(msg){
let command_id = msg.json.command_id;
try {
let el = elementManager.getKnownElement(msg.json.element, curWindow);
let el = elementManager.getKnownElement(msg.json.element, curFrame);
let clientRect = el.getBoundingClientRect();
sendResponse({value: {width: clientRect.width, height: clientRect.height}},
command_id);
@ -1356,7 +1409,7 @@ function getElementSize(msg){
function isElementEnabled(msg) {
let command_id = msg.json.command_id;
try {
let el = elementManager.getKnownElement(msg.json.element, curWindow);
let el = elementManager.getKnownElement(msg.json.element, curFrame);
sendResponse({value: utils.isElementEnabled(el)}, command_id);
}
catch (e) {
@ -1370,7 +1423,7 @@ function isElementEnabled(msg) {
function isElementSelected(msg) {
let command_id = msg.json.command_id;
try {
let el = elementManager.getKnownElement(msg.json.element, curWindow);
let el = elementManager.getKnownElement(msg.json.element, curFrame);
sendResponse({value: utils.isElementSelected(el)}, command_id);
}
catch (e) {
@ -1384,9 +1437,9 @@ function isElementSelected(msg) {
function sendKeysToElement(msg) {
let command_id = msg.json.command_id;
try {
let el = elementManager.getKnownElement(msg.json.element, curWindow);
let el = elementManager.getKnownElement(msg.json.element, curFrame);
if (checkVisible(el)) {
utils.type(curWindow.document, el, msg.json.value.join(""), true);
utils.type(curFrame.document, el, msg.json.value.join(""), true);
sendOk(command_id);
}
else {
@ -1404,7 +1457,7 @@ function sendKeysToElement(msg) {
function getElementPosition(msg) {
let command_id = msg.json.command_id;
try{
let el = elementManager.getKnownElement(msg.json.element, curWindow);
let el = elementManager.getKnownElement(msg.json.element, curFrame);
let rect = el.getBoundingClientRect();
let location = {};
@ -1424,7 +1477,7 @@ function getElementPosition(msg) {
function clearElement(msg) {
let command_id = msg.json.command_id;
try {
let el = elementManager.getKnownElement(msg.json.element, curWindow);
let el = elementManager.getKnownElement(msg.json.element, curFrame);
utils.clearElement(el);
sendOk(command_id);
}
@ -1439,27 +1492,27 @@ function clearElement(msg) {
*/
function switchToFrame(msg) {
let command_id = msg.json.command_id;
function checkLoad() {
function checkLoad() {
let errorRegex = /about:.+(error)|(blocked)\?/;
if (curWindow.document.readyState == "complete") {
if (curFrame.document.readyState == "complete") {
sendOk(command_id);
return;
}
else if (curWindow.document.readyState == "interactive" && errorRegex.exec(curWindow.document.baseURI)) {
else if (curFrame.document.readyState == "interactive" && errorRegex.exec(curFrame.document.baseURI)) {
sendError("Error loading page", 13, null, command_id);
return;
}
checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
}
let foundFrame = null;
let frames = []; //curWindow.document.getElementsByTagName("iframe");
let parWindow = null; //curWindow.QueryInterface(Ci.nsIInterfaceRequestor)
// Check of the curWindow reference is dead
let frames = []; //curFrame.document.getElementsByTagName("iframe");
let parWindow = null; //curFrame.QueryInterface(Ci.nsIInterfaceRequestor)
// Check of the curFrame reference is dead
try {
frames = curWindow.document.getElementsByTagName("iframe");
frames = curFrame.document.getElementsByTagName("iframe");
//Until Bug 761935 lands, we won't have multiple nested OOP iframes. We will only have one.
//parWindow will refer to the iframe above the nested OOP frame.
parWindow = curWindow.QueryInterface(Ci.nsIInterfaceRequestor)
parWindow = curFrame.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
} catch (e) {
// We probably have a dead compartment so accessing it is going to make Firefox
@ -1472,9 +1525,9 @@ function switchToFrame(msg) {
// returning to root frame
sendSyncMessage("Marionette:switchedToFrame", { frameValue: null });
curWindow = content;
curFrame = content;
if(msg.json.focus == true) {
curWindow.focus();
curFrame.focus();
}
sandbox = null;
checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
@ -1484,7 +1537,7 @@ function switchToFrame(msg) {
if (elementManager.seenItems[msg.json.element] != undefined) {
let wantedFrame;
try {
wantedFrame = elementManager.getKnownElement(msg.json.element, curWindow); //HTMLIFrameElement
wantedFrame = elementManager.getKnownElement(msg.json.element, curFrame); //HTMLIFrameElement
}
catch(e) {
sendError(e.message, e.code, e.stack, command_id);
@ -1492,7 +1545,7 @@ function switchToFrame(msg) {
for (let i = 0; i < frames.length; i++) {
// use XPCNativeWrapper to compare elements; see bug 834266
if (XPCNativeWrapper(frames[i]) == XPCNativeWrapper(wantedFrame)) {
curWindow = frames[i];
curFrame = frames[i];
foundFrame = i;
}
}
@ -1516,13 +1569,13 @@ function switchToFrame(msg) {
}
if ((foundFrame == null) && (foundById != null)) {
foundFrame = foundById;
curWindow = frames[foundFrame];
curFrame = frames[foundFrame];
}
break;
case "number":
if (frames[msg.json.value] != undefined) {
foundFrame = msg.json.value;
curWindow = frames[foundFrame];
curFrame = frames[foundFrame];
}
break;
}
@ -1536,21 +1589,21 @@ function switchToFrame(msg) {
// send a synchronous message to let the server update the currently active
// frame element (for getActiveFrame)
let frameValue = elementManager.wrapValue(curWindow.wrappedJSObject)['ELEMENT'];
let frameValue = elementManager.wrapValue(curFrame.wrappedJSObject)['ELEMENT'];
sendSyncMessage("Marionette:switchedToFrame", { frameValue: frameValue });
if (curWindow.contentWindow == null) {
if (curFrame.contentWindow == null) {
// The frame we want to switch to is a remote (out-of-process) frame;
// notify our parent to handle the switch.
curWindow = content;
curFrame = content;
sendToServer('Marionette:switchToFrame', {frame: foundFrame,
win: parWindow,
command_id: command_id});
}
else {
curWindow = curWindow.contentWindow;
curFrame = curFrame.contentWindow;
if(msg.json.focus == true) {
curWindow.focus();
curFrame.focus();
}
checkTimer.initWithCallback(checkLoad, 100, Ci.nsITimer.TYPE_ONE_SHOT);
}
@ -1569,11 +1622,11 @@ function addCookie(msg) {
}
if (!cookie.domain) {
var location = curWindow.document.location;
var location = curFrame.document.location;
cookie.domain = location.hostname;
}
else {
var currLocation = curWindow.location;
var currLocation = curFrame.location;
var currDomain = currLocation.host;
if (currDomain.indexOf(cookie.domain) == -1) {
sendError("You may only set cookies for the current domain", 24, null, msg.json.command_id);
@ -1587,7 +1640,7 @@ function addCookie(msg) {
cookie.domain = cookie.domain.replace(/:\d+$/, '');
}
var document = curWindow.document;
var document = curFrame.document;
if (!document || !document.contentType.match(/html/i)) {
sendError('You may only set cookies on html documents', 25, null, msg.json.command_id);
}
@ -1603,7 +1656,7 @@ function addCookie(msg) {
*/
function getAllCookies(msg) {
var toReturn = [];
var cookies = getVisibleCookies(curWindow.location);
var cookies = getVisibleCookies(curFrame.location);
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i];
var expires = cookie.expires;
@ -1633,7 +1686,7 @@ function deleteCookie(msg) {
var cookieManager = Cc['@mozilla.org/cookiemanager;1'].
getService(Ci.nsICookieManager);
var cookies = getVisibleCookies(curWindow.location);
var cookies = getVisibleCookies(curFrame.location);
for (var i = 0; i < cookies.length; i++) {
var cookie = cookies[i];
if (cookie.name == toDelete) {
@ -1650,7 +1703,7 @@ function deleteCookie(msg) {
function deleteAllCookies(msg) {
let cookieManager = Cc['@mozilla.org/cookiemanager;1'].
getService(Ci.nsICookieManager);
let cookies = getVisibleCookies(curWindow.location);
let cookies = getVisibleCookies(curFrame.location);
for (let i = 0; i < cookies.length; i++) {
let cookie = cookies[i];
cookieManager.remove(cookie.host, cookie.name, cookie.path, false);
@ -1691,7 +1744,7 @@ function getVisibleCookies(location) {
}
function getAppCacheStatus(msg) {
sendResponse({ value: curWindow.applicationCache.status },
sendResponse({ value: curFrame.applicationCache.status },
msg.json.command_id);
}
@ -1753,7 +1806,7 @@ function screenShot(msg) {
let node = null;
if (msg.json.element) {
try {
node = elementManager.getKnownElement(msg.json.element, curWindow)
node = elementManager.getKnownElement(msg.json.element, curFrame)
}
catch (e) {
sendResponse(e.message, e.code, e.stack, msg.json.command_id);
@ -1761,14 +1814,14 @@ function screenShot(msg) {
}
}
else {
node = curWindow;
node = curFrame;
}
let highlights = msg.json.highlights;
var document = curWindow.document;
var document = curFrame.document;
var rect, win, width, height, left, top, needsOffset;
// node can be either a window or an arbitrary DOM node
if (node == curWindow) {
if (node == curFrame) {
// node is a window
win = node;
width = win.innerWidth;

View File

@ -15,6 +15,8 @@ let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
.getService(Ci.mozIJSSubScriptLoader);
loader.loadSubScript("chrome://marionette/content/marionette-simpletest.js");
loader.loadSubScript("chrome://marionette/content/marionette-common.js");
Cu.import("resource://gre/modules/Services.jsm");
loader.loadSubScript("chrome://marionette/content/marionette-frame-manager.js");
Cu.import("chrome://marionette/content/marionette-elements.js");
let utils = {};
loader.loadSubScript("chrome://marionette/content/EventUtils.js", utils);
@ -27,7 +29,6 @@ loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserver.js",
specialpowers.specialPowersObserver = new specialpowers.SpecialPowersObserver();
specialpowers.specialPowersObserver.init();
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/NetUtil.jsm");
@ -76,20 +77,6 @@ Services.obs.addObserver(function() {
systemMessageListenerReady = true;
}, "system-message-listener-ready", false);
/**
* An object representing a frame that Marionette has loaded a
* frame script in.
*/
function MarionetteRemoteFrame(windowId, frameId) {
this.windowId = windowId;
this.frameId = frameId;
this.targetFrameId = null;
this.messageManager = null;
this.command_id = null;
}
// persistent list of remote frames that Marionette has loaded a frame script in
let remoteFrames = [];
/*
* Custom exceptions
*/
@ -144,15 +131,11 @@ function MarionetteServerConnection(aPrefix, aTransport, aServer)
this.marionetteLog = new MarionetteLogObj();
this.command_id = null;
this.mainFrame = null; //topmost chrome frame
this.curFrame = null; //subframe that currently has focus
this.curFrame = null; // chrome iframe that currently has focus
this.importedScripts = FileUtils.getFile('TmpD', ['marionettescriptchrome']);
this.currentRemoteFrame = null; // a member of remoteFrames
this.currentFrameElement = null;
this.testName = null;
this.mozBrowserClose = null;
//register all message listeners
this.addMessageManagerListeners(this.messageManager);
}
MarionetteServerConnection.prototype = {
@ -195,12 +178,12 @@ MarionetteServerConnection.prototype = {
* which removes most of the message listeners from it as well.
*/
switchToGlobalMessageManager: function MDA_switchToGlobalMM() {
if (this.currentRemoteFrame !== null) {
this.removeMessageManagerListeners(this.messageManager);
if (this.curBrowser.frameManager.currentRemoteFrame !== null) {
this.curBrowser.frameManager.removeMessageManagerListeners(this.messageManager);
this.sendAsync("sleepSession", null, null, true);
}
this.messageManager = this.globalMessageManager;
this.currentRemoteFrame = null;
this.curBrowser.frameManager.currentRemoteFrame = null;
},
/**
@ -216,10 +199,10 @@ MarionetteServerConnection.prototype = {
if (values instanceof Object && commandId) {
values.command_id = commandId;
}
if (this.currentRemoteFrame !== null) {
if (this.curBrowser.frameManager.currentRemoteFrame !== null) {
try {
this.messageManager.sendAsyncMessage(
"Marionette:" + name + this.currentRemoteFrame.targetFrameId, values);
"Marionette:" + name + this.curBrowser.frameManager.currentRemoteFrame.targetFrameId, values);
}
catch(e) {
if (!ignoreFailure) {
@ -227,10 +210,10 @@ MarionetteServerConnection.prototype = {
let error = e;
switch(e.result) {
case Components.results.NS_ERROR_FAILURE:
error = new FrameSendFailureError(this.currentRemoteFrame);
error = new FrameSendFailureError(this.curBrowser.frameManager.currentRemoteFrame);
break;
case Components.results.NS_ERROR_NOT_INITIALIZED:
error = new FrameSendNotInitializedError(this.currentRemoteFrame);
error = new FrameSendNotInitializedError(this.curBrowser.frameManager.currentRemoteFrame);
break;
default:
break;
@ -247,44 +230,6 @@ MarionetteServerConnection.prototype = {
return success;
},
/**
* Adds listeners for messages from content frame scripts.
*
* @param object messageManager
* The messageManager object (ChromeMessageBroadcaster or ChromeMessageSender)
* to which the listeners should be added.
*/
addMessageManagerListeners: function MDA_addMessageManagerListeners(messageManager) {
messageManager.addMessageListener("Marionette:ok", this);
messageManager.addMessageListener("Marionette:done", this);
messageManager.addMessageListener("Marionette:error", this);
messageManager.addMessageListener("Marionette:log", this);
messageManager.addMessageListener("Marionette:shareData", this);
messageManager.addMessageListener("Marionette:register", this);
messageManager.addMessageListener("Marionette:runEmulatorCmd", this);
messageManager.addMessageListener("Marionette:switchToFrame", this);
messageManager.addMessageListener("Marionette:switchedToFrame", this);
},
/**
* Removes listeners for messages from content frame scripts.
*
* @param object messageManager
* The messageManager object (ChromeMessageBroadcaster or ChromeMessageSender)
* from which the listeners should be removed.
*/
removeMessageManagerListeners: function MDA_removeMessageManagerListeners(messageManager) {
messageManager.removeMessageListener("Marionette:ok", this);
messageManager.removeMessageListener("Marionette:done", this);
messageManager.removeMessageListener("Marionette:error", this);
messageManager.removeMessageListener("Marionette:log", this);
messageManager.removeMessageListener("Marionette:shareData", this);
messageManager.removeMessageListener("Marionette:register", this);
messageManager.removeMessageListener("Marionette:runEmulatorCmd", this);
messageManager.removeMessageListener("Marionette:switchToFrame", this);
messageManager.removeMessageListener("Marionette:switchedToFrame", this);
},
logRequest: function MDA_logRequest(type, data) {
logger.debug("Got request: " + type + ", data: " + JSON.stringify(data) + ", id: " + this.command_id);
},
@ -429,7 +374,7 @@ MarionetteServerConnection.prototype = {
* Returns the unique server-assigned ID of the window
*/
addBrowser: function MDA_addBrowser(win) {
let browser = new BrowserObj(win);
let browser = new BrowserObj(win, this);
let winId = win.QueryInterface(Ci.nsIInterfaceRequestor).
getInterface(Ci.nsIDOMWindowUtils).outerWindowID;
winId = winId + ((appName == "B2G") ? '-b2g' : '');
@ -552,7 +497,6 @@ MarionetteServerConnection.prototype = {
}
}
this.switchToGlobalMessageManager();
if (!Services.prefs.getBoolPref("marionette.contentListener")) {
waitForWindow.call(this);
@ -566,6 +510,7 @@ MarionetteServerConnection.prototype = {
else {
this.sendError("Session already running", 500, null, this.command_id);
}
this.switchToGlobalMessageManager();
},
getSessionCapabilities: function MDA_getSessionCapabilities(){
@ -1307,7 +1252,7 @@ MarionetteServerConnection.prototype = {
}
else {
if ((!aRequest.value) && (!aRequest.element) &&
(this.currentRemoteFrame !== null)) {
(this.curBrowser.frameManager.currentRemoteFrame !== null)) {
// We're currently using a ChromeMessageSender for a remote frame, so this
// request indicates we need to switch back to the top-level (parent) frame.
// We'll first switch to the parent's (global) ChromeMessageBroadcaster, so
@ -1977,15 +1922,14 @@ MarionetteServerConnection.prototype = {
while (winEnum.hasMoreElements()) {
winEnum.getNext().messageManager.removeDelayedFrameScript(FRAME_SCRIPT);
}
this.curBrowser.frameManager.removeMessageManagerListeners(this.globalMessageManager);
}
this.removeMessageManagerListeners(this.globalMessageManager);
this.switchToGlobalMessageManager();
// reset frame to the top-most frame
this.curFrame = null;
if (this.mainFrame) {
this.mainFrame.focus();
}
this.curBrowser = null;
try {
this.importedScripts.remove(false);
}
@ -2133,60 +2077,50 @@ MarionetteServerConnection.prototype = {
this.sendToClient(message.json, -1);
break;
case "Marionette:switchToFrame":
// Switch to a remote frame.
let frameWindow = Services.wm.getOuterWindowWithId(message.json.win);
let thisFrame = frameWindow.document.getElementsByTagName("iframe")[message.json.frame];
let mm = thisFrame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager;
// See if this frame already has our frame script loaded in it; if so,
// just wake it up.
for (let i = 0; i < remoteFrames.length; i++) {
let frame = remoteFrames[i];
if ((frame.messageManager == mm)) {
this.currentRemoteFrame = frame;
this.currentRemoteFrame.command_id = message.json.command_id;
this.messageManager = frame.messageManager;
this.addMessageManagerListeners(this.messageManager);
this.messageManager.sendAsyncMessage("Marionette:restart", {});
return;
}
}
// Load the frame script in this frame, and set the frame's ChromeMessageSender
// as the active message manager.
this.addMessageManagerListeners(mm);
mm.loadFrameScript(FRAME_SCRIPT, true);
this.messageManager = mm;
let aFrame = new MarionetteRemoteFrame(message.json.win, message.json.frame);
aFrame.messageManager = this.messageManager;
aFrame.command_id = message.json.command_id;
remoteFrames.push(aFrame);
this.currentRemoteFrame = aFrame;
this.curBrowser.frameManager.switchToRemoteFrame(message);
this.messageManager = this.curBrowser.frameManager.currentRemoteFrame.messageManager;
break;
case "Marionette:switchToModalOrigin":
this.curBrowser.frameManager.switchToModalOrigin(message);
this.messageManager = this.curBrowser.frameManager.currentRemoteFrame.messageManager;
break;
case "Marionette:switchedToFrame":
logger.info("Switched to frame: " + JSON.stringify(message.json));
this.currentFrameElement = message.json.frameValue;
if (message.json.restorePrevious) {
this.currentFrameElement = this.previousFrameElement;
}
else {
if (message.json.storePrevious) {
// we don't arbitrarily save previousFrameElement, since
// we allow frame switching after modals appear, which would
// override this value and we'd lose our reference
this.previousFrameElement = this.currentFrameElement;
}
this.currentFrameElement = message.json.frameValue;
}
break;
case "Marionette:register":
// This code processes the content listener's registration information
// and either accepts the listener, or ignores it
let nullPrevious = (this.curBrowser.curFrameId == null);
let listenerWindow =
Services.wm.getOuterWindowWithId(message.json.value);
Services.wm.getOuterWindowWithId(message.json.value);
//go in here if we're already in a remote frame.
if (!listenerWindow || (listenerWindow.location.href != message.json.href) &&
(this.currentRemoteFrame !== null)) {
(this.curBrowser.frameManager.currentRemoteFrame !== null)) {
// The outerWindowID from an OOP frame will not be meaningful to
// the parent process here, since each process maintains its own
// independent window list. So, it will either be null (!listenerWindow)
// if we're already in a remote frame,
// or it will point to some random window, which will hopefully
// cause an href mistmach. Currently this only happens
// cause an href mismatch. Currently this only happens
// in B2G for OOP frames registered in Marionette:switchToFrame, so
// we'll acknowledge the switchToFrame message here.
// XXX: Should have a better way of determining that this message
// is from a remote frame.
this.currentRemoteFrame.targetFrameId = this.generateFrameId(message.json.value);
this.sendOk(this.currentRemoteFrame.command_id);
this.curBrowser.frameManager.currentRemoteFrame.targetFrameId = this.generateFrameId(message.json.value);
this.sendOk(this.command_id);
}
let browserType;
@ -2197,6 +2131,7 @@ MarionetteServerConnection.prototype = {
}
let reg = {};
if (!browserType || browserType != "content") {
//curBrowser holds all the registered frames in knownFrames
reg.id = this.curBrowser.register(this.generateFrameId(message.json.value),
listenerWindow);
}
@ -2285,11 +2220,11 @@ MarionetteServerConnection.prototype.requestTypes = {
* The window whose browser needs to be accessed
*/
function BrowserObj(win) {
function BrowserObj(win, server) {
this.DESKTOP = "desktop";
this.B2G = "B2G";
this.browser;
this.tab = null;
this.tab = null; //Holds a reference to the created tab, if any
this.window = win;
this.knownFrames = [];
this.curFrameId = null;
@ -2298,6 +2233,10 @@ function BrowserObj(win) {
this.newSession = true; //used to set curFrameId upon new session
this.elementManager = new ElementManager([SELECTOR, NAME, LINK_TEXT, PARTIAL_LINK_TEXT]);
this.setBrowser(win);
this.frameManager = new FrameManager(server); //We should have one FM per BO so that we can handle modals in each Browser
//register all message listeners
this.frameManager.addMessageManagerListeners(server.messageManager);
}
BrowserObj.prototype = {