summaryrefslogtreecommitdiffstats
path: root/toolkit/components/bitsdownload/Bits.sys.mjs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 19:33:14 +0000
commit36d22d82aa202bb199967e9512281e9a53db42c9 (patch)
tree105e8c98ddea1c1e4784a60a5a6410fa416be2de /toolkit/components/bitsdownload/Bits.sys.mjs
parentInitial commit. (diff)
downloadfirefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz
firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip
Adding upstream version 115.7.0esr.upstream/115.7.0esr
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/bitsdownload/Bits.sys.mjs')
-rw-r--r--toolkit/components/bitsdownload/Bits.sys.mjs826
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
+ );
+ });
+ },
+};