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 +};