Bug 775116 - Fix frame switching to work with id, name, and remote frames, r=mdas, DONTBUILD(NPOTB)

This commit is contained in:
Jonathan Griffin 2012-09-28 16:16:22 -07:00
Родитель 4bf64bd643
Коммит 4ea382dcba
2 изменённых файлов: 190 добавлений и 51 удалений

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

@ -8,6 +8,8 @@
* Gecko-specific actors.
*/
const FRAME_SCRIPT = "chrome://marionette/content/marionette-listener.js";
let {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
let loader = Cc["@mozilla.org/moz/jssubscript-loader;1"]
@ -94,8 +96,8 @@ MarionetteRootActor.prototype = {
getMarionetteID: function MRA_getMarionette() {
return { "from": "root",
"id": this._marionetteActor.actorID } ;
},
}
}
};
// register the calls
MarionetteRootActor.prototype.requestTypes = {
@ -103,6 +105,19 @@ MarionetteRootActor.prototype.requestTypes = {
"sayHello": MarionetteRootActor.prototype.sayHello
};
/**
* 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;
}
// persistent list of remote frames that Marionette has loaded a frame script in
let remoteFrames = [];
/**
* This actor is responsible for all marionette API calls. It gets created
* for each connection and manages all chrome and browser based calls. It
@ -114,8 +129,9 @@ function MarionetteDriverActor(aConnection)
.getService(Ci.nsIUUIDGenerator);
this.conn = aConnection;
this.messageManager = Cc["@mozilla.org/globalmessagemanager;1"]
.getService(Ci.nsIMessageBroadcaster);
this.globalMessageManager = Cc["@mozilla.org/globalmessagemanager;1"]
.getService(Ci.nsIMessageBroadcaster);
this.messageManager = this.globalMessageManager;
this.browsers = {}; //holds list of BrowserObjs
this.curBrowser = null; // points to current browser
this.context = "content";
@ -127,16 +143,10 @@ function MarionetteDriverActor(aConnection)
this.mainFrame = null; //topmost chrome frame
this.curFrame = null; //subframe that currently has focus
this.importedScripts = FileUtils.getFile('TmpD', ['marionettescriptchrome']);
this.currentRemoteFrame = null; // a member of remoteFrames
//register all message listeners
this.messageManager.addMessageListener("Marionette:ok", this);
this.messageManager.addMessageListener("Marionette:done", this);
this.messageManager.addMessageListener("Marionette:error", this);
this.messageManager.addMessageListener("Marionette:log", this);
this.messageManager.addMessageListener("Marionette:shareData", this);
this.messageManager.addMessageListener("Marionette:register", this);
this.messageManager.addMessageListener("Marionette:goUrl", this);
this.messageManager.addMessageListener("Marionette:runEmulatorCmd", this);
this.addMessageManagerListeners(this.messageManager);
}
MarionetteDriverActor.prototype = {
@ -144,6 +154,26 @@ MarionetteDriverActor.prototype = {
//name of the actor
actorPrefix: "marionette",
/**
* Helper methods:
*/
/**
* Switches to the global ChromeMessageBroadcaster, potentially replacing a frame-specific
* ChromeMessageSender. Has no effect if the global ChromeMessageBroadcaster is already
* in use. If this replaces a frame-specific ChromeMessageSender, it removes the message
* listeners from that sender, and then puts the corresponding frame script "to sleep",
* which removes most of the message listeners from it as well.
*/
switchToGlobalMessageManager: function MDA_switchToGlobalMM() {
if (this.currentRemoteFrame !== null) {
this.removeMessageManagerListeners(this.messageManager);
this.sendAsync("sleepSession");
}
this.messageManager = this.globalMessageManager;
this.currentRemoteFrame = null;
},
/**
* Helper method to send async messages to the content listener
*
@ -153,12 +183,51 @@ MarionetteDriverActor.prototype = {
* Object to send to the listener
*/
sendAsync: function MDA_sendAsync(name, values) {
this.messageManager.broadcastAsyncMessage("Marionette:" + name + this.curBrowser.curFrameId, values);
if (this.currentRemoteFrame !== null) {
this.messageManager.sendAsyncMessage(
"Marionette:" + name + this.currentRemoteFrame.targetFrameId, values);
}
else {
this.messageManager.broadcastAsyncMessage(
"Marionette:" + name + this.curBrowser.curFrameId, values);
}
},
/**
* Helper methods:
* 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);
},
/**
* 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);
},
/**
* Generic method to pass a response to the client
@ -316,7 +385,7 @@ MarionetteDriverActor.prototype = {
whenBrowserStarted: function MDA_whenBrowserStarted(win, newSession) {
try {
if (!Services.prefs.getBoolPref("marionette.contentListener") || !newSession) {
this.curBrowser.loadFrameScript("chrome://marionette/content/marionette-listener.js", win);
this.curBrowser.loadFrameScript(FRAME_SCRIPT, win);
}
}
catch (e) {
@ -377,6 +446,8 @@ MarionetteDriverActor.prototype = {
}
}
this.switchToGlobalMessageManager();
if (!Services.prefs.getBoolPref("marionette.contentListener")) {
waitForWindow.call(this);
}
@ -968,6 +1039,14 @@ MarionetteDriverActor.prototype = {
}
}
else {
if ((aRequest.value == null) && (aRequest.element == null) &&
(this.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
// we send the message to the right listener.
this.switchToGlobalMessageManager();
}
this.sendAsync("switchToFrame", aRequest);
}
},
@ -1302,7 +1381,7 @@ MarionetteDriverActor.prototype = {
}
try{
this.messageManager.removeDelayedFrameScript("chrome://marionette/content/marionette-listener.js");
this.messageManager.removeDelayedFrameScript(FRAME_SCRIPT);
this.getCurrentWindow().close();
this.sendOk();
}
@ -1324,7 +1403,7 @@ MarionetteDriverActor.prototype = {
deleteSession: function MDA_deleteSession() {
if (this.curBrowser != null) {
if (appName == "B2G") {
this.messageManager.broadcastAsyncMessage("Marionette:sleepSession" + this.curBrowser.mainContentId, {});
this.globalMessageManager.broadcastAsyncMessage("Marionette:sleepSession" + this.curBrowser.mainContentId, {});
this.curBrowser.knownFrames.splice(this.curBrowser.knownFrames.indexOf(this.curBrowser.mainContentId), 1);
}
else {
@ -1335,23 +1414,17 @@ MarionetteDriverActor.prototype = {
//delete session in each frame in each browser
for (let win in this.browsers) {
for (let i in this.browsers[win].knownFrames) {
this.messageManager.broadcastAsyncMessage("Marionette:deleteSession" + this.browsers[win].knownFrames[i], {});
this.globalMessageManager.broadcastAsyncMessage("Marionette:deleteSession" + this.browsers[win].knownFrames[i], {});
}
}
let winEnum = this.getWinEnumerator();
while (winEnum.hasMoreElements()) {
winEnum.getNext().messageManager.removeDelayedFrameScript("chrome://marionette/content/marionette-listener.js");
winEnum.getNext().messageManager.removeDelayedFrameScript(FRAME_SCRIPT);
}
}
this.sendOk();
this.messageManager.removeMessageListener("Marionette:ok", this);
this.messageManager.removeMessageListener("Marionette:done", this);
this.messageManager.removeMessageListener("Marionette:error", this);
this.messageManager.removeMessageListener("Marionette:log", this);
this.messageManager.removeMessageListener("Marionette:shareData", this);
this.messageManager.removeMessageListener("Marionette:register", this);
this.messageManager.removeMessageListener("Marionette:goUrl", this);
this.messageManager.removeMessageListener("Marionette:runEmulatorCmd", this);
this.removeMessageManagerListeners(this.globalMessageManager);
this.switchToGlobalMessageManager();
this.curBrowser = null;
try {
this.importedScripts.remove(false);
@ -1415,6 +1488,15 @@ MarionetteDriverActor.prototype = {
}
},
/**
* Helper function to convert an outerWindowID into a UID that Marionette
* tracks.
*/
generateFrameId: function MDA_generateFrameId(id) {
let uid = id + (appName == "B2G" ? "-b2g" : "");
return uid;
},
/**
* Receives all messages from content messageManager
*/
@ -1449,12 +1531,58 @@ MarionetteDriverActor.prototype = {
case "Marionette:runEmulatorCmd":
this.sendToClient(message.json);
break;
case "Marionette:switchToFrame":
// Switch to a remote frame.
for (let i = 0; i < remoteFrames.length; i++) {
let frame = remoteFrames[i];
if ((frame.windowId == message.json.win) && (frame.frameId == message.json.frame)) {
// The frame script has already been loaded in this frame, so just wake it up.
this.currentRemoteFrame = frame;
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.
let thisWin = this.getCurrentWindow();
let frameWindow = thisWin.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.getOuterWindowWithId(message.json.win);
let thisFrame = frameWindow.document.getElementsByTagName("iframe")[message.json.frame];
let mm = thisFrame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager
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;
remoteFrames.push(aFrame);
this.currentRemoteFrame = aFrame;
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 curWin = this.getCurrentWindow();
let frameObject = curWin.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils).getOuterWindowWithId(message.json.value);
let listenerWindow = curWin.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowUtils)
.getOuterWindowWithId(message.json.value);
if (listenerWindow.location.href != message.json.href) {
// If there is a mismatch between the calculated href and the one
// sent from the frame script, it means that the frame script is
// running in a separate process. 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.sendOk();
this.currentRemoteFrame.targetFrameId = this.generateFrameId(message.json.value);
}
let browserType;
try {
browserType = message.target.getAttribute("type");
@ -1463,9 +1591,10 @@ MarionetteDriverActor.prototype = {
}
let reg;
if (!browserType || browserType != "content") {
reg = this.curBrowser.register(message.json.value, message.json.href);
reg = this.curBrowser.register(this.generateFrameId(message.json.value),
message.json.href);
}
this.curBrowser.elementManager.seenItems[reg] = frameObject; //add to seenItems
this.curBrowser.elementManager.seenItems[reg] = listenerWindow; //add to seenItems
if (nullPrevious && (this.curBrowser.curFrameId != null)) {
this.sendAsync("newSession", {B2G: (appName == "B2G")});
if (this.curBrowser.newSession) {
@ -1545,8 +1674,6 @@ function BrowserObj(win) {
this.curFrameId = null;
this.startPage = "about:blank";
this.mainContentId = null; // used in B2G to identify the homescreen content page
this.messageManager = Cc["@mozilla.org/globalmessagemanager;1"]
.getService(Ci.nsIMessageBroadcaster);
this.newSession = true; //used to set curFrameId upon new session
this.elementManager = new ElementManager([SELECTOR, NAME, LINK_TEXT, PARTIAL_LINK_TEXT]);
this.setBrowser(win);
@ -1642,20 +1769,20 @@ BrowserObj.prototype = {
* if it is not already assigned, and if a) we already have a session
* or b) we're starting a new session and it is the right start frame.
*
* @param string id
* frame id
* @param string uid
* frame uid
* @param string href
* frame's href
*/
register: function BO_register(id, href) {
let uid = id + ((appName == "B2G") ? '-b2g' : '');
register: function BO_register(uid, href) {
if (this.curFrameId == null) {
if ((!this.newSession) || (this.newSession && ((appName != "Firefox") || href.indexOf(this.startPage) > -1))) {
if ((!this.newSession) || (this.newSession &&
((appName != "Firefox") || href.indexOf(this.startPage) > -1))) {
this.curFrameId = uid;
this.mainContentId = uid;
}
}
this.knownFrames.push(uid); //used to deletesessions
this.knownFrames.push(uid); //used to delete sessions
return uid;
},
}

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

@ -747,28 +747,31 @@ function switchToFrame(msg) {
}
}
}
let frames = curWindow.document.getElementsByTagName("iframe");
switch(typeof(msg.json.value)) {
case "string" :
let foundById = null;
let numFrames = curWindow.frames.length;
for (let i = 0; i < numFrames; i++) {
for (let i = 0; i < frames.length; i++) {
//give precedence to name
let frame = curWindow.frames[i];
let frameElement = frame.frameElement;
if (frameElement.name == msg.json.value) {
let frame = frames[i];
let name = utils.getElementAttribute(frame, 'name');
let id = utils.getElementAttribute(frame, 'id');
if (name == msg.json.value) {
foundFrame = i;
break;
} else if ((foundById == null) && (frameElement.id == msg.json.value)) {
} else if ((foundById == null) && (id == msg.json.value)) {
foundById = i;
}
}
if ((foundFrame == null) && (foundById != null)) {
foundFrame = foundById;
curWindow = frames[foundFrame];
}
break;
case "number":
if (curWindow.frames[msg.json.value] != undefined) {
if (frames[msg.json.value] != undefined) {
foundFrame = msg.json.value;
curWindow = frames[foundFrame];
}
break;
}
@ -776,11 +779,20 @@ function switchToFrame(msg) {
sendError("Unable to locate frame: " + msg.json.value, 8, null);
return;
}
curWindow = curWindow.frames[foundFrame];
curWindow.focus();
sendOk();
sandbox = null;
if (curWindow.contentWindow == null) {
// The frame we want to switch to is a remote frame; notify our parent to handle
// the switch.
curWindow = content;
sendToServer('Marionette:switchToFrame', {win: winUtil.outerWindowID, frame: foundFrame});
}
else {
curWindow = curWindow.contentWindow;
curWindow.focus();
sendOk();
}
}
// emulator callbacks