зеркало из https://github.com/mozilla/gecko-dev.git
Bug 775116 - Fix frame switching to work with id, name, and remote frames, r=mdas, DONTBUILD(NPOTB)
This commit is contained in:
Родитель
4bf64bd643
Коммит
4ea382dcba
|
@ -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,9 +105,22 @@ 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
|
||||
* for each connection and manages all chrome and browser based calls. It
|
||||
* mediates content calls by issuing appropriate messages to the content process.
|
||||
*/
|
||||
function MarionetteDriverActor(aConnection)
|
||||
|
@ -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,16 +183,55 @@ 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
|
||||
*
|
||||
*
|
||||
* @param object msg
|
||||
* Response to send back to client
|
||||
* @param string command_id
|
||||
|
@ -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
|
||||
|
|
Загрузка…
Ссылка в новой задаче