summaryrefslogtreecommitdiffstats
path: root/testing/marionette/frame.js
diff options
context:
space:
mode:
Diffstat (limited to 'testing/marionette/frame.js')
-rw-r--r--testing/marionette/frame.js260
1 files changed, 260 insertions, 0 deletions
diff --git a/testing/marionette/frame.js b/testing/marionette/frame.js
new file mode 100644
index 000000000..fc713eb97
--- /dev/null
+++ b/testing/marionette/frame.js
@@ -0,0 +1,260 @@
+/* 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]);