browser/modules/SharedFrame.jsm

changeset 0
6474c204b198
     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 +}

mercurial