testing/marionette/marionette-frame-manager.js

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/testing/marionette/marionette-frame-manager.js	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,207 @@
     1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.7 +
     1.8 +this.EXPORTED_SYMBOLS = [
     1.9 +  "FrameManager"
    1.10 +];
    1.11 +
    1.12 +let FRAME_SCRIPT = "chrome://marionette/content/marionette-listener.js";
    1.13 +Cu.import("resource://gre/modules/Services.jsm");
    1.14 +Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    1.15 +
    1.16 +Cu.import("resource://gre/modules/Log.jsm");
    1.17 +let logger = Log.repository.getLogger("Marionette");
    1.18 +
    1.19 +//list of OOP frames that has the frame script loaded
    1.20 +let remoteFrames = [];
    1.21 +
    1.22 +/**
    1.23 + * An object representing a frame that Marionette has loaded a
    1.24 + * frame script in.
    1.25 + */
    1.26 +function MarionetteRemoteFrame(windowId, frameId) {
    1.27 +  this.windowId = windowId; //outerWindowId relative to main process
    1.28 +  this.frameId = frameId ? frameId : null; //actual frame relative to windowId's frames list
    1.29 +  this.targetFrameId = this.frameId; //assigned FrameId, used for messaging
    1.30 +};
    1.31 +
    1.32 +/**
    1.33 + * The FrameManager will maintain the list of Out Of Process (OOP) frames and will handle
    1.34 + * frame switching between them.
    1.35 + * It handles explicit frame switching (switchToFrame), and implicit frame switching, which
    1.36 + * occurs when a modal dialog is triggered in B2G.
    1.37 + *
    1.38 + */
    1.39 +this.FrameManager = function FrameManager(server) {
    1.40 +  //messageManager maintains the messageManager for the current process' chrome frame or the global message manager
    1.41 +  this.currentRemoteFrame = null; //holds a member of remoteFrames (for an OOP frame) or null (for the main process)
    1.42 +  this.previousRemoteFrame = null; //frame we'll need to restore once interrupt is gone
    1.43 +  this.handledModal = false; //set to true when we have been interrupted by a modal
    1.44 +  this.server = server; // a reference to the marionette server
    1.45 +};
    1.46 +
    1.47 +FrameManager.prototype = {
    1.48 +  QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener,
    1.49 +                                         Ci.nsISupportsWeakReference]),
    1.50 +
    1.51 +  /**
    1.52 +   * Receives all messages from content messageManager
    1.53 +   */
    1.54 +  receiveMessage: function FM_receiveMessage(message) {
    1.55 +    switch (message.name) {
    1.56 +      case "MarionetteFrame:getInterruptedState":
    1.57 +        // This will return true if the calling frame was interrupted by a modal dialog
    1.58 +        if (this.previousRemoteFrame) {
    1.59 +          let interruptedFrame = Services.wm.getOuterWindowWithId(this.previousRemoteFrame.windowId);//get the frame window of the interrupted frame
    1.60 +          if (this.previousRemoteFrame.frameId != null) {
    1.61 +            interruptedFrame = interruptedFrame.document.getElementsByTagName("iframe")[this.previousRemoteFrame.frameId]; //find the OOP frame
    1.62 +          }
    1.63 +          //check if the interrupted frame is the same as the calling frame
    1.64 +          if (interruptedFrame.src == message.target.src) {
    1.65 +            return {value: this.handledModal};
    1.66 +          }
    1.67 +        }
    1.68 +        else if (this.currentRemoteFrame == null) {
    1.69 +          // we get here if previousRemoteFrame and currentRemoteFrame are null, ie: 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.
    1.70 +          return {value: this.handledModal};
    1.71 +        }
    1.72 +        return {value: false};
    1.73 +      case "MarionetteFrame:handleModal":
    1.74 +        /*
    1.75 +         * handleModal is called when we need to switch frames to the main process due to a modal dialog interrupt.
    1.76 +         */
    1.77 +        // If previousRemoteFrame was set, that means we switched into a remote frame.
    1.78 +        // If this is the case, then we want to switch back into the system frame.
    1.79 +        // If it isn't the case, then we're in a non-OOP environment, so we don't need to handle remote frames
    1.80 +        let isLocal = true;
    1.81 +        if (this.currentRemoteFrame != null) {
    1.82 +          isLocal = false;
    1.83 +          this.removeMessageManagerListeners(this.currentRemoteFrame.messageManager.get());
    1.84 +          //store the previous frame so we can switch back to it when the modal is dismissed
    1.85 +          this.previousRemoteFrame = this.currentRemoteFrame;
    1.86 +          //by setting currentRemoteFrame to null, it signifies we're in the main process
    1.87 +          this.currentRemoteFrame = null;
    1.88 +          this.server.messageManager = Cc["@mozilla.org/globalmessagemanager;1"]
    1.89 +                                       .getService(Ci.nsIMessageBroadcaster);
    1.90 +        }
    1.91 +        this.handledModal = true;
    1.92 +        this.server.sendOk(this.server.command_id);
    1.93 +        return {value: isLocal};
    1.94 +      case "MarionetteFrame:getCurrentFrameId":
    1.95 +        if (this.currentRemoteFrame != null) {
    1.96 +          return this.currentRemoteFrame.frameId;
    1.97 +        }
    1.98 +    }
    1.99 +  },
   1.100 +
   1.101 +  //This is just 'switch to OOP frame'. We're handling this here so we can maintain a list of remoteFrames. 
   1.102 +  switchToFrame: function FM_switchToFrame(message) {
   1.103 +    // Switch to a remote frame.
   1.104 +    let frameWindow = Services.wm.getOuterWindowWithId(message.json.win); //get the original frame window
   1.105 +    let oopFrame = frameWindow.document.getElementsByTagName("iframe")[message.json.frame]; //find the OOP frame
   1.106 +    let mm = oopFrame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager; //get the OOP frame's mm
   1.107 +
   1.108 +    // See if this frame already has our frame script loaded in it; if so,
   1.109 +    // just wake it up.
   1.110 +    for (let i = 0; i < remoteFrames.length; i++) {
   1.111 +      let frame = remoteFrames[i];
   1.112 +      let frameMessageManager = frame.messageManager.get();
   1.113 +      logger.info("trying remote frame " + i);
   1.114 +      try {
   1.115 +        frameMessageManager.sendAsyncMessage("aliveCheck", {});
   1.116 +      }
   1.117 +      catch(e) {
   1.118 +        if (e.result ==  Components.results.NS_ERROR_NOT_INITIALIZED) {
   1.119 +          logger.info("deleting frame");
   1.120 +          remoteFrames.splice(i, 1);
   1.121 +          continue;
   1.122 +        }
   1.123 +      }
   1.124 +      if (frameMessageManager == mm) {
   1.125 +        this.currentRemoteFrame = frame;
   1.126 +        this.addMessageManagerListeners(mm);
   1.127 +        mm.sendAsyncMessage("Marionette:restart", {});
   1.128 +        return;
   1.129 +      }
   1.130 +    }
   1.131 +
   1.132 +    // If we get here, then we need to load the frame script in this frame, 
   1.133 +    // and set the frame's ChromeMessageSender as the active message manager the server will listen to
   1.134 +    this.addMessageManagerListeners(mm);
   1.135 +    logger.info("frame-manager load script: " + mm.toString());
   1.136 +    mm.loadFrameScript(FRAME_SCRIPT, true, true);
   1.137 +    let aFrame = new MarionetteRemoteFrame(message.json.win, message.json.frame);
   1.138 +    aFrame.messageManager = Cu.getWeakReference(mm);
   1.139 +    remoteFrames.push(aFrame);
   1.140 +    this.currentRemoteFrame = aFrame;
   1.141 +  },
   1.142 +
   1.143 +  /*
   1.144 +   * This function handles switching back to the frame that was interrupted by the modal dialog.
   1.145 +   * This function gets called by the interrupted frame once the dialog is dismissed and the frame resumes its process
   1.146 +   */
   1.147 +  switchToModalOrigin: function FM_switchToModalOrigin() {
   1.148 +    //only handle this if we indeed switched out of the modal's originating frame
   1.149 +    if (this.previousRemoteFrame != null) {
   1.150 +      this.currentRemoteFrame = this.previousRemoteFrame;
   1.151 +      this.addMessageManagerListeners(this.currentRemoteFrame.messageManager.get());
   1.152 +    }
   1.153 +    this.handledModal = false;
   1.154 +  },
   1.155 +
   1.156 +  /**
   1.157 +   * Adds message listeners to the server, listening for messages from content frame scripts.
   1.158 +   * It also adds a "MarionetteFrame:getInterruptedState" message listener to the FrameManager,
   1.159 +   * so the frame manager's state can be checked by the frame
   1.160 +   *
   1.161 +   * @param object messageManager
   1.162 +   *        The messageManager object (ChromeMessageBroadcaster or ChromeMessageSender)
   1.163 +   *        to which the listeners should be added.
   1.164 +   */
   1.165 +  addMessageManagerListeners: function MDA_addMessageManagerListeners(messageManager) {
   1.166 +    messageManager.addWeakMessageListener("Marionette:ok", this.server);
   1.167 +    messageManager.addWeakMessageListener("Marionette:done", this.server);
   1.168 +    messageManager.addWeakMessageListener("Marionette:error", this.server);
   1.169 +    messageManager.addWeakMessageListener("Marionette:emitTouchEvent", this.server);
   1.170 +    messageManager.addWeakMessageListener("Marionette:log", this.server);
   1.171 +    messageManager.addWeakMessageListener("Marionette:register", this.server);
   1.172 +    messageManager.addWeakMessageListener("Marionette:runEmulatorCmd", this.server);
   1.173 +    messageManager.addWeakMessageListener("Marionette:runEmulatorShell", this.server);
   1.174 +    messageManager.addWeakMessageListener("Marionette:shareData", this.server);
   1.175 +    messageManager.addWeakMessageListener("Marionette:switchToModalOrigin", this.server);
   1.176 +    messageManager.addWeakMessageListener("Marionette:switchToFrame", this.server);
   1.177 +    messageManager.addWeakMessageListener("Marionette:switchedToFrame", this.server);
   1.178 +    messageManager.addWeakMessageListener("MarionetteFrame:handleModal", this);
   1.179 +    messageManager.addWeakMessageListener("MarionetteFrame:getCurrentFrameId", this);
   1.180 +    messageManager.addWeakMessageListener("MarionetteFrame:getInterruptedState", this);
   1.181 +  },
   1.182 +
   1.183 +  /**
   1.184 +   * Removes listeners for messages from content frame scripts.
   1.185 +   * We do not remove the "MarionetteFrame:getInterruptedState" or the
   1.186 +   * "Marioentte:switchToModalOrigin" message listener,
   1.187 +   * because we want to allow all known frames to contact the frame manager so that
   1.188 +   * it can check if it was interrupted, and if so, it will call switchToModalOrigin
   1.189 +   * when its process gets resumed.
   1.190 +   *
   1.191 +   * @param object messageManager
   1.192 +   *        The messageManager object (ChromeMessageBroadcaster or ChromeMessageSender)
   1.193 +   *        from which the listeners should be removed.
   1.194 +   */
   1.195 +  removeMessageManagerListeners: function MDA_removeMessageManagerListeners(messageManager) {
   1.196 +    messageManager.removeWeakMessageListener("Marionette:ok", this.server);
   1.197 +    messageManager.removeWeakMessageListener("Marionette:done", this.server);
   1.198 +    messageManager.removeWeakMessageListener("Marionette:error", this.server);
   1.199 +    messageManager.removeWeakMessageListener("Marionette:log", this.server);
   1.200 +    messageManager.removeWeakMessageListener("Marionette:shareData", this.server);
   1.201 +    messageManager.removeWeakMessageListener("Marionette:register", this.server);
   1.202 +    messageManager.removeWeakMessageListener("Marionette:runEmulatorCmd", this.server);
   1.203 +    messageManager.removeWeakMessageListener("Marionette:runEmulatorShell", this.server);
   1.204 +    messageManager.removeWeakMessageListener("Marionette:switchToFrame", this.server);
   1.205 +    messageManager.removeWeakMessageListener("Marionette:switchedToFrame", this.server);
   1.206 +    messageManager.removeWeakMessageListener("MarionetteFrame:handleModal", this);
   1.207 +    messageManager.removeWeakMessageListener("MarionetteFrame:getCurrentFrameId", this);
   1.208 +  },
   1.209 +
   1.210 +};

mercurial