michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: this.EXPORTED_SYMBOLS = [ michael@0: "FrameManager" michael@0: ]; michael@0: michael@0: let FRAME_SCRIPT = "chrome://marionette/content/marionette-listener.js"; michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: michael@0: Cu.import("resource://gre/modules/Log.jsm"); michael@0: let logger = Log.repository.getLogger("Marionette"); michael@0: michael@0: //list of OOP frames that has the frame script loaded michael@0: let remoteFrames = []; michael@0: michael@0: /** michael@0: * An object representing a frame that Marionette has loaded a michael@0: * frame script in. michael@0: */ michael@0: function MarionetteRemoteFrame(windowId, frameId) { michael@0: this.windowId = windowId; //outerWindowId relative to main process michael@0: this.frameId = frameId ? frameId : null; //actual frame relative to windowId's frames list michael@0: this.targetFrameId = this.frameId; //assigned FrameId, used for messaging michael@0: }; michael@0: michael@0: /** michael@0: * The FrameManager will maintain the list of Out Of Process (OOP) frames and will handle michael@0: * frame switching between them. michael@0: * It handles explicit frame switching (switchToFrame), and implicit frame switching, which michael@0: * occurs when a modal dialog is triggered in B2G. michael@0: * michael@0: */ michael@0: this.FrameManager = function FrameManager(server) { michael@0: //messageManager maintains the messageManager for the current process' chrome frame or the global message manager michael@0: this.currentRemoteFrame = null; //holds a member of remoteFrames (for an OOP frame) or null (for the main process) michael@0: this.previousRemoteFrame = null; //frame we'll need to restore once interrupt is gone michael@0: this.handledModal = false; //set to true when we have been interrupted by a modal michael@0: this.server = server; // a reference to the marionette server michael@0: }; michael@0: michael@0: FrameManager.prototype = { michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIMessageListener, michael@0: Ci.nsISupportsWeakReference]), michael@0: michael@0: /** michael@0: * Receives all messages from content messageManager michael@0: */ michael@0: receiveMessage: function FM_receiveMessage(message) { michael@0: switch (message.name) { michael@0: case "MarionetteFrame:getInterruptedState": michael@0: // This will return true if the calling frame was interrupted by a modal dialog michael@0: if (this.previousRemoteFrame) { michael@0: let interruptedFrame = Services.wm.getOuterWindowWithId(this.previousRemoteFrame.windowId);//get the frame window of the interrupted frame michael@0: if (this.previousRemoteFrame.frameId != null) { michael@0: interruptedFrame = interruptedFrame.document.getElementsByTagName("iframe")[this.previousRemoteFrame.frameId]; //find the OOP frame michael@0: } michael@0: //check if the interrupted frame is the same as the calling frame michael@0: if (interruptedFrame.src == message.target.src) { michael@0: return {value: this.handledModal}; michael@0: } michael@0: } michael@0: else if (this.currentRemoteFrame == null) { michael@0: // 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: return {value: this.handledModal}; michael@0: } michael@0: return {value: false}; michael@0: case "MarionetteFrame:handleModal": michael@0: /* michael@0: * handleModal is called when we need to switch frames to the main process due to a modal dialog interrupt. michael@0: */ michael@0: // If previousRemoteFrame was set, that means we switched into a remote frame. michael@0: // If this is the case, then we want to switch back into the system frame. michael@0: // 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: let isLocal = true; michael@0: if (this.currentRemoteFrame != null) { michael@0: isLocal = false; michael@0: this.removeMessageManagerListeners(this.currentRemoteFrame.messageManager.get()); michael@0: //store the previous frame so we can switch back to it when the modal is dismissed michael@0: this.previousRemoteFrame = this.currentRemoteFrame; michael@0: //by setting currentRemoteFrame to null, it signifies we're in the main process michael@0: this.currentRemoteFrame = null; michael@0: this.server.messageManager = Cc["@mozilla.org/globalmessagemanager;1"] michael@0: .getService(Ci.nsIMessageBroadcaster); michael@0: } michael@0: this.handledModal = true; michael@0: this.server.sendOk(this.server.command_id); michael@0: return {value: isLocal}; michael@0: case "MarionetteFrame:getCurrentFrameId": michael@0: if (this.currentRemoteFrame != null) { michael@0: return this.currentRemoteFrame.frameId; michael@0: } michael@0: } michael@0: }, michael@0: michael@0: //This is just 'switch to OOP frame'. We're handling this here so we can maintain a list of remoteFrames. michael@0: switchToFrame: function FM_switchToFrame(message) { michael@0: // Switch to a remote frame. michael@0: let frameWindow = Services.wm.getOuterWindowWithId(message.json.win); //get the original frame window michael@0: let oopFrame = frameWindow.document.getElementsByTagName("iframe")[message.json.frame]; //find the OOP frame michael@0: let mm = oopFrame.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader.messageManager; //get the OOP frame's mm michael@0: michael@0: // See if this frame already has our frame script loaded in it; if so, michael@0: // just wake it up. michael@0: for (let i = 0; i < remoteFrames.length; i++) { michael@0: let frame = remoteFrames[i]; michael@0: let frameMessageManager = frame.messageManager.get(); michael@0: logger.info("trying remote frame " + i); michael@0: try { michael@0: frameMessageManager.sendAsyncMessage("aliveCheck", {}); michael@0: } michael@0: catch(e) { michael@0: if (e.result == Components.results.NS_ERROR_NOT_INITIALIZED) { michael@0: logger.info("deleting frame"); michael@0: remoteFrames.splice(i, 1); michael@0: continue; michael@0: } michael@0: } michael@0: if (frameMessageManager == mm) { michael@0: this.currentRemoteFrame = frame; michael@0: this.addMessageManagerListeners(mm); michael@0: mm.sendAsyncMessage("Marionette:restart", {}); michael@0: return; michael@0: } michael@0: } michael@0: michael@0: // If we get here, then we need to load the frame script in this frame, michael@0: // and set the frame's ChromeMessageSender as the active message manager the server will listen to michael@0: this.addMessageManagerListeners(mm); michael@0: logger.info("frame-manager load script: " + mm.toString()); michael@0: mm.loadFrameScript(FRAME_SCRIPT, true, true); michael@0: let aFrame = new MarionetteRemoteFrame(message.json.win, message.json.frame); michael@0: aFrame.messageManager = Cu.getWeakReference(mm); michael@0: remoteFrames.push(aFrame); michael@0: this.currentRemoteFrame = aFrame; michael@0: }, michael@0: michael@0: /* michael@0: * This function handles switching back to the frame that was interrupted by the modal dialog. michael@0: * This function gets called by the interrupted frame once the dialog is dismissed and the frame resumes its process michael@0: */ michael@0: switchToModalOrigin: function FM_switchToModalOrigin() { michael@0: //only handle this if we indeed switched out of the modal's originating frame michael@0: if (this.previousRemoteFrame != null) { michael@0: this.currentRemoteFrame = this.previousRemoteFrame; michael@0: this.addMessageManagerListeners(this.currentRemoteFrame.messageManager.get()); michael@0: } michael@0: this.handledModal = false; michael@0: }, michael@0: michael@0: /** michael@0: * Adds message listeners to the server, listening for messages from content frame scripts. michael@0: * It also adds a "MarionetteFrame:getInterruptedState" message listener to the FrameManager, michael@0: * so the frame manager's state can be checked by the frame michael@0: * michael@0: * @param object messageManager michael@0: * The messageManager object (ChromeMessageBroadcaster or ChromeMessageSender) michael@0: * to which the listeners should be added. michael@0: */ michael@0: addMessageManagerListeners: function MDA_addMessageManagerListeners(messageManager) { michael@0: messageManager.addWeakMessageListener("Marionette:ok", this.server); michael@0: messageManager.addWeakMessageListener("Marionette:done", this.server); michael@0: messageManager.addWeakMessageListener("Marionette:error", this.server); michael@0: messageManager.addWeakMessageListener("Marionette:emitTouchEvent", this.server); michael@0: messageManager.addWeakMessageListener("Marionette:log", this.server); michael@0: messageManager.addWeakMessageListener("Marionette:register", this.server); michael@0: messageManager.addWeakMessageListener("Marionette:runEmulatorCmd", this.server); michael@0: messageManager.addWeakMessageListener("Marionette:runEmulatorShell", this.server); michael@0: messageManager.addWeakMessageListener("Marionette:shareData", this.server); michael@0: messageManager.addWeakMessageListener("Marionette:switchToModalOrigin", this.server); michael@0: messageManager.addWeakMessageListener("Marionette:switchToFrame", this.server); michael@0: messageManager.addWeakMessageListener("Marionette:switchedToFrame", this.server); michael@0: messageManager.addWeakMessageListener("MarionetteFrame:handleModal", this); michael@0: messageManager.addWeakMessageListener("MarionetteFrame:getCurrentFrameId", this); michael@0: messageManager.addWeakMessageListener("MarionetteFrame:getInterruptedState", this); michael@0: }, michael@0: michael@0: /** michael@0: * Removes listeners for messages from content frame scripts. michael@0: * We do not remove the "MarionetteFrame:getInterruptedState" or the michael@0: * "Marioentte:switchToModalOrigin" message listener, michael@0: * because we want to allow all known frames to contact the frame manager so that michael@0: * it can check if it was interrupted, and if so, it will call switchToModalOrigin michael@0: * when its process gets resumed. michael@0: * michael@0: * @param object messageManager michael@0: * The messageManager object (ChromeMessageBroadcaster or ChromeMessageSender) michael@0: * from which the listeners should be removed. michael@0: */ michael@0: removeMessageManagerListeners: function MDA_removeMessageManagerListeners(messageManager) { michael@0: messageManager.removeWeakMessageListener("Marionette:ok", this.server); michael@0: messageManager.removeWeakMessageListener("Marionette:done", this.server); michael@0: messageManager.removeWeakMessageListener("Marionette:error", this.server); michael@0: messageManager.removeWeakMessageListener("Marionette:log", this.server); michael@0: messageManager.removeWeakMessageListener("Marionette:shareData", this.server); michael@0: messageManager.removeWeakMessageListener("Marionette:register", this.server); michael@0: messageManager.removeWeakMessageListener("Marionette:runEmulatorCmd", this.server); michael@0: messageManager.removeWeakMessageListener("Marionette:runEmulatorShell", this.server); michael@0: messageManager.removeWeakMessageListener("Marionette:switchToFrame", this.server); michael@0: messageManager.removeWeakMessageListener("Marionette:switchedToFrame", this.server); michael@0: messageManager.removeWeakMessageListener("MarionetteFrame:handleModal", this); michael@0: messageManager.removeWeakMessageListener("MarionetteFrame:getCurrentFrameId", this); michael@0: }, michael@0: michael@0: };