зеркало из https://github.com/mozilla/gecko-dev.git
803 строки
23 KiB
JavaScript
803 строки
23 KiB
JavaScript
/* 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.
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
const { AppConstants } = ChromeUtils.import(
|
|
"resource://gre/modules/AppConstants.jsm"
|
|
);
|
|
const { XPCOMUtils } = ChromeUtils.import(
|
|
"resource://gre/modules/XPCOMUtils.jsm"
|
|
);
|
|
|
|
// 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.platform == "win") {
|
|
XPCOMUtils.defineLazyServiceGetter(
|
|
this,
|
|
"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.
|
|
*/
|
|
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 gBits.ERROR_CODE_TYPE_NONE:
|
|
code = null;
|
|
message += ", codeType: none}";
|
|
break;
|
|
case gBits.ERROR_CODE_TYPE_NSRESULT:
|
|
message += `, codeType: nsresult, code: ${code}}`;
|
|
break;
|
|
case gBits.ERROR_CODE_TYPE_HRESULT:
|
|
message += `, codeType: hresult, code: ${code}}`;
|
|
break;
|
|
case gBits.ERROR_CODE_TYPE_STRING:
|
|
message += `, codeType: string, code: ${JSON.stringify(code)}}`;
|
|
break;
|
|
case 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.
|
|
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
|
|
);
|
|
}
|
|
}
|
|
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(
|
|
gBits.ERROR_TYPE_METHOD_TIMEOUT,
|
|
errorAction,
|
|
gBits.ERROR_STAGE_UNKNOWN,
|
|
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([Ci.nsIBitsCallback]),
|
|
success() {
|
|
timer.cancel();
|
|
resolve();
|
|
},
|
|
failure(type, action, stage) {
|
|
timer.cancel();
|
|
let error = new BitsError(
|
|
type,
|
|
action,
|
|
stage,
|
|
gBits.ERROR_CODE_TYPE_NONE
|
|
);
|
|
reject(error);
|
|
},
|
|
failureNsresult(type, action, stage, code) {
|
|
timer.cancel();
|
|
let error = new BitsError(
|
|
type,
|
|
action,
|
|
stage,
|
|
gBits.ERROR_CODE_TYPE_NSRESULT,
|
|
code
|
|
);
|
|
reject(error);
|
|
},
|
|
failureHresult(type, action, stage, code) {
|
|
timer.cancel();
|
|
let error = new BitsError(
|
|
type,
|
|
action,
|
|
stage,
|
|
gBits.ERROR_CODE_TYPE_HRESULT,
|
|
code
|
|
);
|
|
reject(error);
|
|
},
|
|
failureString(type, action, stage, message) {
|
|
timer.cancel();
|
|
let error = new BitsError(
|
|
type,
|
|
action,
|
|
stage,
|
|
gBits.ERROR_CODE_TYPE_STRING,
|
|
message
|
|
);
|
|
reject(error);
|
|
},
|
|
};
|
|
|
|
try {
|
|
actionFn(callback);
|
|
} catch (e) {
|
|
let error = new BitsError(
|
|
gBits.ERROR_TYPE_METHOD_THREW,
|
|
errorAction,
|
|
gBits.ERROR_STAGE_PRETASK,
|
|
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.
|
|
*/
|
|
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 = 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 = 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 = 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 = gBits.ERROR_ACTION_SET_PRIORITY;
|
|
return requestPromise(action, callback => {
|
|
this._request.setPriorityLow(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 = 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 = 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 = gBits.ERROR_ACTION_RESUME;
|
|
return requestPromise(action, callback => {
|
|
this._request.resumeAsync(callback);
|
|
});
|
|
}
|
|
}
|
|
BitsRequest.prototype.QueryInterface = ChromeUtils.generateQI([Ci.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(
|
|
gBits.ERROR_TYPE_NULL_ARGUMENT,
|
|
errorAction,
|
|
gBits.ERROR_STAGE_PRETASK,
|
|
gBits.ERROR_CODE_TYPE_NONE
|
|
);
|
|
reject(error);
|
|
return;
|
|
}
|
|
try {
|
|
observer.QueryInterface(Ci.nsIRequestObserver);
|
|
} catch (e) {
|
|
let error = new BitsError(
|
|
gBits.ERROR_TYPE_INVALID_ARGUMENT,
|
|
errorAction,
|
|
gBits.ERROR_STAGE_PRETASK,
|
|
gBits.ERROR_CODE_TYPE_EXCEPTION,
|
|
e
|
|
);
|
|
reject(error);
|
|
return;
|
|
}
|
|
let isProgressEventSink = false;
|
|
try {
|
|
observer.QueryInterface(Ci.nsIProgressEventSink);
|
|
isProgressEventSink = true;
|
|
} catch (e) {}
|
|
|
|
// 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,
|
|
context,
|
|
progress,
|
|
progressMax
|
|
) {
|
|
if (isProgressEventSink) {
|
|
if (!wrappedRequest) {
|
|
wrappedRequest = new BitsRequest(request);
|
|
}
|
|
observer.onProgress(wrappedRequest, context, progress, progressMax);
|
|
}
|
|
},
|
|
onStatus: function wrappedObserver_onStatus(
|
|
request,
|
|
context,
|
|
status,
|
|
statusArg
|
|
) {
|
|
if (isProgressEventSink) {
|
|
if (!wrappedRequest) {
|
|
wrappedRequest = new BitsRequest(request);
|
|
}
|
|
observer.onStatus(wrappedRequest, context, status, statusArg);
|
|
}
|
|
},
|
|
QueryInterface: ChromeUtils.generateQI([
|
|
Ci.nsIRequestObserver,
|
|
Ci.nsIProgressEventSink,
|
|
]),
|
|
};
|
|
|
|
let timer = makeTimeout(reject, errorAction);
|
|
let callback = {
|
|
QueryInterface: ChromeUtils.generateQI([Ci.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,
|
|
gBits.ERROR_CODE_TYPE_NONE
|
|
);
|
|
reject(error);
|
|
},
|
|
failureNsresult(type, action, stage, code) {
|
|
timer.cancel();
|
|
let error = new BitsError(
|
|
type,
|
|
action,
|
|
stage,
|
|
gBits.ERROR_CODE_TYPE_NSRESULT,
|
|
code
|
|
);
|
|
reject(error);
|
|
},
|
|
failureHresult(type, action, stage, code) {
|
|
timer.cancel();
|
|
let error = new BitsError(
|
|
type,
|
|
action,
|
|
stage,
|
|
gBits.ERROR_CODE_TYPE_HRESULT,
|
|
code
|
|
);
|
|
reject(error);
|
|
},
|
|
failureString(type, action, stage, message) {
|
|
timer.cancel();
|
|
let error = new BitsError(
|
|
type,
|
|
action,
|
|
stage,
|
|
gBits.ERROR_CODE_TYPE_STRING,
|
|
message
|
|
);
|
|
reject(error);
|
|
},
|
|
};
|
|
|
|
try {
|
|
actionFn(wrappedObserver, callback);
|
|
} catch (e) {
|
|
let error = new BitsError(
|
|
gBits.ERROR_TYPE_METHOD_THREW,
|
|
errorAction,
|
|
gBits.ERROR_STAGE_PRETASK,
|
|
gBits.ERROR_CODE_TYPE_EXCEPTION,
|
|
e
|
|
);
|
|
reject(error);
|
|
}
|
|
});
|
|
}
|
|
|
|
var Bits = {
|
|
/**
|
|
* This function wraps nsIBits::initialized.
|
|
*/
|
|
get initialized() {
|
|
return gBits.initialized;
|
|
},
|
|
|
|
/**
|
|
* This function wraps nsIBits::init.
|
|
*/
|
|
init(jobName, savePathPrefix, monitorTimeoutMs) {
|
|
return 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,
|
|
monitorIntervalMs,
|
|
observer,
|
|
context
|
|
) {
|
|
let action = gBits.ERROR_ACTION_START_DOWNLOAD;
|
|
return servicePromise(action, observer, (wrappedObserver, callback) => {
|
|
gBits.startDownload(
|
|
downloadURL,
|
|
saveRelPath,
|
|
proxy,
|
|
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 = gBits.ERROR_ACTION_MONITOR_DOWNLOAD;
|
|
return servicePromise(action, observer, (wrappedObserver, callback) => {
|
|
gBits.monitorDownload(
|
|
id,
|
|
monitorIntervalMs,
|
|
wrappedObserver,
|
|
context,
|
|
callback
|
|
);
|
|
});
|
|
},
|
|
};
|
|
|
|
const EXPORTED_SYMBOLS = [
|
|
"Bits",
|
|
"BitsError",
|
|
"BitsRequest",
|
|
"BitsSuccess",
|
|
"BitsUnknownError",
|
|
"BitsVerificationError",
|
|
];
|