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