services/sync/tps/extensions/mozmill/resource/modules/windows.js

Wed, 31 Dec 2014 07:53:36 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:53:36 +0100
branch
TOR_BUG_3246
changeset 5
4ab42b5ab56c
permissions
-rw-r--r--

Correct small whitespace inconsistency, lost while renaming variables.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, you can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 var EXPORTED_SYMBOLS = ["init", "map"];
michael@0 6
michael@0 7 const Cc = Components.classes;
michael@0 8 const Ci = Components.interfaces;
michael@0 9 const Cu = Components.utils;
michael@0 10
michael@0 11 // imports
michael@0 12 var utils = {}; Cu.import('resource://mozmill/stdlib/utils.js', utils);
michael@0 13
michael@0 14 var uuidgen = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
michael@0 15
michael@0 16 /**
michael@0 17 * The window map is used to store information about the current state of
michael@0 18 * open windows, e.g. loaded state
michael@0 19 */
michael@0 20 var map = {
michael@0 21 _windows : { },
michael@0 22
michael@0 23 /**
michael@0 24 * Check if a given window id is contained in the map of windows
michael@0 25 *
michael@0 26 * @param {Number} aWindowId
michael@0 27 * Outer ID of the window to check.
michael@0 28 * @returns {Boolean} True if the window is part of the map, otherwise false.
michael@0 29 */
michael@0 30 contains : function (aWindowId) {
michael@0 31 return (aWindowId in this._windows);
michael@0 32 },
michael@0 33
michael@0 34 /**
michael@0 35 * Retrieve the value of the specified window's property.
michael@0 36 *
michael@0 37 * @param {Number} aWindowId
michael@0 38 * Outer ID of the window to check.
michael@0 39 * @param {String} aProperty
michael@0 40 * Property to retrieve the value from
michael@0 41 * @return {Object} Value of the window's property
michael@0 42 */
michael@0 43 getValue : function (aWindowId, aProperty) {
michael@0 44 if (!this.contains(aWindowId)) {
michael@0 45 return undefined;
michael@0 46 } else {
michael@0 47 var win = this._windows[aWindowId];
michael@0 48
michael@0 49 return (aProperty in win) ? win[aProperty]
michael@0 50 : undefined;
michael@0 51 }
michael@0 52 },
michael@0 53
michael@0 54 /**
michael@0 55 * Remove the entry for a given window
michael@0 56 *
michael@0 57 * @param {Number} aWindowId
michael@0 58 * Outer ID of the window to check.
michael@0 59 */
michael@0 60 remove : function (aWindowId) {
michael@0 61 if (this.contains(aWindowId)) {
michael@0 62 delete this._windows[aWindowId];
michael@0 63 }
michael@0 64
michael@0 65 // dump("* current map: " + JSON.stringify(this._windows) + "\n");
michael@0 66 },
michael@0 67
michael@0 68 /**
michael@0 69 * Update the property value of a given window
michael@0 70 *
michael@0 71 * @param {Number} aWindowId
michael@0 72 * Outer ID of the window to check.
michael@0 73 * @param {String} aProperty
michael@0 74 * Property to update the value for
michael@0 75 * @param {Object}
michael@0 76 * Value to set
michael@0 77 */
michael@0 78 update : function (aWindowId, aProperty, aValue) {
michael@0 79 if (!this.contains(aWindowId)) {
michael@0 80 this._windows[aWindowId] = { };
michael@0 81 }
michael@0 82
michael@0 83 this._windows[aWindowId][aProperty] = aValue;
michael@0 84 // dump("* current map: " + JSON.stringify(this._windows) + "\n");
michael@0 85 },
michael@0 86
michael@0 87 /**
michael@0 88 * Update the internal loaded state of the given content window. To identify
michael@0 89 * an active (re)load action we make use of an uuid.
michael@0 90 *
michael@0 91 * @param {Window} aId - The outer id of the window to update
michael@0 92 * @param {Boolean} aIsLoaded - Has the window been loaded
michael@0 93 */
michael@0 94 updatePageLoadStatus : function (aId, aIsLoaded) {
michael@0 95 this.update(aId, "loaded", aIsLoaded);
michael@0 96
michael@0 97 var uuid = this.getValue(aId, "id_load_in_transition");
michael@0 98
michael@0 99 // If no uuid has been set yet or when the page gets unloaded create a new id
michael@0 100 if (!uuid || !aIsLoaded) {
michael@0 101 uuid = uuidgen.generateUUID();
michael@0 102 this.update(aId, "id_load_in_transition", uuid);
michael@0 103 }
michael@0 104
michael@0 105 // dump("*** Page status updated: id=" + aId + ", loaded=" + aIsLoaded + ", uuid=" + uuid + "\n");
michael@0 106 },
michael@0 107
michael@0 108 /**
michael@0 109 * This method only applies to content windows, where we have to check if it has
michael@0 110 * been successfully loaded or reloaded. An uuid allows us to wait for the next
michael@0 111 * load action triggered by e.g. controller.open().
michael@0 112 *
michael@0 113 * @param {Window} aId - The outer id of the content window to check
michael@0 114 *
michael@0 115 * @returns {Boolean} True if the content window has been loaded
michael@0 116 */
michael@0 117 hasPageLoaded : function (aId) {
michael@0 118 var load_current = this.getValue(aId, "id_load_in_transition");
michael@0 119 var load_handled = this.getValue(aId, "id_load_handled");
michael@0 120
michael@0 121 var isLoaded = this.contains(aId) && this.getValue(aId, "loaded") &&
michael@0 122 (load_current !== load_handled);
michael@0 123
michael@0 124 if (isLoaded) {
michael@0 125 // Backup the current uuid so we can check later if another page load happened.
michael@0 126 this.update(aId, "id_load_handled", load_current);
michael@0 127 }
michael@0 128
michael@0 129 // dump("** Page has been finished loading: id=" + aId + ", status=" + isLoaded + ", uuid=" + load_current + "\n");
michael@0 130
michael@0 131 return isLoaded;
michael@0 132 }
michael@0 133 };
michael@0 134
michael@0 135
michael@0 136 // Observer when a new top-level window is ready
michael@0 137 var windowReadyObserver = {
michael@0 138 observe: function (aSubject, aTopic, aData) {
michael@0 139 // Not in all cases we get a ChromeWindow. So ensure we really operate
michael@0 140 // on such an instance. Otherwise load events will not be handled.
michael@0 141 var win = utils.getChromeWindow(aSubject);
michael@0 142
michael@0 143 // var id = utils.getWindowId(win);
michael@0 144 // dump("*** 'toplevel-window-ready' observer notification: id=" + id + "\n");
michael@0 145 attachEventListeners(win);
michael@0 146 }
michael@0 147 };
michael@0 148
michael@0 149
michael@0 150 // Observer when a top-level window is closed
michael@0 151 var windowCloseObserver = {
michael@0 152 observe: function (aSubject, aTopic, aData) {
michael@0 153 var id = utils.getWindowId(aSubject);
michael@0 154 // dump("*** 'outer-window-destroyed' observer notification: id=" + id + "\n");
michael@0 155
michael@0 156 map.remove(id);
michael@0 157 }
michael@0 158 };
michael@0 159
michael@0 160 // Bug 915554
michael@0 161 // Support for the old Private Browsing Mode (eg. ESR17)
michael@0 162 // TODO: remove once ESR17 is no longer supported
michael@0 163 var enterLeavePrivateBrowsingObserver = {
michael@0 164 observe: function (aSubject, aTopic, aData) {
michael@0 165 handleAttachEventListeners();
michael@0 166 }
michael@0 167 };
michael@0 168
michael@0 169 /**
michael@0 170 * Attach event listeners
michael@0 171 *
michael@0 172 * @param {ChromeWindow} aWindow
michael@0 173 * Window to attach listeners on.
michael@0 174 */
michael@0 175 function attachEventListeners(aWindow) {
michael@0 176 // These are the event handlers
michael@0 177 var pageShowHandler = function (aEvent) {
michael@0 178 var doc = aEvent.originalTarget;
michael@0 179
michael@0 180 // Only update the flag if we have a document as target
michael@0 181 // see https://bugzilla.mozilla.org/show_bug.cgi?id=690829
michael@0 182 if ("defaultView" in doc) {
michael@0 183 var id = utils.getWindowId(doc.defaultView);
michael@0 184 // dump("*** 'pageshow' event: id=" + id + ", baseURI=" + doc.baseURI + "\n");
michael@0 185 map.updatePageLoadStatus(id, true);
michael@0 186 }
michael@0 187
michael@0 188 // We need to add/remove the unload/pagehide event listeners to preserve caching.
michael@0 189 aWindow.addEventListener("beforeunload", beforeUnloadHandler, true);
michael@0 190 aWindow.addEventListener("pagehide", pageHideHandler, true);
michael@0 191 };
michael@0 192
michael@0 193 var DOMContentLoadedHandler = function (aEvent) {
michael@0 194 var doc = aEvent.originalTarget;
michael@0 195
michael@0 196 // Only update the flag if we have a document as target
michael@0 197 if ("defaultView" in doc) {
michael@0 198 var id = utils.getWindowId(doc.defaultView);
michael@0 199 // dump("*** 'DOMContentLoaded' event: id=" + id + ", baseURI=" + doc.baseURI + "\n");
michael@0 200
michael@0 201 // We only care about error pages for DOMContentLoaded
michael@0 202 var errorRegex = /about:.+(error)|(blocked)\?/;
michael@0 203 if (errorRegex.exec(doc.baseURI)) {
michael@0 204 // Wait about 1s to be sure the DOM is ready
michael@0 205 utils.sleep(1000);
michael@0 206
michael@0 207 map.updatePageLoadStatus(id, true);
michael@0 208 }
michael@0 209
michael@0 210 // We need to add/remove the unload event listener to preserve caching.
michael@0 211 aWindow.addEventListener("beforeunload", beforeUnloadHandler, true);
michael@0 212 }
michael@0 213 };
michael@0 214
michael@0 215 // beforeunload is still needed because pagehide doesn't fire before the page is unloaded.
michael@0 216 // still use pagehide for cases when beforeunload doesn't get fired
michael@0 217 var beforeUnloadHandler = function (aEvent) {
michael@0 218 var doc = aEvent.originalTarget;
michael@0 219
michael@0 220 // Only update the flag if we have a document as target
michael@0 221 if ("defaultView" in doc) {
michael@0 222 var id = utils.getWindowId(doc.defaultView);
michael@0 223 // dump("*** 'beforeunload' event: id=" + id + ", baseURI=" + doc.baseURI + "\n");
michael@0 224 map.updatePageLoadStatus(id, false);
michael@0 225 }
michael@0 226
michael@0 227 aWindow.removeEventListener("beforeunload", beforeUnloadHandler, true);
michael@0 228 };
michael@0 229
michael@0 230 var pageHideHandler = function (aEvent) {
michael@0 231 var doc = aEvent.originalTarget;
michael@0 232
michael@0 233 // Only update the flag if we have a document as target
michael@0 234 if ("defaultView" in doc) {
michael@0 235 var id = utils.getWindowId(doc.defaultView);
michael@0 236 // dump("*** 'pagehide' event: id=" + id + ", baseURI=" + doc.baseURI + "\n");
michael@0 237 map.updatePageLoadStatus(id, false);
michael@0 238 }
michael@0 239 // If event.persisted is true the beforeUnloadHandler would never fire
michael@0 240 // and we have to remove the event handler here to avoid memory leaks.
michael@0 241 if (aEvent.persisted)
michael@0 242 aWindow.removeEventListener("beforeunload", beforeUnloadHandler, true);
michael@0 243 };
michael@0 244
michael@0 245 var onWindowLoaded = function (aEvent) {
michael@0 246 var id = utils.getWindowId(aWindow);
michael@0 247 // dump("*** 'load' event: id=" + id + ", baseURI=" + aWindow.document.baseURI + "\n");
michael@0 248
michael@0 249 map.update(id, "loaded", true);
michael@0 250
michael@0 251 // Note: Error pages will never fire a "pageshow" event. For those we
michael@0 252 // have to wait for the "DOMContentLoaded" event. That's the final state.
michael@0 253 // Error pages will always have a baseURI starting with
michael@0 254 // "about:" followed by "error" or "blocked".
michael@0 255 aWindow.addEventListener("DOMContentLoaded", DOMContentLoadedHandler, true);
michael@0 256
michael@0 257 // Page is ready
michael@0 258 aWindow.addEventListener("pageshow", pageShowHandler, true);
michael@0 259
michael@0 260 // Leave page (use caching)
michael@0 261 aWindow.addEventListener("pagehide", pageHideHandler, true);
michael@0 262 };
michael@0 263
michael@0 264 // If the window has already been finished loading, call the load handler
michael@0 265 // directly. Otherwise attach it to the current window.
michael@0 266 if (aWindow.document.readyState === 'complete') {
michael@0 267 onWindowLoaded();
michael@0 268 } else {
michael@0 269 aWindow.addEventListener("load", onWindowLoaded, false);
michael@0 270 }
michael@0 271 }
michael@0 272
michael@0 273 // Attach event listeners to all already open top-level windows
michael@0 274 function handleAttachEventListeners() {
michael@0 275 var enumerator = Cc["@mozilla.org/appshell/window-mediator;1"].
michael@0 276 getService(Ci.nsIWindowMediator).getEnumerator("");
michael@0 277 while (enumerator.hasMoreElements()) {
michael@0 278 var win = enumerator.getNext();
michael@0 279 attachEventListeners(win);
michael@0 280 }
michael@0 281 }
michael@0 282
michael@0 283 function init() {
michael@0 284 // Activate observer for new top level windows
michael@0 285 var observerService = Cc["@mozilla.org/observer-service;1"].
michael@0 286 getService(Ci.nsIObserverService);
michael@0 287 observerService.addObserver(windowReadyObserver, "toplevel-window-ready", false);
michael@0 288 observerService.addObserver(windowCloseObserver, "outer-window-destroyed", false);
michael@0 289 observerService.addObserver(enterLeavePrivateBrowsingObserver, "private-browsing", false);
michael@0 290
michael@0 291 handleAttachEventListeners();
michael@0 292 }

mercurial