summaryrefslogtreecommitdiffstats
path: root/modules/SharedFrame.jsm
blob: b9d59bfa986c7332cdd3a96be99a66ce83e5fb60 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

"use strict";

this.EXPORTED_SYMBOLS = [ "SharedFrame" ];

const Ci = Components.interfaces;
const Cu = Components.utils;

/**
 * The purpose of this module is to create and group various iframe
 * elements that are meant to all display the same content and only
 * one at a time. This makes it possible to have the content loaded
 * only once, while the other iframes can be kept as placeholders to
 * quickly move the content to them through the swapFrameLoaders function
 * when another one of the placeholder is meant to be displayed.
 * */

var Frames = new Map();

/**
 * The Frames map is the main data structure that holds information
 * about the groups being tracked. Each entry's key is the group name,
 * and the object holds information about what is the URL being displayed
 * on that group, and what is the active element on the group (the frame that
 * holds the loaded content).
 * The reference to the activeFrame is a weak reference, which allows the
 * frame to go away at any time, and when that happens the module considers that
 * there are no active elements in that group. The group can be reactivated
 * by changing the URL, calling preload again or adding a new element.
 *
 *
 *  Frames = {
 *    "messages-panel": {
 *      url: string,
 *      activeFrame: weakref
 *    }
 *  }
 *
 * Each object on the map is called a _SharedFrameGroup, which is an internal
 * class of this module which does not automatically keep track of its state. This
 * object should not be used externally, and all control should be handled by the
 * module's functions.
 */

function UNLOADED_URL(aStr) "data:text/html;charset=utf-8,<!-- Unloaded frame " + aStr + " -->";


this.SharedFrame = {
  /**
   * Creates an iframe element and track it as part of the specified group
   * The module must create the iframe itself because it needs to do some special
   * handling for the element's src attribute.
   *
   * @param aGroupName        the name of the group to which this frame belongs
   * @param aParent           the parent element to which the frame will be appended to
   * @param aFrameAttributes  an object with a list of attributes to set in the iframe
   *                          before appending it to the DOM. The "src" attribute has
   *                          special meaning here and if it's not blank it specifies
   *                          the URL that will be initially assigned to this group
   * @param aPreload          optional, tells if the URL specified in the src attribute
   *                          should be preloaded in the frame being created, in case
   *                          it's not yet preloaded in any other frame of the group.
   *                          This parameter has no meaning if src is blank.
   */
  createFrame: function (aGroupName, aParent, aFrameAttributes, aPreload = true) {
    let frame = aParent.ownerDocument.createElement("iframe");

    for (let [key, val] of Iterator(aFrameAttributes)) {
      frame.setAttribute(key, val);
    }

    let src = aFrameAttributes.src;
    if (!src) {
      aPreload = false;
    }

    let group = Frames.get(aGroupName);

    if (group) {
      // If this group has already been created

      if (aPreload && !group.isAlive) {
        // If aPreload is set and the group is not already loaded, load it.
        // This can happen if:
        // - aPreload was not used while creating the previous frames of this group, or
        // - the previously active frame went dead in the meantime
        group.url = src;
        this.preload(aGroupName, frame);
      } else {
        // If aPreload is not set, or the group is already loaded in a different frame,
        // there's not much that we need to do here: just create this frame as an
        // inactivate placeholder
        frame.setAttribute("src", UNLOADED_URL(aGroupName));
      }

    } else {
      // This is the first time we hear about this group, so let's start tracking it,
      // and also preload it if the src attribute was set and aPreload = true
      group = new _SharedFrameGroup(src);
      Frames.set(aGroupName, group);

      if (aPreload) {
        this.preload(aGroupName, frame);
      } else {
        frame.setAttribute("src", UNLOADED_URL(aGroupName));
      }
    }

    aParent.appendChild(frame);
    return frame;

  },

  /**
   * Function that moves the loaded content from one active frame to
   * another one that is currently a placeholder. If there's no active
   * frame in the group, the content is loaded/reloaded.
   *
   * @param aGroupName   the name of the group
   * @param aTargetFrame the frame element to which the content should
   *                     be moved to.
   */
  setOwner: function (aGroupName, aTargetFrame) {
    let group = Frames.get(aGroupName);
    let frame = group.activeFrame;

    if (frame == aTargetFrame) {
      // nothing to do here
      return;
    }

    if (group.isAlive) {
      // Move document ownership to the desired frame, and make it the active one
      frame.QueryInterface(Ci.nsIFrameLoaderOwner).swapFrameLoaders(aTargetFrame);
      group.activeFrame = aTargetFrame;
    } else {
      // Previous owner was dead, reload the document at the new owner and make it the active one
      aTargetFrame.setAttribute("src", group.url);
      group.activeFrame = aTargetFrame;
    }
  },

  /**
   * Updates the current URL in use by this group, and loads it into the active frame.
   *
   * @param aGroupName  the name of the group
   * @param aURL        the new url
   */
  updateURL: function (aGroupName, aURL) {
    let group = Frames.get(aGroupName);
    group.url = aURL;

    if (group.isAlive) {
      group.activeFrame.setAttribute("src", aURL);
    }
  },

  /**
   * Loads the group's url into a target frame, if the group doesn't have a currently
   * active frame.
   *
   * @param aGroupName    the name of the group
   * @param aTargetFrame  the frame element which should be made active and
   *                      have the group's content loaded to
   */
  preload: function (aGroupName, aTargetFrame) {
    let group = Frames.get(aGroupName);
    if (!group.isAlive) {
      aTargetFrame.setAttribute("src", group.url);
      group.activeFrame = aTargetFrame;
    }
  },

  /**
   * Tells if a group currently have an active element.
   *
   * @param aGroupName  the name of the group
   */
  isGroupAlive: function (aGroupName) {
    return Frames.get(aGroupName).isAlive;
  },

  /**
   * Forgets about this group. This function doesn't need to be used
   * unless the group's name needs to be reused.
   *
   * @param aGroupName  the name of the group
   */
  forgetGroup: function (aGroupName) {
    Frames.delete(aGroupName);
  }
}


function _SharedFrameGroup(aURL) {
  this.url = aURL;
  this._activeFrame = null;
}

_SharedFrameGroup.prototype = {
  get isAlive() {
    let frame = this.activeFrame;
    return !!(frame &&
              frame.contentDocument &&
              frame.contentDocument.location);
  },

  get activeFrame() {
    return this._activeFrame &&
           this._activeFrame.get();
  },

  set activeFrame(aActiveFrame) {
    this._activeFrame = aActiveFrame
                        ? Cu.getWeakReference(aActiveFrame)
                        : null;
  }
}