diff options
Diffstat (limited to 'toolkit/components/bitsdownload/Bits.sys.mjs')
-rw-r--r-- | toolkit/components/bitsdownload/Bits.sys.mjs | 826 |
1 files changed, 826 insertions, 0 deletions
diff --git a/toolkit/components/bitsdownload/Bits.sys.mjs b/toolkit/components/bitsdownload/Bits.sys.mjs new file mode 100644 index 0000000000..3248d2effa --- /dev/null +++ b/toolkit/components/bitsdownload/Bits.sys.mjs @@ -0,0 +1,826 @@ +/* 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/. */ + +/** + * This module is used to interact with the Windows BITS component (Background + * Intelligent Transfer Service). This functionality cannot be used unless on + * Windows. + * + * The reason for this file's existence is that the interfaces in nsIBits.idl + * are asynchronous, but are unable to use Promises because they are implemented + * in Rust, which does not yet support Promises. This file functions as a layer + * between the Rust and the JS that provides access to the functionality + * provided by nsIBits via Promises rather than callbacks. + */ + +import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs"; +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const lazy = {}; + +// This conditional prevents errors if this file is imported from operating +// systems other than Windows. This is purely for convenient importing, because +// attempting to use anything in this file on platforms other than Windows will +// result in an error. +if (AppConstants.MOZ_BITS_DOWNLOAD) { + XPCOMUtils.defineLazyServiceGetter( + lazy, + "gBits", + "@mozilla.org/bits;1", + "nsIBits" + ); +} + +// This value exists to mitigate a very unlikely problem: If a BITS method +// catastrophically fails, it may never call its callback. This would result in +// methods in this file returning promises that never resolve. This could, in +// turn, lead to download code hanging altogether rather than being able to +// report errors and utilize fallback mechanisms. +// This problem is mitigated by giving these promises a timeout, the length of +// which will be determined by this value. +const kBitsMethodTimeoutMs = 10 * 60 * 1000; // 10 minutes + +/** + * This class will wrap the errors returned by the nsIBits interface to make + * them more uniform and more easily consumable. + * + * The values of stored by this error type are entirely numeric. This should + * make them easier to consume with JS and telemetry, but does make them fairly + * unreadable. nsIBits.idl will need to be referenced to look up what errors + * the values correspond to. + * + * The type of BitsError.code is dependent on the value of BitsError.codeType. + * It may be null, a number (corresponding to an nsresult or hresult value), + * a string, or an exception. + */ +export class BitsError extends Error { + // If codeType == "none", code may be unspecified. + constructor(type, action, stage, codeType, code) { + let message = + `${BitsError.name} {type: ${type}, action: ${action}, ` + + `stage: ${stage}`; + switch (codeType) { + case lazy.gBits.ERROR_CODE_TYPE_NONE: + code = null; + message += ", codeType: none}"; + break; + case lazy.gBits.ERROR_CODE_TYPE_NSRESULT: + message += `, codeType: nsresult, code: ${code}}`; + break; + case lazy.gBits.ERROR_CODE_TYPE_HRESULT: + message += `, codeType: hresult, code: ${code}}`; + break; + case lazy.gBits.ERROR_CODE_TYPE_STRING: + message += `, codeType: string, code: ${JSON.stringify(code)}}`; + break; + case lazy.gBits.ERROR_CODE_TYPE_EXCEPTION: + message += `, codeType: exception, code: ${code}}`; + break; + default: + message += ", codeType: invalid}"; + break; + } + super(message); + + this.type = type; + this.action = action; + this.stage = stage; + this.codeType = codeType; + this.code = code; + this.name = this.constructor.name; + this.succeeded = false; + } +} + +// These specializations exist to make them easier to construct since they may +// need to be constructed outside of this file. +export class BitsVerificationError extends BitsError { + constructor() { + super( + Ci.nsIBits.ERROR_TYPE_VERIFICATION_FAILURE, + Ci.nsIBits.ERROR_ACTION_NONE, + Ci.nsIBits.ERROR_STAGE_VERIFICATION, + Ci.nsIBits.ERROR_CODE_TYPE_NONE + ); + } +} + +export class BitsUnknownError extends BitsError { + constructor() { + super( + Ci.nsIBits.ERROR_TYPE_UNKNOWN, + Ci.nsIBits.ERROR_ACTION_UNKNOWN, + Ci.nsIBits.ERROR_STAGE_UNKNOWN, + Ci.nsIBits.ERROR_CODE_TYPE_NONE + ); + } +} + +/** + * Returns a timer object. If the timer expires, reject will be called with + * a BitsError error. The timer's cancel method should be called if the promise + * resolves or rejects without the timeout expiring. + */ +function makeTimeout(reject, errorAction) { + let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + timer.initWithCallback( + () => { + let error = new BitsError( + lazy.gBits.ERROR_TYPE_METHOD_TIMEOUT, + errorAction, + lazy.gBits.ERROR_STAGE_UNKNOWN, + lazy.gBits.ERROR_CODE_TYPE_NONE + ); + reject(error); + }, + kBitsMethodTimeoutMs, + Ci.nsITimer.TYPE_ONE_SHOT + ); + return timer; +} + +/** + * This function does all of the wrapping and error handling for an async + * BitsRequest method. This allows the implementations for those methods to + * simply call this function with a closure that executes appropriate + * nsIBitsRequest method. + * + * Specifically, this function takes an nsBitsErrorAction and a function. + * The nsBitsErrorAction will be used when constructing a BitsError, if the + * wrapper encounters an error. + * The function will be passed the callback function that should be passed to + * the nsIBitsRequest method. + */ +async function requestPromise(errorAction, actionFn) { + return new Promise((resolve, reject) => { + let timer = makeTimeout(reject, errorAction); + + let callback = { + QueryInterface: ChromeUtils.generateQI(["nsIBitsCallback"]), + success() { + timer.cancel(); + resolve(); + }, + failure(type, action, stage) { + timer.cancel(); + let error = new BitsError( + type, + action, + stage, + lazy.gBits.ERROR_CODE_TYPE_NONE + ); + reject(error); + }, + failureNsresult(type, action, stage, code) { + timer.cancel(); + let error = new BitsError( + type, + action, + stage, + lazy.gBits.ERROR_CODE_TYPE_NSRESULT, + code + ); + reject(error); + }, + failureHresult(type, action, stage, code) { + timer.cancel(); + let error = new BitsError( + type, + action, + stage, + lazy.gBits.ERROR_CODE_TYPE_HRESULT, + code + ); + reject(error); + }, + failureString(type, action, stage, message) { + timer.cancel(); + let error = new BitsError( + type, + action, + stage, + lazy.gBits.ERROR_CODE_TYPE_STRING, + message + ); + reject(error); + }, + }; + + try { + actionFn(callback); + } catch (e) { + let error = new BitsError( + lazy.gBits.ERROR_TYPE_METHOD_THREW, + errorAction, + lazy.gBits.ERROR_STAGE_PRETASK, + lazy.gBits.ERROR_CODE_TYPE_EXCEPTION, + e + ); + reject(error); + } + }); +} + +/** + * This class is a wrapper around nsIBitsRequest that converts functions taking + * callbacks to asynchronous functions. This class implements nsIRequest. + * + * Note that once the request has been shutdown, calling methods on it will + * cause an exception to be thrown. The request will be shutdown automatically + * when the BITS job is successfully completed or cancelled. If the request is + * no longer needed, but the transfer is still in progress, the shutdown method + * should be called manually to prevent memory leaks. + * Getter methods (except loadGroup and loadFlags) should continue to be + * accessible, even after shutdown. + */ +export class BitsRequest { + constructor(request) { + this._request = request; + this._request.QueryInterface(Ci.nsIBitsRequest); + } + + /** + * This function releases the Rust request that backs this wrapper. Calling + * any method on this request after calling release will result in a BitsError + * being thrown. + * + * This step is important, because otherwise a cycle exists that the cycle + * collector doesn't know about. To break this cycle, either the Rust request + * needs to let go of the observer, or the JS request wrapper needs to let go + * of the Rust request (which is what we do here). + * + * Once there is support for cycle collection of cycles that extend through + * Rust, this function may no longer be necessary. + */ + shutdown() { + if (this.hasShutdown) { + return; + } + // Cache some values before we shut down so they are still available + this._name = this._request.name; + this._status = this._request.status; + this._bitsId = this._request.bitsId; + this._transferError = this._request.transferError; + + this._request = null; + } + + /** + * Allows consumers to determine if this request has been shutdown. + */ + get hasShutdown() { + return !this._request; + } + + /** + * This is the nsIRequest implementation. Since this._request is an + * nsIRequest, these functions just call the corresponding method on it. + * + * Note that nsIBitsRequest does not yet properly implement load groups or + * load flags. This class will still forward those calls, but they will have + * not succeed. + */ + get name() { + if (!this._request) { + return this._name; + } + return this._request.name; + } + isPending() { + if (!this._request) { + return false; + } + return this._request.isPending(); + } + get status() { + if (!this._request) { + return this._status; + } + return this._request.status; + } + cancel(status) { + return this.cancelAsync(status); + } + suspend() { + if (!this._request) { + throw new BitsError( + Ci.nsIBits.ERROR_TYPE_USE_AFTER_REQUEST_SHUTDOWN, + Ci.nsIBits.ERROR_ACTION_SUSPEND, + Ci.nsIBits.ERROR_STAGE_PRETASK, + Ci.nsIBits.ERROR_CODE_TYPE_NONE + ); + } + return this._request.suspend(); + } + resume() { + if (!this._request) { + throw new BitsError( + Ci.nsIBits.ERROR_TYPE_USE_AFTER_REQUEST_SHUTDOWN, + Ci.nsIBits.ERROR_ACTION_RESUME, + Ci.nsIBits.ERROR_STAGE_PRETASK, + Ci.nsIBits.ERROR_CODE_TYPE_NONE + ); + } + return this._request.resume(); + } + get loadGroup() { + if (!this._request) { + throw new BitsError( + Ci.nsIBits.ERROR_TYPE_USE_AFTER_REQUEST_SHUTDOWN, + Ci.nsIBits.ERROR_ACTION_NONE, + Ci.nsIBits.ERROR_STAGE_PRETASK, + Ci.nsIBits.ERROR_CODE_TYPE_NONE + ); + } + return this._request.loadGroup; + } + set loadGroup(group) { + if (!this._request) { + throw new BitsError( + Ci.nsIBits.ERROR_TYPE_USE_AFTER_REQUEST_SHUTDOWN, + Ci.nsIBits.ERROR_ACTION_NONE, + Ci.nsIBits.ERROR_STAGE_PRETASK, + Ci.nsIBits.ERROR_CODE_TYPE_NONE + ); + } + this._request.loadGroup = group; + } + get loadFlags() { + if (!this._request) { + throw new BitsError( + Ci.nsIBits.ERROR_TYPE_USE_AFTER_REQUEST_SHUTDOWN, + Ci.nsIBits.ERROR_ACTION_NONE, + Ci.nsIBits.ERROR_STAGE_PRETASK, + Ci.nsIBits.ERROR_CODE_TYPE_NONE + ); + } + return this._request.loadFlags; + } + set loadFlags(flags) { + if (!this._request) { + throw new BitsError( + Ci.nsIBits.ERROR_TYPE_USE_AFTER_REQUEST_SHUTDOWN, + Ci.nsIBits.ERROR_ACTION_NONE, + Ci.nsIBits.ERROR_STAGE_PRETASK, + Ci.nsIBits.ERROR_CODE_TYPE_NONE + ); + } + this._request.loadFlags = flags; + } + + /** + * This function wraps nsIBitsRequest::bitsId. + */ + get bitsId() { + if (!this._request) { + return this._bitsId; + } + return this._request.bitsId; + } + + /** + * This function wraps nsIBitsRequest::transferError. + * + * Instead of simply returning the nsBitsErrorType value, however, it returns + * a BitsError object, or null. + */ + get transferError() { + let result; + if (this._request) { + result = this._request.transferError; + } else { + result = this._transferError; + } + if (result == Ci.nsIBits.ERROR_TYPE_SUCCESS) { + return null; + } + return new BitsError( + result, + Ci.nsIBits.ERROR_ACTION_NONE, + Ci.nsIBits.ERROR_STAGE_MONITOR, + Ci.nsIBits.ERROR_CODE_TYPE_NONE + ); + } + + /** + * This function wraps nsIBitsRequest::changeMonitorInterval. + * + * Instead of taking a callback, the function is asynchronous. + * This method either resolves with no data, or rejects with a BitsError. + */ + async changeMonitorInterval(monitorIntervalMs) { + if (!this._request) { + throw new BitsError( + Ci.nsIBits.ERROR_TYPE_USE_AFTER_REQUEST_SHUTDOWN, + Ci.nsIBits.ERROR_ACTION_CHANGE_MONITOR_INTERVAL, + Ci.nsIBits.ERROR_STAGE_PRETASK, + Ci.nsIBits.ERROR_CODE_TYPE_NONE + ); + } + let action = lazy.gBits.ERROR_ACTION_CHANGE_MONITOR_INTERVAL; + return requestPromise(action, callback => { + this._request.changeMonitorInterval(monitorIntervalMs, callback); + }); + } + + /** + * This function wraps nsIBitsRequest::cancelAsync. + * + * Instead of taking a callback, the function is asynchronous. + * This method either resolves with no data, or rejects with a BitsError. + * + * Adds a default status of NS_ERROR_ABORT if one is not provided. + */ + async cancelAsync(status) { + if (!this._request) { + throw new BitsError( + Ci.nsIBits.ERROR_TYPE_USE_AFTER_REQUEST_SHUTDOWN, + Ci.nsIBits.ERROR_ACTION_CANCEL, + Ci.nsIBits.ERROR_STAGE_PRETASK, + Ci.nsIBits.ERROR_CODE_TYPE_NONE + ); + } + if (status === undefined) { + status = Cr.NS_ERROR_ABORT; + } + let action = lazy.gBits.ERROR_ACTION_CANCEL; + return requestPromise(action, callback => { + this._request.cancelAsync(status, callback); + }).then(() => this.shutdown()); + } + + /** + * This function wraps nsIBitsRequest::setPriorityHigh. + * + * Instead of taking a callback, the function is asynchronous. + * This method either resolves with no data, or rejects with a BitsError. + */ + async setPriorityHigh() { + if (!this._request) { + throw new BitsError( + Ci.nsIBits.ERROR_TYPE_USE_AFTER_REQUEST_SHUTDOWN, + Ci.nsIBits.ERROR_ACTION_SET_PRIORITY, + Ci.nsIBits.ERROR_STAGE_PRETASK, + Ci.nsIBits.ERROR_CODE_TYPE_NONE + ); + } + let action = lazy.gBits.ERROR_ACTION_SET_PRIORITY; + return requestPromise(action, callback => { + this._request.setPriorityHigh(callback); + }); + } + + /** + * This function wraps nsIBitsRequest::setPriorityLow. + * + * Instead of taking a callback, the function is asynchronous. + * This method either resolves with no data, or rejects with a BitsError. + */ + async setPriorityLow() { + if (!this._request) { + throw new BitsError( + Ci.nsIBits.ERROR_TYPE_USE_AFTER_REQUEST_SHUTDOWN, + Ci.nsIBits.ERROR_ACTION_SET_PRIORITY, + Ci.nsIBits.ERROR_STAGE_PRETASK, + Ci.nsIBits.ERROR_CODE_TYPE_NONE + ); + } + let action = lazy.gBits.ERROR_ACTION_SET_PRIORITY; + return requestPromise(action, callback => { + this._request.setPriorityLow(callback); + }); + } + + /** + * This function wraps nsIBitsRequest::setNoProgressTimeout. + * + * Instead of taking a callback, the function is asynchronous. + * This method either resolves with no data, or rejects with a BitsError. + */ + async setNoProgressTimeout(timeoutSecs) { + if (!this._request) { + throw new BitsError( + Ci.nsIBits.ERROR_TYPE_USE_AFTER_REQUEST_SHUTDOWN, + Ci.nsIBits.ERROR_ACTION_SET_NO_PROGRESS_TIMEOUT, + Ci.nsIBits.ERROR_STAGE_PRETASK, + Ci.nsIBits.ERROR_CODE_TYPE_NONE + ); + } + let action = lazy.gBits.ERROR_ACTION_SET_NO_PROGRESS_TIMEOUT; + return requestPromise(action, callback => { + this._request.setNoProgressTimeout(timeoutSecs, callback); + }); + } + + /** + * This function wraps nsIBitsRequest::complete. + * + * Instead of taking a callback, the function is asynchronous. + * This method either resolves with no data, or rejects with a BitsError. + */ + async complete() { + if (!this._request) { + throw new BitsError( + Ci.nsIBits.ERROR_TYPE_USE_AFTER_REQUEST_SHUTDOWN, + Ci.nsIBits.ERROR_ACTION_COMPLETE, + Ci.nsIBits.ERROR_STAGE_PRETASK, + Ci.nsIBits.ERROR_CODE_TYPE_NONE + ); + } + let action = lazy.gBits.ERROR_ACTION_COMPLETE; + return requestPromise(action, callback => { + this._request.complete(callback); + }).then(() => this.shutdown()); + } + + /** + * This function wraps nsIBitsRequest::suspendAsync. + * + * Instead of taking a callback, the function is asynchronous. + * This method either resolves with no data, or rejects with a BitsError. + */ + async suspendAsync() { + if (!this._request) { + throw new BitsError( + Ci.nsIBits.ERROR_TYPE_USE_AFTER_REQUEST_SHUTDOWN, + Ci.nsIBits.ERROR_ACTION_SUSPEND, + Ci.nsIBits.ERROR_STAGE_PRETASK, + Ci.nsIBits.ERROR_CODE_TYPE_NONE + ); + } + let action = lazy.gBits.ERROR_ACTION_SUSPEND; + return requestPromise(action, callback => { + this._request.suspendAsync(callback); + }); + } + + /** + * This function wraps nsIBitsRequest::resumeAsync. + * + * Instead of taking a callback, the function is asynchronous. + * This method either resolves with no data, or rejects with a BitsError. + */ + async resumeAsync() { + if (!this._request) { + throw new BitsError( + Ci.nsIBits.ERROR_TYPE_USE_AFTER_REQUEST_SHUTDOWN, + Ci.nsIBits.ERROR_ACTION_RESUME, + Ci.nsIBits.ERROR_STAGE_PRETASK, + Ci.nsIBits.ERROR_CODE_TYPE_NONE + ); + } + let action = lazy.gBits.ERROR_ACTION_RESUME; + return requestPromise(action, callback => { + this._request.resumeAsync(callback); + }); + } +} + +BitsRequest.prototype.QueryInterface = ChromeUtils.generateQI(["nsIRequest"]); + +/** + * This function does all of the wrapping and error handling for an async + * Bits Service method. This allows the implementations for those methods to + * simply call this function with a closure that executes appropriate + * nsIBits method. + * + * Specifically, this function takes an nsBitsErrorAction, an observer and a + * function. + * The nsBitsErrorAction will be used when constructing a BitsError, if the + * wrapper encounters an error. + * The observer should be the one that the caller passed to the Bits Interface + * method. It will be wrapped so that its methods are passed a BitsRequest + * rather than an nsIBitsRequest. + * The function will be passed the callback function and the wrapped observer, + * both of which should be passed to the nsIBitsRequest method. + */ +async function servicePromise(errorAction, observer, actionFn) { + return new Promise((resolve, reject) => { + if (!observer) { + let error = new BitsError( + lazy.gBits.ERROR_TYPE_NULL_ARGUMENT, + errorAction, + lazy.gBits.ERROR_STAGE_PRETASK, + lazy.gBits.ERROR_CODE_TYPE_NONE + ); + reject(error); + return; + } + try { + observer.QueryInterface(Ci.nsIRequestObserver); + } catch (e) { + let error = new BitsError( + lazy.gBits.ERROR_TYPE_INVALID_ARGUMENT, + errorAction, + lazy.gBits.ERROR_STAGE_PRETASK, + lazy.gBits.ERROR_CODE_TYPE_EXCEPTION, + e + ); + reject(error); + return; + } + let isProgressEventSink = false; + try { + observer.QueryInterface(Ci.nsIProgressEventSink); + isProgressEventSink = true; + } catch (e) {} + + // Check if we are not late in creating new requests. + if ( + Services && + Services.startup && + Services.startup.isInOrBeyondShutdownPhase( + Ci.nsIAppStartup.SHUTDOWN_PHASE_APPSHUTDOWNCONFIRMED + ) + ) { + let error = new BitsError( + lazy.gBits.ERROR_TYPE_BROWSER_SHUTTING_DOWN, + errorAction, + lazy.gBits.ERROR_STAGE_PRETASK, + lazy.gBits.ERROR_CODE_TYPE_NONE + ); + reject(error); + return; + } + + // This will be set to the BitsRequest (wrapping the nsIBitsRequest), once + // it is available. This prevents a new wrapper from having to be made every + // time an observer function is called. + let wrappedRequest; + + let wrappedObserver = { + onStartRequest: function wrappedObserver_onStartRequest(request) { + if (!wrappedRequest) { + wrappedRequest = new BitsRequest(request); + } + observer.onStartRequest(wrappedRequest); + }, + onStopRequest: function wrappedObserver_onStopRequest(request, status) { + if (!wrappedRequest) { + wrappedRequest = new BitsRequest(request); + } + observer.onStopRequest(wrappedRequest, status); + }, + onProgress: function wrappedObserver_onProgress( + request, + progress, + progressMax + ) { + if (isProgressEventSink) { + if (!wrappedRequest) { + wrappedRequest = new BitsRequest(request); + } + observer.onProgress(wrappedRequest, progress, progressMax); + } + }, + onStatus: function wrappedObserver_onStatus(request, status, statusArg) { + if (isProgressEventSink) { + if (!wrappedRequest) { + wrappedRequest = new BitsRequest(request); + } + observer.onStatus(wrappedRequest, status, statusArg); + } + }, + QueryInterface: ChromeUtils.generateQI([ + "nsIRequestObserver", + "nsIProgressEventSink", + ]), + }; + + let timer = makeTimeout(reject, errorAction); + let callback = { + QueryInterface: ChromeUtils.generateQI(["nsIBitsNewRequestCallback"]), + success(request) { + timer.cancel(); + if (!wrappedRequest) { + wrappedRequest = new BitsRequest(request); + } + resolve(wrappedRequest); + }, + failure(type, action, stage) { + timer.cancel(); + let error = new BitsError( + type, + action, + stage, + lazy.gBits.ERROR_CODE_TYPE_NONE + ); + reject(error); + }, + failureNsresult(type, action, stage, code) { + timer.cancel(); + let error = new BitsError( + type, + action, + stage, + lazy.gBits.ERROR_CODE_TYPE_NSRESULT, + code + ); + reject(error); + }, + failureHresult(type, action, stage, code) { + timer.cancel(); + let error = new BitsError( + type, + action, + stage, + lazy.gBits.ERROR_CODE_TYPE_HRESULT, + code + ); + reject(error); + }, + failureString(type, action, stage, message) { + timer.cancel(); + let error = new BitsError( + type, + action, + stage, + lazy.gBits.ERROR_CODE_TYPE_STRING, + message + ); + reject(error); + }, + }; + + try { + actionFn(wrappedObserver, callback); + } catch (e) { + let error = new BitsError( + lazy.gBits.ERROR_TYPE_METHOD_THREW, + errorAction, + lazy.gBits.ERROR_STAGE_PRETASK, + lazy.gBits.ERROR_CODE_TYPE_EXCEPTION, + e + ); + reject(error); + } + }); +} + +export var Bits = { + /** + * This function wraps nsIBits::initialized. + */ + get initialized() { + return lazy.gBits.initialized; + }, + + /** + * This function wraps nsIBits::init. + */ + init(jobName, savePathPrefix, monitorTimeoutMs) { + return lazy.gBits.init(jobName, savePathPrefix, monitorTimeoutMs); + }, + + /** + * This function wraps nsIBits::startDownload. + * + * Instead of taking a callback, the function is asynchronous. + * This method either resolves with a BitsRequest (which is also an + * nsIRequest), or rejects with a BitsError. + */ + async startDownload( + downloadURL, + saveRelPath, + proxy, + noProgressTimeoutSecs, + monitorIntervalMs, + observer, + context + ) { + let action = lazy.gBits.ERROR_ACTION_START_DOWNLOAD; + return servicePromise(action, observer, (wrappedObserver, callback) => { + lazy.gBits.startDownload( + downloadURL, + saveRelPath, + proxy, + noProgressTimeoutSecs, + monitorIntervalMs, + wrappedObserver, + context, + callback + ); + }); + }, + + /** + * This function wraps nsIBits::monitorDownload. + * + * Instead of taking a callback, the function is asynchronous. + * This method either resolves with a BitsRequest (which is also an + * nsIRequest), or rejects with a BitsError. + */ + async monitorDownload(id, monitorIntervalMs, observer, context) { + let action = lazy.gBits.ERROR_ACTION_MONITOR_DOWNLOAD; + return servicePromise(action, observer, (wrappedObserver, callback) => { + lazy.gBits.monitorDownload( + id, + monitorIntervalMs, + wrappedObserver, + context, + callback + ); + }); + }, +}; |