SharedFrame.jsm 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  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
  3. * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
  4. "use strict";
  5. this.EXPORTED_SYMBOLS = [ "SharedFrame" ];
  6. const Ci = Components.interfaces;
  7. const Cu = Components.utils;
  8. /**
  9. * The purpose of this module is to create and group various iframe
  10. * elements that are meant to all display the same content and only
  11. * one at a time. This makes it possible to have the content loaded
  12. * only once, while the other iframes can be kept as placeholders to
  13. * quickly move the content to them through the swapFrameLoaders function
  14. * when another one of the placeholder is meant to be displayed.
  15. * */
  16. var Frames = new Map();
  17. /**
  18. * The Frames map is the main data structure that holds information
  19. * about the groups being tracked. Each entry's key is the group name,
  20. * and the object holds information about what is the URL being displayed
  21. * on that group, and what is the active element on the group (the frame that
  22. * holds the loaded content).
  23. * The reference to the activeFrame is a weak reference, which allows the
  24. * frame to go away at any time, and when that happens the module considers that
  25. * there are no active elements in that group. The group can be reactivated
  26. * by changing the URL, calling preload again or adding a new element.
  27. *
  28. *
  29. * Frames = {
  30. * "messages-panel": {
  31. * url: string,
  32. * activeFrame: weakref
  33. * }
  34. * }
  35. *
  36. * Each object on the map is called a _SharedFrameGroup, which is an internal
  37. * class of this module which does not automatically keep track of its state. This
  38. * object should not be used externally, and all control should be handled by the
  39. * module's functions.
  40. */
  41. function UNLOADED_URL(aStr) "data:text/html;charset=utf-8,<!-- Unloaded frame " + aStr + " -->";
  42. this.SharedFrame = {
  43. /**
  44. * Creates an iframe element and track it as part of the specified group
  45. * The module must create the iframe itself because it needs to do some special
  46. * handling for the element's src attribute.
  47. *
  48. * @param aGroupName the name of the group to which this frame belongs
  49. * @param aParent the parent element to which the frame will be appended to
  50. * @param aFrameAttributes an object with a list of attributes to set in the iframe
  51. * before appending it to the DOM. The "src" attribute has
  52. * special meaning here and if it's not blank it specifies
  53. * the URL that will be initially assigned to this group
  54. * @param aPreload optional, tells if the URL specified in the src attribute
  55. * should be preloaded in the frame being created, in case
  56. * it's not yet preloaded in any other frame of the group.
  57. * This parameter has no meaning if src is blank.
  58. */
  59. createFrame: function (aGroupName, aParent, aFrameAttributes, aPreload = true) {
  60. let frame = aParent.ownerDocument.createElement("iframe");
  61. for (let [key, val] of Iterator(aFrameAttributes)) {
  62. frame.setAttribute(key, val);
  63. }
  64. let src = aFrameAttributes.src;
  65. if (!src) {
  66. aPreload = false;
  67. }
  68. let group = Frames.get(aGroupName);
  69. if (group) {
  70. // If this group has already been created
  71. if (aPreload && !group.isAlive) {
  72. // If aPreload is set and the group is not already loaded, load it.
  73. // This can happen if:
  74. // - aPreload was not used while creating the previous frames of this group, or
  75. // - the previously active frame went dead in the meantime
  76. group.url = src;
  77. this.preload(aGroupName, frame);
  78. } else {
  79. // If aPreload is not set, or the group is already loaded in a different frame,
  80. // there's not much that we need to do here: just create this frame as an
  81. // inactivate placeholder
  82. frame.setAttribute("src", UNLOADED_URL(aGroupName));
  83. }
  84. } else {
  85. // This is the first time we hear about this group, so let's start tracking it,
  86. // and also preload it if the src attribute was set and aPreload = true
  87. group = new _SharedFrameGroup(src);
  88. Frames.set(aGroupName, group);
  89. if (aPreload) {
  90. this.preload(aGroupName, frame);
  91. } else {
  92. frame.setAttribute("src", UNLOADED_URL(aGroupName));
  93. }
  94. }
  95. aParent.appendChild(frame);
  96. return frame;
  97. },
  98. /**
  99. * Function that moves the loaded content from one active frame to
  100. * another one that is currently a placeholder. If there's no active
  101. * frame in the group, the content is loaded/reloaded.
  102. *
  103. * @param aGroupName the name of the group
  104. * @param aTargetFrame the frame element to which the content should
  105. * be moved to.
  106. */
  107. setOwner: function (aGroupName, aTargetFrame) {
  108. let group = Frames.get(aGroupName);
  109. let frame = group.activeFrame;
  110. if (frame == aTargetFrame) {
  111. // nothing to do here
  112. return;
  113. }
  114. if (group.isAlive) {
  115. // Move document ownership to the desired frame, and make it the active one
  116. frame.QueryInterface(Ci.nsIFrameLoaderOwner).swapFrameLoaders(aTargetFrame);
  117. group.activeFrame = aTargetFrame;
  118. } else {
  119. // Previous owner was dead, reload the document at the new owner and make it the active one
  120. aTargetFrame.setAttribute("src", group.url);
  121. group.activeFrame = aTargetFrame;
  122. }
  123. },
  124. /**
  125. * Updates the current URL in use by this group, and loads it into the active frame.
  126. *
  127. * @param aGroupName the name of the group
  128. * @param aURL the new url
  129. */
  130. updateURL: function (aGroupName, aURL) {
  131. let group = Frames.get(aGroupName);
  132. group.url = aURL;
  133. if (group.isAlive) {
  134. group.activeFrame.setAttribute("src", aURL);
  135. }
  136. },
  137. /**
  138. * Loads the group's url into a target frame, if the group doesn't have a currently
  139. * active frame.
  140. *
  141. * @param aGroupName the name of the group
  142. * @param aTargetFrame the frame element which should be made active and
  143. * have the group's content loaded to
  144. */
  145. preload: function (aGroupName, aTargetFrame) {
  146. let group = Frames.get(aGroupName);
  147. if (!group.isAlive) {
  148. aTargetFrame.setAttribute("src", group.url);
  149. group.activeFrame = aTargetFrame;
  150. }
  151. },
  152. /**
  153. * Tells if a group currently have an active element.
  154. *
  155. * @param aGroupName the name of the group
  156. */
  157. isGroupAlive: function (aGroupName) {
  158. return Frames.get(aGroupName).isAlive;
  159. },
  160. /**
  161. * Forgets about this group. This function doesn't need to be used
  162. * unless the group's name needs to be reused.
  163. *
  164. * @param aGroupName the name of the group
  165. */
  166. forgetGroup: function (aGroupName) {
  167. Frames.delete(aGroupName);
  168. }
  169. }
  170. function _SharedFrameGroup(aURL) {
  171. this.url = aURL;
  172. this._activeFrame = null;
  173. }
  174. _SharedFrameGroup.prototype = {
  175. get isAlive() {
  176. let frame = this.activeFrame;
  177. return !!(frame &&
  178. frame.contentDocument &&
  179. frame.contentDocument.location);
  180. },
  181. get activeFrame() {
  182. return this._activeFrame &&
  183. this._activeFrame.get();
  184. },
  185. set activeFrame(aActiveFrame) {
  186. this._activeFrame = aActiveFrame
  187. ? Cu.getWeakReference(aActiveFrame)
  188. : null;
  189. }
  190. }