1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/modules/SharedFrame.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,221 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +this.EXPORTED_SYMBOLS = [ "SharedFrame" ]; 1.11 + 1.12 +const Ci = Components.interfaces; 1.13 +const Cu = Components.utils; 1.14 + 1.15 +/** 1.16 + * The purpose of this module is to create and group various iframe 1.17 + * elements that are meant to all display the same content and only 1.18 + * one at a time. This makes it possible to have the content loaded 1.19 + * only once, while the other iframes can be kept as placeholders to 1.20 + * quickly move the content to them through the swapFrameLoaders function 1.21 + * when another one of the placeholder is meant to be displayed. 1.22 + * */ 1.23 + 1.24 +let Frames = new Map(); 1.25 + 1.26 +/** 1.27 + * The Frames map is the main data structure that holds information 1.28 + * about the groups being tracked. Each entry's key is the group name, 1.29 + * and the object holds information about what is the URL being displayed 1.30 + * on that group, and what is the active element on the group (the frame that 1.31 + * holds the loaded content). 1.32 + * The reference to the activeFrame is a weak reference, which allows the 1.33 + * frame to go away at any time, and when that happens the module considers that 1.34 + * there are no active elements in that group. The group can be reactivated 1.35 + * by changing the URL, calling preload again or adding a new element. 1.36 + * 1.37 + * 1.38 + * Frames = { 1.39 + * "messages-panel": { 1.40 + * url: string, 1.41 + * activeFrame: weakref 1.42 + * } 1.43 + * } 1.44 + * 1.45 + * Each object on the map is called a _SharedFrameGroup, which is an internal 1.46 + * class of this module which does not automatically keep track of its state. This 1.47 + * object should not be used externally, and all control should be handled by the 1.48 + * module's functions. 1.49 + */ 1.50 + 1.51 +function UNLOADED_URL(aStr) "data:text/html;charset=utf-8,<!-- Unloaded frame " + aStr + " -->"; 1.52 + 1.53 + 1.54 +this.SharedFrame = { 1.55 + /** 1.56 + * Creates an iframe element and track it as part of the specified group 1.57 + * The module must create the iframe itself because it needs to do some special 1.58 + * handling for the element's src attribute. 1.59 + * 1.60 + * @param aGroupName the name of the group to which this frame belongs 1.61 + * @param aParent the parent element to which the frame will be appended to 1.62 + * @param aFrameAttributes an object with a list of attributes to set in the iframe 1.63 + * before appending it to the DOM. The "src" attribute has 1.64 + * special meaning here and if it's not blank it specifies 1.65 + * the URL that will be initially assigned to this group 1.66 + * @param aPreload optional, tells if the URL specified in the src attribute 1.67 + * should be preloaded in the frame being created, in case 1.68 + * it's not yet preloaded in any other frame of the group. 1.69 + * This parameter has no meaning if src is blank. 1.70 + */ 1.71 + createFrame: function (aGroupName, aParent, aFrameAttributes, aPreload = true) { 1.72 + let frame = aParent.ownerDocument.createElement("iframe"); 1.73 + 1.74 + for (let [key, val] of Iterator(aFrameAttributes)) { 1.75 + frame.setAttribute(key, val); 1.76 + } 1.77 + 1.78 + let src = aFrameAttributes.src; 1.79 + if (!src) { 1.80 + aPreload = false; 1.81 + } 1.82 + 1.83 + let group = Frames.get(aGroupName); 1.84 + 1.85 + if (group) { 1.86 + // If this group has already been created 1.87 + 1.88 + if (aPreload && !group.isAlive) { 1.89 + // If aPreload is set and the group is not already loaded, load it. 1.90 + // This can happen if: 1.91 + // - aPreload was not used while creating the previous frames of this group, or 1.92 + // - the previously active frame went dead in the meantime 1.93 + group.url = src; 1.94 + this.preload(aGroupName, frame); 1.95 + } else { 1.96 + // If aPreload is not set, or the group is already loaded in a different frame, 1.97 + // there's not much that we need to do here: just create this frame as an 1.98 + // inactivate placeholder 1.99 + frame.setAttribute("src", UNLOADED_URL(aGroupName)); 1.100 + } 1.101 + 1.102 + } else { 1.103 + // This is the first time we hear about this group, so let's start tracking it, 1.104 + // and also preload it if the src attribute was set and aPreload = true 1.105 + group = new _SharedFrameGroup(src); 1.106 + Frames.set(aGroupName, group); 1.107 + 1.108 + if (aPreload) { 1.109 + this.preload(aGroupName, frame); 1.110 + } else { 1.111 + frame.setAttribute("src", UNLOADED_URL(aGroupName)); 1.112 + } 1.113 + } 1.114 + 1.115 + aParent.appendChild(frame); 1.116 + return frame; 1.117 + 1.118 + }, 1.119 + 1.120 + /** 1.121 + * Function that moves the loaded content from one active frame to 1.122 + * another one that is currently a placeholder. If there's no active 1.123 + * frame in the group, the content is loaded/reloaded. 1.124 + * 1.125 + * @param aGroupName the name of the group 1.126 + * @param aTargetFrame the frame element to which the content should 1.127 + * be moved to. 1.128 + */ 1.129 + setOwner: function (aGroupName, aTargetFrame) { 1.130 + let group = Frames.get(aGroupName); 1.131 + let frame = group.activeFrame; 1.132 + 1.133 + if (frame == aTargetFrame) { 1.134 + // nothing to do here 1.135 + return; 1.136 + } 1.137 + 1.138 + if (group.isAlive) { 1.139 + // Move document ownership to the desired frame, and make it the active one 1.140 + frame.QueryInterface(Ci.nsIFrameLoaderOwner).swapFrameLoaders(aTargetFrame); 1.141 + group.activeFrame = aTargetFrame; 1.142 + } else { 1.143 + // Previous owner was dead, reload the document at the new owner and make it the active one 1.144 + aTargetFrame.setAttribute("src", group.url); 1.145 + group.activeFrame = aTargetFrame; 1.146 + } 1.147 + }, 1.148 + 1.149 + /** 1.150 + * Updates the current URL in use by this group, and loads it into the active frame. 1.151 + * 1.152 + * @param aGroupName the name of the group 1.153 + * @param aURL the new url 1.154 + */ 1.155 + updateURL: function (aGroupName, aURL) { 1.156 + let group = Frames.get(aGroupName); 1.157 + group.url = aURL; 1.158 + 1.159 + if (group.isAlive) { 1.160 + group.activeFrame.setAttribute("src", aURL); 1.161 + } 1.162 + }, 1.163 + 1.164 + /** 1.165 + * Loads the group's url into a target frame, if the group doesn't have a currently 1.166 + * active frame. 1.167 + * 1.168 + * @param aGroupName the name of the group 1.169 + * @param aTargetFrame the frame element which should be made active and 1.170 + * have the group's content loaded to 1.171 + */ 1.172 + preload: function (aGroupName, aTargetFrame) { 1.173 + let group = Frames.get(aGroupName); 1.174 + if (!group.isAlive) { 1.175 + aTargetFrame.setAttribute("src", group.url); 1.176 + group.activeFrame = aTargetFrame; 1.177 + } 1.178 + }, 1.179 + 1.180 + /** 1.181 + * Tells if a group currently have an active element. 1.182 + * 1.183 + * @param aGroupName the name of the group 1.184 + */ 1.185 + isGroupAlive: function (aGroupName) { 1.186 + return Frames.get(aGroupName).isAlive; 1.187 + }, 1.188 + 1.189 + /** 1.190 + * Forgets about this group. This function doesn't need to be used 1.191 + * unless the group's name needs to be reused. 1.192 + * 1.193 + * @param aGroupName the name of the group 1.194 + */ 1.195 + forgetGroup: function (aGroupName) { 1.196 + Frames.delete(aGroupName); 1.197 + } 1.198 +} 1.199 + 1.200 + 1.201 +function _SharedFrameGroup(aURL) { 1.202 + this.url = aURL; 1.203 + this._activeFrame = null; 1.204 +} 1.205 + 1.206 +_SharedFrameGroup.prototype = { 1.207 + get isAlive() { 1.208 + let frame = this.activeFrame; 1.209 + return !!(frame && 1.210 + frame.contentDocument && 1.211 + frame.contentDocument.location); 1.212 + }, 1.213 + 1.214 + get activeFrame() { 1.215 + return this._activeFrame && 1.216 + this._activeFrame.get(); 1.217 + }, 1.218 + 1.219 + set activeFrame(aActiveFrame) { 1.220 + this._activeFrame = aActiveFrame 1.221 + ? Cu.getWeakReference(aActiveFrame) 1.222 + : null; 1.223 + } 1.224 +}