123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- /* 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]);
|