diff options
Diffstat (limited to 'dom/downloads/DownloadsAPI.js')
-rw-r--r-- | dom/downloads/DownloadsAPI.js | 517 |
1 files changed, 0 insertions, 517 deletions
diff --git a/dom/downloads/DownloadsAPI.js b/dom/downloads/DownloadsAPI.js deleted file mode 100644 index 8294e2a3e..000000000 --- a/dom/downloads/DownloadsAPI.js +++ /dev/null @@ -1,517 +0,0 @@ -/* 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"; - -const Cc = Components.classes; -const Ci = Components.interfaces; -const Cu = Components.utils; -const Cr = Components.results; - -Cu.import("resource://gre/modules/XPCOMUtils.jsm"); -Cu.import("resource://gre/modules/Services.jsm"); -Cu.import("resource://gre/modules/DOMRequestHelper.jsm"); -Cu.import("resource://gre/modules/DownloadsIPC.jsm"); - -XPCOMUtils.defineLazyServiceGetter(this, "cpmm", - "@mozilla.org/childprocessmessagemanager;1", - "nsIMessageSender"); -XPCOMUtils.defineLazyServiceGetter(this, "volumeService", - "@mozilla.org/telephony/volume-service;1", - "nsIVolumeService"); - -/** - * The content process implementations of navigator.mozDownloadManager and its - * DOMDownload download objects. Uses DownloadsIPC.jsm to communicate with - * DownloadsAPI.jsm in the parent process. - */ - -function debug(aStr) { -#ifdef MOZ_DEBUG - dump("-*- DownloadsAPI.js : " + aStr + "\n"); -#endif -} - -function DOMDownloadManagerImpl() { - debug("DOMDownloadManagerImpl constructor"); -} - -DOMDownloadManagerImpl.prototype = { - __proto__: DOMRequestIpcHelper.prototype, - - // nsIDOMGlobalPropertyInitializer implementation - init: function(aWindow) { - debug("DownloadsManager init"); - this.initDOMRequestHelper(aWindow, - ["Downloads:Added", - "Downloads:Removed"]); - - // Get the manifest URL if this is an installed app - let appsService = Cc["@mozilla.org/AppsService;1"] - .getService(Ci.nsIAppsService); - let principal = aWindow.document.nodePrincipal; - // This returns the empty string if we're not an installed app. Coerce to - // null. - this._manifestURL = appsService.getManifestURLByLocalId(principal.appId) || - null; - }, - - uninit: function() { - debug("uninit"); - downloadsCache.evict(this._window); - }, - - set ondownloadstart(aHandler) { - this.__DOM_IMPL__.setEventHandler("ondownloadstart", aHandler); - }, - - get ondownloadstart() { - return this.__DOM_IMPL__.getEventHandler("ondownloadstart"); - }, - - getDownloads: function() { - debug("getDownloads()"); - - return this.createPromise(function (aResolve, aReject) { - DownloadsIPC.getDownloads().then( - function(aDownloads) { - // Turn the list of download objects into DOM objects and - // send them. - let array = new this._window.Array(); - for (let id in aDownloads) { - let dom = createDOMDownloadObject(this._window, aDownloads[id]); - array.push(this._prepareForContent(dom)); - } - aResolve(array); - }.bind(this), - function() { - aReject("GetDownloadsError"); - } - ); - }.bind(this)); - }, - - clearAllDone: function() { - debug("clearAllDone()"); - // This is a void function; we just kick it off. No promises, etc. - DownloadsIPC.clearAllDone(); - }, - - remove: function(aDownload) { - debug("remove " + aDownload.url + " " + aDownload.id); - return this.createPromise(function (aResolve, aReject) { - if (!downloadsCache.has(this._window, aDownload.id)) { - debug("no download " + aDownload.id); - aReject("InvalidDownload"); - return; - } - - DownloadsIPC.remove(aDownload.id).then( - function(aResult) { - let dom = createDOMDownloadObject(this._window, aResult); - // Change the state right away to not race against the update message. - dom.wrappedJSObject.state = "finalized"; - aResolve(this._prepareForContent(dom)); - }.bind(this), - function() { - aReject("RemoveError"); - } - ); - }.bind(this)); - }, - - adoptDownload: function(aAdoptDownloadDict) { - // Our AdoptDownloadDict only includes simple types, which WebIDL enforces. - // We have no object/any types so we do not need to worry about invoking - // JSON.stringify (and it inheriting our security privileges). - debug("adoptDownload"); - return this.createPromise(function (aResolve, aReject) { - if (!aAdoptDownloadDict) { - debug("Download dictionary is required!"); - aReject("InvalidDownload"); - return; - } - if (!aAdoptDownloadDict.storageName || !aAdoptDownloadDict.storagePath || - !aAdoptDownloadDict.contentType) { - debug("Missing one of: storageName, storagePath, contentType"); - aReject("InvalidDownload"); - return; - } - - // Convert storageName/storagePath to a local filesystem path. - let volume; - // getVolumeByName throws if you give it something it doesn't like - // because XPConnect converts the NS_ERROR_NOT_AVAILABLE to an - // exception. So catch it. - try { - volume = volumeService.getVolumeByName(aAdoptDownloadDict.storageName); - } catch (ex) {} - if (!volume) { - debug("Invalid storage name: " + aAdoptDownloadDict.storageName); - aReject("InvalidDownload"); - return; - } - let computedPath = volume.mountPoint + '/' + - aAdoptDownloadDict.storagePath; - // We validate that there is actually a file at the given path in the - // parent process in DownloadsAPI.js because that's where the file - // access would actually occur either way. - - // Create a DownloadsAPI.jsm 'jsonDownload' style representation. - let jsonDownload = { - url: aAdoptDownloadDict.url, - path: computedPath, - contentType: aAdoptDownloadDict.contentType, - startTime: aAdoptDownloadDict.startTime.valueOf() || Date.now(), - sourceAppManifestURL: this._manifestURL - }; - - DownloadsIPC.adoptDownload(jsonDownload).then( - function(aResult) { - let domDownload = createDOMDownloadObject(this._window, aResult); - aResolve(this._prepareForContent(domDownload)); - }.bind(this), - function(aResult) { - // This will be one of: AdoptError (generic catch-all), - // AdoptNoSuchFile, AdoptFileIsDirectory - aReject(aResult.error); - } - ); - }.bind(this)); - }, - - - /** - * Turns a chrome download object into a content accessible one. - * When we have __DOM_IMPL__ available we just use that, otherwise - * we run _create() with the wrapped js object. - */ - _prepareForContent: function(aChromeObject) { - if (aChromeObject.__DOM_IMPL__) { - return aChromeObject.__DOM_IMPL__; - } - let res = this._window.DOMDownload._create(this._window, - aChromeObject.wrappedJSObject); - return res; - }, - - receiveMessage: function(aMessage) { - let data = aMessage.data; - switch(aMessage.name) { - case "Downloads:Added": - debug("Adding " + uneval(data)); - let event = new this._window.DownloadEvent("downloadstart", { - download: - this._prepareForContent(createDOMDownloadObject(this._window, data)) - }); - this.__DOM_IMPL__.dispatchEvent(event); - break; - } - }, - - classID: Components.ID("{c6587afa-0696-469f-9eff-9dac0dd727fe}"), - QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, - Ci.nsISupportsWeakReference, - Ci.nsIObserver, - Ci.nsIDOMGlobalPropertyInitializer]), - -}; - -/** - * Keep track of download objects per window. - */ -var downloadsCache = { - init: function() { - this.cache = new WeakMap(); - }, - - has: function(aWindow, aId) { - let downloads = this.cache.get(aWindow); - return !!(downloads && downloads[aId]); - }, - - get: function(aWindow, aDownload) { - let downloads = this.cache.get(aWindow); - if (!(downloads && downloads[aDownload.id])) { - debug("Adding download " + aDownload.id + " to cache."); - if (!downloads) { - this.cache.set(aWindow, {}); - downloads = this.cache.get(aWindow); - } - // Create the object and add it to the cache. - let impl = Cc["@mozilla.org/downloads/download;1"] - .createInstance(Ci.nsISupports); - impl.wrappedJSObject._init(aWindow, aDownload); - downloads[aDownload.id] = impl; - } - return downloads[aDownload.id]; - }, - - evict: function(aWindow) { - this.cache.delete(aWindow); - } -}; - -downloadsCache.init(); - -/** - * The DOM facade of a download object. - */ - -function createDOMDownloadObject(aWindow, aDownload) { - return downloadsCache.get(aWindow, aDownload); -} - -function DOMDownloadImpl() { - debug("DOMDownloadImpl constructor "); - - this.wrappedJSObject = this; - this.totalBytes = 0; - this.currentBytes = 0; - this.url = null; - this.path = null; - this.storageName = null; - this.storagePath = null; - this.contentType = null; - - /* fields that require getters/setters */ - this._error = null; - this._startTime = new Date(); - this._state = "stopped"; - - /* private fields */ - this.id = null; -} - -DOMDownloadImpl.prototype = { - - createPromise: function(aPromiseInit) { - return new this._window.Promise(aPromiseInit); - }, - - pause: function() { - debug("DOMDownloadImpl pause"); - let id = this.id; - // We need to wrap the Promise.jsm promise in a "real" DOM promise... - return this.createPromise(function(aResolve, aReject) { - DownloadsIPC.pause(id).then(aResolve, aReject); - }); - }, - - resume: function() { - debug("DOMDownloadImpl resume"); - let id = this.id; - // We need to wrap the Promise.jsm promise in a "real" DOM promise... - return this.createPromise(function(aResolve, aReject) { - DownloadsIPC.resume(id).then(aResolve, aReject); - }); - }, - - set onstatechange(aHandler) { - this.__DOM_IMPL__.setEventHandler("onstatechange", aHandler); - }, - - get onstatechange() { - return this.__DOM_IMPL__.getEventHandler("onstatechange"); - }, - - get error() { - return this._error; - }, - - set error(aError) { - this._error = aError; - }, - - get startTime() { - return this._startTime; - }, - - set startTime(aStartTime) { - if (aStartTime instanceof Date) { - this._startTime = aStartTime; - } - else { - this._startTime = new Date(aStartTime); - } - }, - - get state() { - return this._state; - }, - - // We require a setter here to simplify the internals of the Download Manager - // since we actually pass dummy JSON objects to the child process and update - // them. This is the case for all other setters for read-only attributes - // implemented in this object. - set state(aState) { - // We need to ensure that XPCOM consumers of this API respect the enum - // values as well. - if (["downloading", - "stopped", - "succeeded", - "finalized"].indexOf(aState) != -1) { - this._state = aState; - } - }, - - /** - * Initialize a DOMDownload instance for the given window using the - * 'jsonDownload' serialized format of the download encoded by - * DownloadsAPI.jsm. - */ - _init: function(aWindow, aDownload) { - this._window = aWindow; - this.id = aDownload.id; - this._update(aDownload); - Services.obs.addObserver(this, "downloads-state-change-" + this.id, - /* ownsWeak */ true); - debug("observer set for " + this.id); - }, - - /** - * Updates the state of the object and fires the statechange event. - */ - _update: function(aDownload) { - debug("update " + uneval(aDownload)); - if (this.id != aDownload.id) { - return; - } - - let props = ["totalBytes", "currentBytes", "url", "path", "storageName", - "storagePath", "state", "contentType", "startTime", - "sourceAppManifestURL"]; - let changed = false; - let changedProps = {}; - - props.forEach((prop) => { - if (prop in aDownload && (aDownload[prop] != this[prop])) { - this[prop] = aDownload[prop]; - changedProps[prop] = changed = true; - } - }); - - // When the path changes, we should update the storage name and - // storage path used for our downloaded file in case our download - // was re-targetted to a different storage and/or filename. - if (changedProps["path"]) { - let storages = this._window.navigator.getDeviceStorages("sdcard"); - let preferredStorageName; - // Use the first one or the default storage. Just like jsdownloads picks - // the default / preferred download directory. - storages.forEach((aStorage) => { - if (aStorage.default || !preferredStorageName) { - preferredStorageName = aStorage.storageName; - } - }); - // Now get the path for this storage area. - let volume; - if (preferredStorageName) { - let volume = volumeService.getVolumeByName(preferredStorageName); - if (volume) { - // Finally, create the relative path of the file that can be used - // later on to retrieve the file via DeviceStorage. Our path - // needs to omit the starting '/'. - this.storageName = preferredStorageName; - this.storagePath = - this.path.substring(this.path.indexOf(volume.mountPoint) + - volume.mountPoint.length + 1); - } - } - } - - if (aDownload.error) { - // - // When we get a generic error failure back from the js downloads api - // we will verify the status of device storage to see if we can't provide - // a better error result value. - // - // XXX If these checks expand further, consider moving them into their - // own function. - // - let result = aDownload.error.result; - let storage = this._window.navigator.getDeviceStorage("sdcard"); - - // If we don't have access to device storage we'll opt out of these - // extra checks as they are all dependent on the state of the storage. - if (result == Cr.NS_ERROR_FAILURE && storage) { - // We will delay sending the notification until we've inferred which - // error is really happening. - changed = false; - debug("Attempting to infer error via device storage sanity checks."); - // Get device storage and request availability status. - let available = storage.available(); - available.onsuccess = (function() { - debug("Storage Status = '" + available.result + "'"); - let inferredError = result; - switch (available.result) { - case "unavailable": - inferredError = Cr.NS_ERROR_FILE_NOT_FOUND; - break; - case "shared": - inferredError = Cr.NS_ERROR_FILE_ACCESS_DENIED; - break; - } - this._updateWithError(aDownload, inferredError); - }).bind(this); - available.onerror = (function() { - this._updateWithError(aDownload, result); - }).bind(this); - } - - this.error = - new this._window.DOMError("DownloadError", result); - } else { - this.error = null; - } - - // The visible state has not changed, so no need to fire an event. - if (!changed) { - return; - } - - this._sendStateChange(); - }, - - _updateWithError: function(aDownload, aError) { - this.error = - new this._window.DOMError("DownloadError", aError); - this._sendStateChange(); - }, - - _sendStateChange: function() { - // __DOM_IMPL__ may not be available at first update. - if (this.__DOM_IMPL__) { - let event = new this._window.DownloadEvent("statechange", { - download: this.__DOM_IMPL__ - }); - debug("Dispatching statechange event. state=" + this.state); - this.__DOM_IMPL__.dispatchEvent(event); - } - }, - - observe: function(aSubject, aTopic, aData) { - debug("DOMDownloadImpl observe " + aTopic); - if (aTopic !== "downloads-state-change-" + this.id) { - return; - } - - try { - let download = JSON.parse(aData); - // We get the start time as milliseconds, not as a Date object. - if (download.startTime) { - download.startTime = new Date(download.startTime); - } - this._update(download); - } catch(e) {} - }, - - classID: Components.ID("{96b81b99-aa96-439d-8c59-92eeed34705f}"), - QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, - Ci.nsIObserver, - Ci.nsISupportsWeakReference]) -}; - -this.NSGetFactory = XPCOMUtils.generateNSGetFactory([DOMDownloadManagerImpl, - DOMDownloadImpl]); |