dom/downloads/src/DownloadsAPI.js

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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 "use strict";
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 const Cr = Components.results;
michael@0 11
michael@0 12 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
michael@0 13 Cu.import("resource://gre/modules/Services.jsm");
michael@0 14 Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
michael@0 15 Cu.import("resource://gre/modules/DownloadsIPC.jsm");
michael@0 16
michael@0 17 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
michael@0 18 "@mozilla.org/childprocessmessagemanager;1",
michael@0 19 "nsIMessageSender");
michael@0 20
michael@0 21 function debug(aStr) {
michael@0 22 #ifdef MOZ_DEBUG
michael@0 23 dump("-*- DownloadsAPI.js : " + aStr + "\n");
michael@0 24 #endif
michael@0 25 }
michael@0 26
michael@0 27 function DOMDownloadManagerImpl() {
michael@0 28 debug("DOMDownloadManagerImpl constructor");
michael@0 29 }
michael@0 30
michael@0 31 DOMDownloadManagerImpl.prototype = {
michael@0 32 __proto__: DOMRequestIpcHelper.prototype,
michael@0 33
michael@0 34 // nsIDOMGlobalPropertyInitializer implementation
michael@0 35 init: function(aWindow) {
michael@0 36 debug("DownloadsManager init");
michael@0 37 this.initDOMRequestHelper(aWindow,
michael@0 38 ["Downloads:Added",
michael@0 39 "Downloads:Removed"]);
michael@0 40 },
michael@0 41
michael@0 42 uninit: function() {
michael@0 43 debug("uninit");
michael@0 44 downloadsCache.evict(this._window);
michael@0 45 },
michael@0 46
michael@0 47 set ondownloadstart(aHandler) {
michael@0 48 this.__DOM_IMPL__.setEventHandler("ondownloadstart", aHandler);
michael@0 49 },
michael@0 50
michael@0 51 get ondownloadstart() {
michael@0 52 return this.__DOM_IMPL__.getEventHandler("ondownloadstart");
michael@0 53 },
michael@0 54
michael@0 55 getDownloads: function() {
michael@0 56 debug("getDownloads()");
michael@0 57
michael@0 58 return this.createPromise(function (aResolve, aReject) {
michael@0 59 DownloadsIPC.getDownloads().then(
michael@0 60 function(aDownloads) {
michael@0 61 // Turn the list of download objects into DOM objects and
michael@0 62 // send them.
michael@0 63 let array = new this._window.Array();
michael@0 64 for (let id in aDownloads) {
michael@0 65 let dom = createDOMDownloadObject(this._window, aDownloads[id]);
michael@0 66 array.push(this._prepareForContent(dom));
michael@0 67 }
michael@0 68 aResolve(array);
michael@0 69 }.bind(this),
michael@0 70 function() {
michael@0 71 aReject("GetDownloadsError");
michael@0 72 }
michael@0 73 );
michael@0 74 }.bind(this));
michael@0 75 },
michael@0 76
michael@0 77 clearAllDone: function() {
michael@0 78 debug("clearAllDone()");
michael@0 79 return this.createPromise(function (aResolve, aReject) {
michael@0 80 DownloadsIPC.clearAllDone().then(
michael@0 81 function(aDownloads) {
michael@0 82 // Turn the list of download objects into DOM objects and
michael@0 83 // send them.
michael@0 84 let array = new this._window.Array();
michael@0 85 for (let id in aDownloads) {
michael@0 86 let dom = createDOMDownloadObject(this._window, aDownloads[id]);
michael@0 87 array.push(this._prepareForContent(dom));
michael@0 88 }
michael@0 89 aResolve(array);
michael@0 90 }.bind(this),
michael@0 91 function() {
michael@0 92 aReject("ClearAllDoneError");
michael@0 93 }
michael@0 94 );
michael@0 95 }.bind(this));
michael@0 96 },
michael@0 97
michael@0 98 remove: function(aDownload) {
michael@0 99 debug("remove " + aDownload.url + " " + aDownload.id);
michael@0 100 return this.createPromise(function (aResolve, aReject) {
michael@0 101 if (!downloadsCache.has(this._window, aDownload.id)) {
michael@0 102 debug("no download " + aDownload.id);
michael@0 103 aReject("InvalidDownload");
michael@0 104 return;
michael@0 105 }
michael@0 106
michael@0 107 DownloadsIPC.remove(aDownload.id).then(
michael@0 108 function(aResult) {
michael@0 109 let dom = createDOMDownloadObject(this._window, aResult);
michael@0 110 // Change the state right away to not race against the update message.
michael@0 111 dom.wrappedJSObject.state = "finalized";
michael@0 112 aResolve(this._prepareForContent(dom));
michael@0 113 }.bind(this),
michael@0 114 function() {
michael@0 115 aReject("RemoveError");
michael@0 116 }
michael@0 117 );
michael@0 118 }.bind(this));
michael@0 119 },
michael@0 120
michael@0 121 /**
michael@0 122 * Turns a chrome download object into a content accessible one.
michael@0 123 * When we have __DOM_IMPL__ available we just use that, otherwise
michael@0 124 * we run _create() with the wrapped js object.
michael@0 125 */
michael@0 126 _prepareForContent: function(aChromeObject) {
michael@0 127 if (aChromeObject.__DOM_IMPL__) {
michael@0 128 return aChromeObject.__DOM_IMPL__;
michael@0 129 }
michael@0 130 let res = this._window.DOMDownload._create(this._window,
michael@0 131 aChromeObject.wrappedJSObject);
michael@0 132 return res;
michael@0 133 },
michael@0 134
michael@0 135 receiveMessage: function(aMessage) {
michael@0 136 let data = aMessage.data;
michael@0 137 switch(aMessage.name) {
michael@0 138 case "Downloads:Added":
michael@0 139 debug("Adding " + uneval(data));
michael@0 140 let event = new this._window.DownloadEvent("downloadstart", {
michael@0 141 download:
michael@0 142 this._prepareForContent(createDOMDownloadObject(this._window, data))
michael@0 143 });
michael@0 144 this.__DOM_IMPL__.dispatchEvent(event);
michael@0 145 break;
michael@0 146 }
michael@0 147 },
michael@0 148
michael@0 149 classID: Components.ID("{c6587afa-0696-469f-9eff-9dac0dd727fe}"),
michael@0 150 QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
michael@0 151 Ci.nsISupportsWeakReference,
michael@0 152 Ci.nsIObserver,
michael@0 153 Ci.nsIDOMGlobalPropertyInitializer]),
michael@0 154
michael@0 155 };
michael@0 156
michael@0 157 /**
michael@0 158 * Keep track of download objects per window.
michael@0 159 */
michael@0 160 let downloadsCache = {
michael@0 161 init: function() {
michael@0 162 this.cache = new WeakMap();
michael@0 163 },
michael@0 164
michael@0 165 has: function(aWindow, aId) {
michael@0 166 let downloads = this.cache.get(aWindow);
michael@0 167 return !!(downloads && downloads[aId]);
michael@0 168 },
michael@0 169
michael@0 170 get: function(aWindow, aDownload) {
michael@0 171 let downloads = this.cache.get(aWindow);
michael@0 172 if (!(downloads && downloads[aDownload.id])) {
michael@0 173 debug("Adding download " + aDownload.id + " to cache.");
michael@0 174 if (!downloads) {
michael@0 175 this.cache.set(aWindow, {});
michael@0 176 downloads = this.cache.get(aWindow);
michael@0 177 }
michael@0 178 // Create the object and add it to the cache.
michael@0 179 let impl = Cc["@mozilla.org/downloads/download;1"]
michael@0 180 .createInstance(Ci.nsISupports);
michael@0 181 impl.wrappedJSObject._init(aWindow, aDownload);
michael@0 182 downloads[aDownload.id] = impl;
michael@0 183 }
michael@0 184 return downloads[aDownload.id];
michael@0 185 },
michael@0 186
michael@0 187 evict: function(aWindow) {
michael@0 188 this.cache.delete(aWindow);
michael@0 189 }
michael@0 190 };
michael@0 191
michael@0 192 downloadsCache.init();
michael@0 193
michael@0 194 /**
michael@0 195 * The DOM facade of a download object.
michael@0 196 */
michael@0 197
michael@0 198 function createDOMDownloadObject(aWindow, aDownload) {
michael@0 199 return downloadsCache.get(aWindow, aDownload);
michael@0 200 }
michael@0 201
michael@0 202 function DOMDownloadImpl() {
michael@0 203 debug("DOMDownloadImpl constructor ");
michael@0 204
michael@0 205 this.wrappedJSObject = this;
michael@0 206 this.totalBytes = 0;
michael@0 207 this.currentBytes = 0;
michael@0 208 this.url = null;
michael@0 209 this.path = null;
michael@0 210 this.contentType = null;
michael@0 211
michael@0 212 /* fields that require getters/setters */
michael@0 213 this._error = null;
michael@0 214 this._startTime = new Date();
michael@0 215 this._state = "stopped";
michael@0 216
michael@0 217 /* private fields */
michael@0 218 this.id = null;
michael@0 219 }
michael@0 220
michael@0 221 DOMDownloadImpl.prototype = {
michael@0 222
michael@0 223 createPromise: function(aPromiseInit) {
michael@0 224 return new this._window.Promise(aPromiseInit);
michael@0 225 },
michael@0 226
michael@0 227 pause: function() {
michael@0 228 debug("DOMDownloadImpl pause");
michael@0 229 let id = this.id;
michael@0 230 // We need to wrap the Promise.jsm promise in a "real" DOM promise...
michael@0 231 return this.createPromise(function(aResolve, aReject) {
michael@0 232 DownloadsIPC.pause(id).then(aResolve, aReject);
michael@0 233 });
michael@0 234 },
michael@0 235
michael@0 236 resume: function() {
michael@0 237 debug("DOMDownloadImpl resume");
michael@0 238 let id = this.id;
michael@0 239 // We need to wrap the Promise.jsm promise in a "real" DOM promise...
michael@0 240 return this.createPromise(function(aResolve, aReject) {
michael@0 241 DownloadsIPC.resume(id).then(aResolve, aReject);
michael@0 242 });
michael@0 243 },
michael@0 244
michael@0 245 set onstatechange(aHandler) {
michael@0 246 this.__DOM_IMPL__.setEventHandler("onstatechange", aHandler);
michael@0 247 },
michael@0 248
michael@0 249 get onstatechange() {
michael@0 250 return this.__DOM_IMPL__.getEventHandler("onstatechange");
michael@0 251 },
michael@0 252
michael@0 253 get error() {
michael@0 254 return this._error;
michael@0 255 },
michael@0 256
michael@0 257 set error(aError) {
michael@0 258 this._error = aError;
michael@0 259 },
michael@0 260
michael@0 261 get startTime() {
michael@0 262 return this._startTime;
michael@0 263 },
michael@0 264
michael@0 265 set startTime(aStartTime) {
michael@0 266 if (aStartTime instanceof Date) {
michael@0 267 this._startTime = aStartTime;
michael@0 268 }
michael@0 269 else {
michael@0 270 this._startTime = new Date(aStartTime);
michael@0 271 }
michael@0 272 },
michael@0 273
michael@0 274 get state() {
michael@0 275 return this._state;
michael@0 276 },
michael@0 277
michael@0 278 // We require a setter here to simplify the internals of the Download Manager
michael@0 279 // since we actually pass dummy JSON objects to the child process and update
michael@0 280 // them. This is the case for all other setters for read-only attributes
michael@0 281 // implemented in this object.
michael@0 282 set state(aState) {
michael@0 283 // We need to ensure that XPCOM consumers of this API respect the enum
michael@0 284 // values as well.
michael@0 285 if (["downloading",
michael@0 286 "stopped",
michael@0 287 "succeeded",
michael@0 288 "finalized"].indexOf(aState) != -1) {
michael@0 289 this._state = aState;
michael@0 290 }
michael@0 291 },
michael@0 292
michael@0 293 _init: function(aWindow, aDownload) {
michael@0 294 this._window = aWindow;
michael@0 295 this.id = aDownload.id;
michael@0 296 this._update(aDownload);
michael@0 297 Services.obs.addObserver(this, "downloads-state-change-" + this.id,
michael@0 298 /* ownsWeak */ true);
michael@0 299 debug("observer set for " + this.id);
michael@0 300 },
michael@0 301
michael@0 302 /**
michael@0 303 * Updates the state of the object and fires the statechange event.
michael@0 304 */
michael@0 305 _update: function(aDownload) {
michael@0 306 debug("update " + uneval(aDownload));
michael@0 307 if (this.id != aDownload.id) {
michael@0 308 return;
michael@0 309 }
michael@0 310
michael@0 311 let props = ["totalBytes", "currentBytes", "url", "path", "state",
michael@0 312 "contentType", "startTime"];
michael@0 313 let changed = false;
michael@0 314
michael@0 315 props.forEach((prop) => {
michael@0 316 if (aDownload[prop] && (aDownload[prop] != this[prop])) {
michael@0 317 this[prop] = aDownload[prop];
michael@0 318 changed = true;
michael@0 319 }
michael@0 320 });
michael@0 321
michael@0 322 if (aDownload.error) {
michael@0 323 //
michael@0 324 // When we get a generic error failure back from the js downloads api
michael@0 325 // we will verify the status of device storage to see if we can't provide
michael@0 326 // a better error result value.
michael@0 327 //
michael@0 328 // XXX If these checks expand further, consider moving them into their
michael@0 329 // own function.
michael@0 330 //
michael@0 331 let result = aDownload.error.result;
michael@0 332 let storage = this._window.navigator.getDeviceStorage("sdcard");
michael@0 333
michael@0 334 // If we don't have access to device storage we'll opt out of these
michael@0 335 // extra checks as they are all dependent on the state of the storage.
michael@0 336 if (result == Cr.NS_ERROR_FAILURE && storage) {
michael@0 337 // We will delay sending the notification until we've inferred which
michael@0 338 // error is really happening.
michael@0 339 changed = false;
michael@0 340 debug("Attempting to infer error via device storage sanity checks.");
michael@0 341 // Get device storage and request availability status.
michael@0 342 let available = storage.available();
michael@0 343 available.onsuccess = (function() {
michael@0 344 debug("Storage Status = '" + available.result + "'");
michael@0 345 let inferredError = result;
michael@0 346 switch (available.result) {
michael@0 347 case "unavailable":
michael@0 348 inferredError = Cr.NS_ERROR_FILE_NOT_FOUND;
michael@0 349 break;
michael@0 350 case "shared":
michael@0 351 inferredError = Cr.NS_ERROR_FILE_ACCESS_DENIED;
michael@0 352 break;
michael@0 353 }
michael@0 354 this._updateWithError(aDownload, inferredError);
michael@0 355 }).bind(this);
michael@0 356 available.onerror = (function() {
michael@0 357 this._updateWithError(aDownload, result);
michael@0 358 }).bind(this);
michael@0 359 }
michael@0 360
michael@0 361 this.error =
michael@0 362 new this._window.DOMError("DownloadError", result);
michael@0 363 } else {
michael@0 364 this.error = null;
michael@0 365 }
michael@0 366
michael@0 367 // The visible state has not changed, so no need to fire an event.
michael@0 368 if (!changed) {
michael@0 369 return;
michael@0 370 }
michael@0 371
michael@0 372 this._sendStateChange();
michael@0 373 },
michael@0 374
michael@0 375 _updateWithError: function(aDownload, aError) {
michael@0 376 this.error =
michael@0 377 new this._window.DOMError("DownloadError", aError);
michael@0 378 this._sendStateChange();
michael@0 379 },
michael@0 380
michael@0 381 _sendStateChange: function() {
michael@0 382 // __DOM_IMPL__ may not be available at first update.
michael@0 383 if (this.__DOM_IMPL__) {
michael@0 384 let event = new this._window.DownloadEvent("statechange", {
michael@0 385 download: this.__DOM_IMPL__
michael@0 386 });
michael@0 387 debug("Dispatching statechange event. state=" + this.state);
michael@0 388 this.__DOM_IMPL__.dispatchEvent(event);
michael@0 389 }
michael@0 390 },
michael@0 391
michael@0 392 observe: function(aSubject, aTopic, aData) {
michael@0 393 debug("DOMDownloadImpl observe " + aTopic);
michael@0 394 if (aTopic !== "downloads-state-change-" + this.id) {
michael@0 395 return;
michael@0 396 }
michael@0 397
michael@0 398 try {
michael@0 399 let download = JSON.parse(aData);
michael@0 400 // We get the start time as milliseconds, not as a Date object.
michael@0 401 if (download.startTime) {
michael@0 402 download.startTime = new Date(download.startTime);
michael@0 403 }
michael@0 404 this._update(download);
michael@0 405 } catch(e) {}
michael@0 406 },
michael@0 407
michael@0 408 classID: Components.ID("{96b81b99-aa96-439d-8c59-92eeed34705f}"),
michael@0 409 QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
michael@0 410 Ci.nsIObserver,
michael@0 411 Ci.nsISupportsWeakReference])
michael@0 412 };
michael@0 413
michael@0 414 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DOMDownloadManagerImpl,
michael@0 415 DOMDownloadImpl]);

mercurial