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