frame.js 9.9 KB

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