Wed, 31 Dec 2014 06:55:50 +0100
Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2
michael@0 | 1 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
michael@0 | 3 | * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 4 | |
michael@0 | 5 | this.EXPORTED_SYMBOLS = [ |
michael@0 | 6 | "FrameManager" |
michael@0 | 7 | ]; |
michael@0 | 8 | |
michael@0 | 9 | let FRAME_SCRIPT = "chrome://marionette/content/marionette-listener.js"; |
michael@0 | 10 | Cu.import("resource://gre/modules/Services.jsm"); |
michael@0 | 11 | Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
michael@0 | 12 | |
michael@0 | 13 | Cu.import("resource://gre/modules/Log.jsm"); |
michael@0 | 14 | let logger = Log.repository.getLogger("Marionette"); |
michael@0 | 15 | |
michael@0 | 16 | //list of OOP frames that has the frame script loaded |
michael@0 | 17 | let remoteFrames = []; |
michael@0 | 18 | |
michael@0 | 19 | /** |
michael@0 | 20 | * An object representing a frame that Marionette has loaded a |
michael@0 | 21 | * frame script in. |
michael@0 | 22 | */ |
michael@0 | 23 | function MarionetteRemoteFrame(windowId, frameId) { |
michael@0 | 24 | this.windowId = windowId; //outerWindowId relative to main process |
michael@0 | 25 | this.frameId = frameId ? frameId : null; //actual frame relative to windowId's frames list |
michael@0 | 26 | this.targetFrameId = this.frameId; //assigned FrameId, used for messaging |
michael@0 | 27 | }; |
michael@0 | 28 | |
michael@0 | 29 | /** |
michael@0 | 30 | * The FrameManager will maintain the list of Out Of Process (OOP) frames and will handle |
michael@0 | 31 | * frame switching between them. |
michael@0 | 32 | * It handles explicit frame switching (switchToFrame), and implicit frame switching, which |
michael@0 | 33 | * occurs when a modal dialog is triggered in B2G. |
michael@0 | 34 | * |
michael@0 | 35 | */ |
michael@0 | 36 | this.FrameManager = function FrameManager(server) { |
michael@0 | 37 | //messageManager maintains the messageManager for the current process' chrome frame or the global message manager |
michael@0 | 38 | this.currentRemoteFrame = null; //holds a member of remoteFrames (for an OOP frame) or null (for the main process) |
michael@0 | 39 | this.previousRemoteFrame = null; //frame we'll need to restore once interrupt is gone |
michael@0 | 40 | this.handledModal = false; //set to true when we have been interrupted by a modal |
michael@0 | 41 | this.server = server; // a reference to the marionette server |
michael@0 | 42 | }; |
michael@0 | 43 | |
michael@0 | 44 | FrameManager.prototype = { |
michael@0 | 45 | QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener, |
michael@0 | 46 | Ci.nsISupportsWeakReference]), |
michael@0 | 47 | |
michael@0 | 48 | /** |
michael@0 | 49 | * Receives all messages from content messageManager |
michael@0 | 50 | */ |
michael@0 | 51 | receiveMessage: function FM_receiveMessage(message) { |
michael@0 | 52 | switch (message.name) { |
michael@0 | 53 | case "MarionetteFrame:getInterruptedState": |
michael@0 | 54 | // This will return true if the calling frame was interrupted by a modal dialog |
michael@0 | 55 | if (this.previousRemoteFrame) { |
michael@0 | 56 | let interruptedFrame = Services.wm.getOuterWindowWithId(this.previousRemoteFrame.windowId);//get the frame window of the interrupted frame |
michael@0 | 57 | if (this.previousRemoteFrame.frameId != null) { |
michael@0 | 58 | interruptedFrame = interruptedFrame.document.getElementsByTagName("iframe")[this.previousRemoteFrame.frameId]; //find the OOP frame |
michael@0 | 59 | } |
michael@0 | 60 | //check if the interrupted frame is the same as the calling frame |
michael@0 | 61 | if (interruptedFrame.src == message.target.src) { |
michael@0 | 62 | return {value: this.handledModal}; |
michael@0 | 63 | } |
michael@0 | 64 | } |
michael@0 | 65 | else if (this.currentRemoteFrame == null) { |
michael@0 | 66 | // 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. |
michael@0 | 67 | return {value: this.handledModal}; |
michael@0 | 68 | } |
michael@0 | 69 | return {value: false}; |
michael@0 | 70 | case "MarionetteFrame:handleModal": |
michael@0 | 71 | /* |
michael@0 | 72 | * handleModal is called when we need to switch frames to the main process due to a modal dialog interrupt. |
michael@0 | 73 | */ |
michael@0 | 74 | // If previousRemoteFrame was set, that means we switched into a remote frame. |
michael@0 | 75 | // If this is the case, then we want to switch back into the system frame. |
michael@0 | 76 | // If it isn't the case, then we're in a non-OOP environment, so we don't need to handle remote frames |
michael@0 | 77 | let isLocal = true; |
michael@0 | 78 | if (this.currentRemoteFrame != null) { |
michael@0 | 79 | isLocal = false; |
michael@0 | 80 | this.removeMessageManagerListeners(this.currentRemoteFrame.messageManager.get()); |
michael@0 | 81 | //store the previous frame so we can switch back to it when the modal is dismissed |
michael@0 | 82 | this.previousRemoteFrame = this.currentRemoteFrame; |
michael@0 | 83 | //by setting currentRemoteFrame to null, it signifies we're in the main process |
michael@0 | 84 | this.currentRemoteFrame = null; |
michael@0 | 85 | this.server.messageManager = Cc["@mozilla.org/globalmessagemanager;1"] |
michael@0 | 86 | .getService(Ci.nsIMessageBroadcaster); |
michael@0 | 87 | } |
michael@0 | 88 | this.handledModal = true; |
michael@0 | 89 | this.server.sendOk(this.server.command_id); |
michael@0 | 90 | return {value: isLocal}; |
michael@0 | 91 | case "MarionetteFrame:getCurrentFrameId": |
michael@0 | 92 | if (this.currentRemoteFrame != null) { |
michael@0 | 93 | return this.currentRemoteFrame.frameId; |
michael@0 | 94 | } |
michael@0 | 95 | } |
michael@0 | 96 | }, |
michael@0 | 97 | |
michael@0 | 98 | //This is just 'switch to OOP frame'. We're handling this here so we can maintain a list of remoteFrames. |
michael@0 | 99 | switchToFrame: function FM_switchToFrame(message) { |
michael@0 | 100 | // Switch to a remote frame. |
michael@0 | 101 | let frameWindow = Services.wm.getOuterWindowWithId(message.json.win); //get the original frame window |
michael@0 | 102 | let oopFrame = frameWindow.document.getElementsByTagName("iframe")[message.json.frame]; //find the OOP frame |
michael@0 | 103 | let mm = oopFrame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager; //get the OOP frame's mm |
michael@0 | 104 | |
michael@0 | 105 | // See if this frame already has our frame script loaded in it; if so, |
michael@0 | 106 | // just wake it up. |
michael@0 | 107 | for (let i = 0; i < remoteFrames.length; i++) { |
michael@0 | 108 | let frame = remoteFrames[i]; |
michael@0 | 109 | let frameMessageManager = frame.messageManager.get(); |
michael@0 | 110 | logger.info("trying remote frame " + i); |
michael@0 | 111 | try { |
michael@0 | 112 | frameMessageManager.sendAsyncMessage("aliveCheck", {}); |
michael@0 | 113 | } |
michael@0 | 114 | catch(e) { |
michael@0 | 115 | if (e.result == Components.results.NS_ERROR_NOT_INITIALIZED) { |
michael@0 | 116 | logger.info("deleting frame"); |
michael@0 | 117 | remoteFrames.splice(i, 1); |
michael@0 | 118 | continue; |
michael@0 | 119 | } |
michael@0 | 120 | } |
michael@0 | 121 | if (frameMessageManager == mm) { |
michael@0 | 122 | this.currentRemoteFrame = frame; |
michael@0 | 123 | this.addMessageManagerListeners(mm); |
michael@0 | 124 | mm.sendAsyncMessage("Marionette:restart", {}); |
michael@0 | 125 | return; |
michael@0 | 126 | } |
michael@0 | 127 | } |
michael@0 | 128 | |
michael@0 | 129 | // If we get here, then we need to load the frame script in this frame, |
michael@0 | 130 | // and set the frame's ChromeMessageSender as the active message manager the server will listen to |
michael@0 | 131 | this.addMessageManagerListeners(mm); |
michael@0 | 132 | logger.info("frame-manager load script: " + mm.toString()); |
michael@0 | 133 | mm.loadFrameScript(FRAME_SCRIPT, true, true); |
michael@0 | 134 | let aFrame = new MarionetteRemoteFrame(message.json.win, message.json.frame); |
michael@0 | 135 | aFrame.messageManager = Cu.getWeakReference(mm); |
michael@0 | 136 | remoteFrames.push(aFrame); |
michael@0 | 137 | this.currentRemoteFrame = aFrame; |
michael@0 | 138 | }, |
michael@0 | 139 | |
michael@0 | 140 | /* |
michael@0 | 141 | * This function handles switching back to the frame that was interrupted by the modal dialog. |
michael@0 | 142 | * This function gets called by the interrupted frame once the dialog is dismissed and the frame resumes its process |
michael@0 | 143 | */ |
michael@0 | 144 | switchToModalOrigin: function FM_switchToModalOrigin() { |
michael@0 | 145 | //only handle this if we indeed switched out of the modal's originating frame |
michael@0 | 146 | if (this.previousRemoteFrame != null) { |
michael@0 | 147 | this.currentRemoteFrame = this.previousRemoteFrame; |
michael@0 | 148 | this.addMessageManagerListeners(this.currentRemoteFrame.messageManager.get()); |
michael@0 | 149 | } |
michael@0 | 150 | this.handledModal = false; |
michael@0 | 151 | }, |
michael@0 | 152 | |
michael@0 | 153 | /** |
michael@0 | 154 | * Adds message listeners to the server, listening for messages from content frame scripts. |
michael@0 | 155 | * It also adds a "MarionetteFrame:getInterruptedState" message listener to the FrameManager, |
michael@0 | 156 | * so the frame manager's state can be checked by the frame |
michael@0 | 157 | * |
michael@0 | 158 | * @param object messageManager |
michael@0 | 159 | * The messageManager object (ChromeMessageBroadcaster or ChromeMessageSender) |
michael@0 | 160 | * to which the listeners should be added. |
michael@0 | 161 | */ |
michael@0 | 162 | addMessageManagerListeners: function MDA_addMessageManagerListeners(messageManager) { |
michael@0 | 163 | messageManager.addWeakMessageListener("Marionette:ok", this.server); |
michael@0 | 164 | messageManager.addWeakMessageListener("Marionette:done", this.server); |
michael@0 | 165 | messageManager.addWeakMessageListener("Marionette:error", this.server); |
michael@0 | 166 | messageManager.addWeakMessageListener("Marionette:emitTouchEvent", this.server); |
michael@0 | 167 | messageManager.addWeakMessageListener("Marionette:log", this.server); |
michael@0 | 168 | messageManager.addWeakMessageListener("Marionette:register", this.server); |
michael@0 | 169 | messageManager.addWeakMessageListener("Marionette:runEmulatorCmd", this.server); |
michael@0 | 170 | messageManager.addWeakMessageListener("Marionette:runEmulatorShell", this.server); |
michael@0 | 171 | messageManager.addWeakMessageListener("Marionette:shareData", this.server); |
michael@0 | 172 | messageManager.addWeakMessageListener("Marionette:switchToModalOrigin", this.server); |
michael@0 | 173 | messageManager.addWeakMessageListener("Marionette:switchToFrame", this.server); |
michael@0 | 174 | messageManager.addWeakMessageListener("Marionette:switchedToFrame", this.server); |
michael@0 | 175 | messageManager.addWeakMessageListener("MarionetteFrame:handleModal", this); |
michael@0 | 176 | messageManager.addWeakMessageListener("MarionetteFrame:getCurrentFrameId", this); |
michael@0 | 177 | messageManager.addWeakMessageListener("MarionetteFrame:getInterruptedState", this); |
michael@0 | 178 | }, |
michael@0 | 179 | |
michael@0 | 180 | /** |
michael@0 | 181 | * Removes listeners for messages from content frame scripts. |
michael@0 | 182 | * We do not remove the "MarionetteFrame:getInterruptedState" or the |
michael@0 | 183 | * "Marioentte:switchToModalOrigin" message listener, |
michael@0 | 184 | * because we want to allow all known frames to contact the frame manager so that |
michael@0 | 185 | * it can check if it was interrupted, and if so, it will call switchToModalOrigin |
michael@0 | 186 | * when its process gets resumed. |
michael@0 | 187 | * |
michael@0 | 188 | * @param object messageManager |
michael@0 | 189 | * The messageManager object (ChromeMessageBroadcaster or ChromeMessageSender) |
michael@0 | 190 | * from which the listeners should be removed. |
michael@0 | 191 | */ |
michael@0 | 192 | removeMessageManagerListeners: function MDA_removeMessageManagerListeners(messageManager) { |
michael@0 | 193 | messageManager.removeWeakMessageListener("Marionette:ok", this.server); |
michael@0 | 194 | messageManager.removeWeakMessageListener("Marionette:done", this.server); |
michael@0 | 195 | messageManager.removeWeakMessageListener("Marionette:error", this.server); |
michael@0 | 196 | messageManager.removeWeakMessageListener("Marionette:log", this.server); |
michael@0 | 197 | messageManager.removeWeakMessageListener("Marionette:shareData", this.server); |
michael@0 | 198 | messageManager.removeWeakMessageListener("Marionette:register", this.server); |
michael@0 | 199 | messageManager.removeWeakMessageListener("Marionette:runEmulatorCmd", this.server); |
michael@0 | 200 | messageManager.removeWeakMessageListener("Marionette:runEmulatorShell", this.server); |
michael@0 | 201 | messageManager.removeWeakMessageListener("Marionette:switchToFrame", this.server); |
michael@0 | 202 | messageManager.removeWeakMessageListener("Marionette:switchedToFrame", this.server); |
michael@0 | 203 | messageManager.removeWeakMessageListener("MarionetteFrame:handleModal", this); |
michael@0 | 204 | messageManager.removeWeakMessageListener("MarionetteFrame:getCurrentFrameId", this); |
michael@0 | 205 | }, |
michael@0 | 206 | |
michael@0 | 207 | }; |