Mypal/testing/marionette/frame.js

261 lines
9.9 KiB
JavaScript

/* 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/. */
"use strict";
const {classes: Cc, interfaces: Ci, results: Cr, utils: Cu} = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
this.EXPORTED_SYMBOLS = ["frame"];
this.frame = {};
const FRAME_SCRIPT = "chrome://marionette/content/listener.js";
// list of OOP frames that has the frame script loaded
var remoteFrames = [];
/**
* An object representing a frame that Marionette has loaded a
* frame script in.
*/
frame.RemoteFrame = function (windowId, frameId) {
// outerWindowId relative to main process
this.windowId = windowId;
// actual frame relative to the windowId's frames list
this.frameId = frameId;
// assigned frame ID, used for messaging
this.targetFrameId = this.frameId;
// list of OOP frames that has the frame script loaded
this.remoteFrames = [];
};
/**
* The FrameManager will maintain the list of Out Of Process (OOP)
* frames and will handle frame switching between them.
*
* It handles explicit frame switching (switchToFrame), and implicit
* frame switching, which occurs when a modal dialog is triggered in B2G.
*
* @param {GeckoDriver} driver
* Reference to the driver instance.
*/
frame.Manager = class {
constructor(driver) {
// messageManager maintains the messageManager
// for the current process' chrome frame or the global message manager
// holds a member of the remoteFrames (for an OOP frame)
// or null (for the main process)
this.currentRemoteFrame = null;
// frame we'll need to restore once interrupt is gone
this.previousRemoteFrame = null;
// set to true when we have been interrupted by a modal
this.handledModal = false;
this.driver = driver;
}
/**
* Receives all messages from content messageManager.
*/
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) {
// get the frame window of the interrupted frame
let interruptedFrame = Services.wm.getOuterWindowWithId(
this.previousRemoteFrame.windowId);
if (this.previousRemoteFrame.frameId !== null) {
// find OOP frame
let iframes = interruptedFrame.document.getElementsByTagName("iframe");
interruptedFrame = iframes[this.previousRemoteFrame.frameId];
}
// check if the interrupted frame is the same as the calling frame
if (interruptedFrame.src == message.target.src) {
return {value: this.handledModal};
}
// we get here if previousRemoteFrame and currentRemoteFrame are null,
// i.e. 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
} else if (this.currentRemoteFrame === null) {
return {value: this.handledModal};
}
return {value: false};
// handleModal is called when we need to switch frames to the main
// process due to a modal dialog interrupt
case "MarionetteFrame:handleModal":
// 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.get());
// 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.driver.messageManager = Cc["@mozilla.org/globalmessagemanager;1"]
.getService(Ci.nsIMessageBroadcaster);
}
this.handledModal = true;
this.driver.sendOk(this.driver.command_id);
return {value: isLocal};
case "MarionetteFrame:getCurrentFrameId":
if (this.currentRemoteFrame !== null) {
return this.currentRemoteFrame.frameId;
}
}
}
getOopFrame(winId, frameId) {
// get original frame window
let outerWin = Services.wm.getOuterWindowWithId(winId);
// find the OOP frame
let f = outerWin.document.getElementsByTagName("iframe")[frameId];
return f;
}
getFrameMM(winId, frameId) {
let oopFrame = this.getOopFrame(winId, frameId);
let mm = oopFrame.QueryInterface(Ci.nsIFrameLoaderOwner)
.frameLoader.messageManager;
return mm;
}
/**
* Switch to OOP frame. We're handling this here so we can maintain
* a list of remote frames.
*/
switchToFrame(winId, frameId) {
let oopFrame = this.getOopFrame(winId, frameId);
let mm = this.getFrameMM(winId, frameId);
// 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 f = remoteFrames[i];
let fmm = f.messageManager.get();
try {
fmm.sendAsyncMessage("aliveCheck", {});
} catch (e) {
if (e.result == Cr.NS_ERROR_NOT_INITIALIZED) {
remoteFrames.splice(i--, 1);
continue;
}
}
if (fmm == mm) {
this.currentRemoteFrame = f;
this.addMessageManagerListeners(mm);
mm.sendAsyncMessage("Marionette:restart");
return oopFrame.id;
}
}
// 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 driver will listen to.
this.addMessageManagerListeners(mm);
let f = new frame.RemoteFrame(winId, frameId);
f.messageManager = Cu.getWeakReference(mm);
remoteFrames.push(f);
this.currentRemoteFrame = f;
mm.loadFrameScript(FRAME_SCRIPT, true, true);
return oopFrame.id;
}
/*
* This function handles switching back to the frame that was
* interrupted by the modal dialog. It gets called by the interrupted
* frame once the dialog is dismissed and the frame resumes its process.
*/
switchToModalOrigin() {
// only handle this if we indeed switched out of the modal's
// originating frame
if (this.previousRemoteFrame !== null) {
this.currentRemoteFrame = this.previousRemoteFrame;
let mm = this.currentRemoteFrame.messageManager.get();
this.addMessageManagerListeners(mm);
}
this.handledModal = false;
}
/**
* Adds message listeners to the driver, 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 {nsIMessageListenerManager} mm
* The message manager object, typically
* ChromeMessageBroadcaster or ChromeMessageSender.
*/
addMessageManagerListeners(mm) {
mm.addWeakMessageListener("Marionette:ok", this.driver);
mm.addWeakMessageListener("Marionette:done", this.driver);
mm.addWeakMessageListener("Marionette:error", this.driver);
mm.addWeakMessageListener("Marionette:emitTouchEvent", this.driver);
mm.addWeakMessageListener("Marionette:log", this.driver);
mm.addWeakMessageListener("Marionette:shareData", this.driver);
mm.addWeakMessageListener("Marionette:switchToModalOrigin", this.driver);
mm.addWeakMessageListener("Marionette:switchedToFrame", this.driver);
mm.addWeakMessageListener("Marionette:getVisibleCookies", this.driver);
mm.addWeakMessageListener("Marionette:getImportedScripts", this.driver.importedScripts);
mm.addWeakMessageListener("Marionette:register", this.driver);
mm.addWeakMessageListener("Marionette:listenersAttached", this.driver);
mm.addWeakMessageListener("MarionetteFrame:handleModal", this);
mm.addWeakMessageListener("MarionetteFrame:getCurrentFrameId", this);
mm.addWeakMessageListener("MarionetteFrame:getInterruptedState", this);
}
/**
* Removes listeners for messages from content frame scripts.
* We do not remove the MarionetteFrame:getInterruptedState or
* the Marionette: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 {nsIMessageListenerManager} mm
* The message manager object, typically
* ChromeMessageBroadcaster or ChromeMessageSender.
*/
removeMessageManagerListeners(mm) {
mm.removeWeakMessageListener("Marionette:ok", this.driver);
mm.removeWeakMessageListener("Marionette:done", this.driver);
mm.removeWeakMessageListener("Marionette:error", this.driver);
mm.removeWeakMessageListener("Marionette:log", this.driver);
mm.removeWeakMessageListener("Marionette:shareData", this.driver);
mm.removeWeakMessageListener("Marionette:switchedToFrame", this.driver);
mm.removeWeakMessageListener("Marionette:getVisibleCookies", this.driver);
mm.removeWeakMessageListener("Marionette:getImportedScripts", this.driver.importedScripts);
mm.removeWeakMessageListener("Marionette:listenersAttached", this.driver);
mm.removeWeakMessageListener("Marionette:register", this.driver);
mm.removeWeakMessageListener("MarionetteFrame:handleModal", this);
mm.removeWeakMessageListener("MarionetteFrame:getCurrentFrameId", this);
}
};
frame.Manager.prototype.QueryInterface = XPCOMUtils.generateQI(
[Ci.nsIMessageListener, Ci.nsISupportsWeakReference]);