Bug 1520321 - Implement XPCOM interface for BITS r=lina
authorKirk Steuber <ksteuber@mozilla.com>
Mon, 15 Apr 2019 19:44:35 +0000
changeset 469554 a1cbd2f6e5bc
parent 469553 6f3f506c7740
child 469555 84cc6e41ba17
push id35874
push userccoroiu@mozilla.com
push dateTue, 16 Apr 2019 04:04:58 +0000
treeherdermozilla-central@be3f40425b52 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslina
bugs1520321, 1523417
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1520321 - Implement XPCOM interface for BITS r=lina This patch introduces an asynchronous XPCOM interface for the bits client added in Bug 1523417 Differential Revision: https://phabricator.services.mozilla.com/D25161
Cargo.lock
toolkit/components/bitsdownload/Bits.cpp
toolkit/components/bitsdownload/Bits.h
toolkit/components/bitsdownload/Bits.jsm
toolkit/components/bitsdownload/Cargo.toml
toolkit/components/bitsdownload/components.conf
toolkit/components/bitsdownload/moz.build
toolkit/components/bitsdownload/nsIBits.idl
toolkit/components/bitsdownload/src/bits_interface/action.rs
toolkit/components/bitsdownload/src/bits_interface/dispatch_callback.rs
toolkit/components/bitsdownload/src/bits_interface/error.rs
toolkit/components/bitsdownload/src/bits_interface/mod.rs
toolkit/components/bitsdownload/src/bits_interface/monitor.rs
toolkit/components/bitsdownload/src/bits_interface/request.rs
toolkit/components/bitsdownload/src/bits_interface/string.rs
toolkit/components/bitsdownload/src/bits_interface/task/client.rs
toolkit/components/bitsdownload/src/bits_interface/task/from_threadbound.rs
toolkit/components/bitsdownload/src/bits_interface/task/mod.rs
toolkit/components/bitsdownload/src/bits_interface/task/request_task.rs
toolkit/components/bitsdownload/src/bits_interface/task/service_task.rs
toolkit/components/bitsdownload/src/bits_interface/xpcom_methods.rs
toolkit/components/bitsdownload/src/lib.rs
toolkit/components/moz.build
xpcom/build/Services.py
xpcom/rust/xpcom/src/refptr.rs
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -308,16 +308,26 @@ dependencies = [
  "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "bitsdownload"
 version = "0.1.0"
 dependencies = [
  "bits_client 0.1.0",
+ "comedy 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
+ "crossbeam-utils 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "failure 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "failure_derive 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)",
+ "libc 0.2.51 (registry+https://github.com/rust-lang/crates.io-index)",
+ "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
+ "moz_task 0.1.0",
+ "nserror 0.1.0",
+ "nsstring 0.1.0",
+ "xpcom 0.1.0",
 ]
 
 [[package]]
 name = "blake2-rfc"
 version = "0.2.18"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "arrayvec 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
new file mode 100644
--- /dev/null
+++ b/toolkit/components/bitsdownload/Bits.cpp
@@ -0,0 +1,34 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/StaticPtr.h"
+#include "nsCOMPtr.h"
+#include "Bits.h"
+
+// This anonymous namespace prevents outside C++ code from improperly accessing
+// these implementation details.
+namespace {
+extern "C" {
+// Implemented in Rust.
+void new_bits_service(nsIBits** result);
+}
+
+static mozilla::StaticRefPtr<nsIBits> sBitsService;
+}  // namespace
+
+already_AddRefed<nsIBits> GetBitsService() {
+  nsCOMPtr<nsIBits> bitsService;
+
+  if (sBitsService) {
+    bitsService = sBitsService;
+  } else {
+    new_bits_service(getter_AddRefs(bitsService));
+    sBitsService = bitsService;
+    mozilla::ClearOnShutdown(&sBitsService);
+  }
+
+  return bitsService.forget();
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/bitsdownload/Bits.h
@@ -0,0 +1,9 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+#include "nsIBits.h"
+
+already_AddRefed<nsIBits> GetBitsService();
new file mode 100644
--- /dev/null
+++ b/toolkit/components/bitsdownload/Bits.jsm
@@ -0,0 +1,543 @@
+/* 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.
+ */
+class BitsRequest {
+  constructor(request) {
+    this._request = request;
+    this._request.QueryInterface(Ci.nsIBitsRequest);
+  }
+
+  /**
+   * 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() {
+    return this._request.name;
+  }
+  isPending() {
+    return this._request.isPending();
+  }
+  get status() {
+    return this._request.status;
+  }
+  cancel(status) {
+    return this._request.cancel(status);
+  }
+  suspend() {
+    return this._request.suspend();
+  }
+  resume() {
+    return this._request.resume();
+  }
+  get loadGroup() {
+    return this._request.loadGroup;
+  }
+  set loadGroup(group) {
+    this._request.loadGroup = group;
+  }
+  get loadFlags() {
+    return this._request.loadFlags;
+  }
+  set loadFlags(flags) {
+    this._request.loadFlags = flags;
+  }
+
+  /**
+   * This function wraps nsIBitsRequest::bitsId.
+   */
+  get 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 = this._request.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) {
+    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 (status === undefined) {
+      status = Cr.NS_ERROR_ABORT;
+    }
+    let action = gBits.ERROR_ACTION_CANCEL;
+    return requestPromise(action, callback => {
+      this._request.cancelAsync(status, callback);
+    });
+  }
+
+  /**
+   * 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() {
+    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() {
+    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() {
+    let action = gBits.ERROR_ACTION_COMPLETE;
+    return requestPromise(action, callback => {
+      this._request.complete(callback);
+    });
+  }
+
+  /**
+   * 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() {
+    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() {
+    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",
+];
--- a/toolkit/components/bitsdownload/Cargo.toml
+++ b/toolkit/components/bitsdownload/Cargo.toml
@@ -1,8 +1,22 @@
 [package]
 name = "bitsdownload"
 version = "0.1.0"
-authors = ["nobody@mozilla.org"]
+authors = ["Kirk Steuber <bytesized@mozilla.com>"]
 license = "MPL-2.0"
 
 [dependencies]
 bits_client = { path = "./bits_client" }
+comedy = "0.1.0"
+crossbeam-utils = "0.6.3"
+failure_derive = "0.1.3"
+libc = "0.2"
+log = "0.4"
+moz_task = { path = "../../../xpcom/rust/moz_task" }
+nserror = { path = "../../../xpcom/rust/nserror" }
+nsstring = { path = "../../../xpcom/rust/nsstring" }
+xpcom = { path = "../../../xpcom/rust/xpcom" }
+
+[dependencies.failure]
+version = "0.1.3"
+features = ["derive"]
+default_features = false
new file mode 100644
--- /dev/null
+++ b/toolkit/components/bitsdownload/components.conf
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+Classes = []
+if buildconfig.substs['OS_ARCH'] == 'WINNT' and defined('MOZ_BITS_DOWNLOAD'):
+    Classes += [
+        {
+            'cid': '{495d6f3d-9748-4d30-8ce5-0290c0001edf}',
+            'contract_ids': ['@mozilla.org/bits;1'],
+            'singleton': True,
+            'constructor': 'GetBitsService',
+            'headers': ['Bits.h'],
+        },
+    ]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/bitsdownload/moz.build
@@ -0,0 +1,32 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+XPIDL_MODULE = 'Bits'
+
+XPIDL_SOURCES += [
+    'nsIBits.idl',
+]
+
+XPCOM_MANIFESTS += [
+    'components.conf',
+]
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+  EXPORTS += [
+    'Bits.h'
+  ]
+  UNIFIED_SOURCES += [
+    'Bits.cpp'
+  ]
+
+EXTRA_JS_MODULES += [
+    'Bits.jsm',
+]
+
+FINAL_LIBRARY = 'xul'
+
+with Files('**'):
+    BUG_COMPONENT = ('Toolkit', 'Application Update')
new file mode 100644
--- /dev/null
+++ b/toolkit/components/bitsdownload/nsIBits.idl
@@ -0,0 +1,406 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* 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/. */
+
+#include "nsIRequest.idl"
+
+interface nsIRequest;
+interface nsIRequestObserver;
+
+interface nsIBitsRequest;
+
+interface nsIBitsNewRequestCallback;
+interface nsIBitsCallback;
+
+typedef long nsProxyUsage;
+typedef long nsBitsErrorType;
+typedef long nsBitsErrorAction;
+typedef long nsBitsErrorStage;
+
+/**
+ * An interface for interacting with Windows Background Intelligent Transfer
+ * Service. This should only be used on Windows.
+ *
+ * It would be preferable for the functions in this interface to return
+ * Promises, but this interface is implemented in Rust, which does not yet have
+ * support for Promises. There is a JS wrapper around this class that should be
+ * preferred over using this interface directly, located in Bits.jsm.
+ *
+ * Methods of this class that take a nsIBitsNewRequestCallback do not return or
+ * throw errors. All errors will be reported through the callback. The only
+ * things that should cause methods to directly throw errors are null arguments.
+ */
+[scriptable, uuid(495d6f3d-9748-4d30-8ce5-0290c0001edf)]
+interface nsIBits : nsISupports
+{
+  /**
+   * nsBitsErrorType values
+   * The BITS interface returns many error codes. These are intended to help
+   * determine appropriate fallback actions and to report to telemetry.
+   */
+  const long ERROR_TYPE_SUCCESS                                           = 0;
+  const long ERROR_TYPE_UNKNOWN                                           = 1;
+  const long ERROR_TYPE_METHOD_THREW                                      = 2;
+  const long ERROR_TYPE_METHOD_TIMEOUT                                    = 3;
+  const long ERROR_TYPE_NULL_ARGUMENT                                     = 4;
+  const long ERROR_TYPE_INVALID_ARGUMENT                                  = 5;
+  const long ERROR_TYPE_NOT_INITIALIZED                                   = 6;
+  const long ERROR_TYPE_NO_UTF8_CONVERSION                                = 7;
+  const long ERROR_TYPE_INVALID_GUID                                      = 8;
+  const long ERROR_TYPE_PIPE_NOT_CONNECTED                                = 9;
+  const long ERROR_TYPE_PIPE_TIMEOUT                                      = 10;
+  const long ERROR_TYPE_PIPE_BAD_WRITE_COUNT                              = 11;
+  const long ERROR_TYPE_PIPE_API_ERROR                                    = 12;
+  const long ERROR_TYPE_FAILED_TO_CREATE_BITS_JOB                         = 13;
+  const long ERROR_TYPE_FAILED_TO_ADD_FILE_TO_JOB                         = 14;
+  const long ERROR_TYPE_FAILED_TO_APPLY_BITS_JOB_SETTINGS                 = 15;
+  const long ERROR_TYPE_FAILED_TO_RESUME_BITS_JOB                         = 16;
+  const long ERROR_TYPE_OTHER_BITS_ERROR                                  = 17;
+  const long ERROR_TYPE_OTHER_BITS_CLIENT_ERROR                           = 18;
+  const long ERROR_TYPE_BITS_JOB_NOT_FOUND                                = 19;
+  const long ERROR_TYPE_FAILED_TO_GET_BITS_JOB                            = 20;
+  const long ERROR_TYPE_FAILED_TO_SUSPEND_BITS_JOB                        = 21;
+  const long ERROR_TYPE_FAILED_TO_COMPLETE_BITS_JOB                       = 22;
+  const long ERROR_TYPE_PARTIALLY_COMPLETED_BITS_JOB                      = 23;
+  const long ERROR_TYPE_FAILED_TO_CANCEL_BITS_JOB                         = 24;
+  const long ERROR_TYPE_MISSING_RESULT_DATA                               = 25;
+  const long ERROR_TYPE_MISSING_CALLBACK                                  = 26;
+  const long ERROR_TYPE_CALLBACK_ON_WRONG_THREAD                          = 27;
+  const long ERROR_TYPE_MISSING_BITS_SERVICE                              = 28;
+  const long ERROR_TYPE_BITS_SERVICE_ON_WRONG_THREAD                      = 29;
+  const long ERROR_TYPE_MISSING_BITS_REQUEST                              = 30;
+  const long ERROR_TYPE_BITS_REQUEST_ON_WRONG_THREAD                      = 31;
+  const long ERROR_TYPE_MISSING_OBSERVER                                  = 32;
+  const long ERROR_TYPE_OBSERVER_ON_WRONG_THREAD                          = 33;
+  const long ERROR_TYPE_MISSING_CONTEXT                                   = 34;
+  const long ERROR_TYPE_CONTEXT_ON_WRONG_THREAD                           = 35;
+  const long ERROR_TYPE_FAILED_TO_START_THREAD                            = 36;
+  const long ERROR_TYPE_FAILED_TO_CONSTRUCT_TASK_RUNNABLE                 = 37;
+  const long ERROR_TYPE_FAILED_TO_DISPATCH_RUNNABLE                       = 38;
+  const long ERROR_TYPE_TRANSFER_ALREADY_COMPLETE                         = 39;
+  const long ERROR_TYPE_OPERATION_ALREADY_IN_PROGRESS                     = 40;
+  const long ERROR_TYPE_MISSING_BITS_CLIENT                               = 41;
+  const long ERROR_TYPE_FAILED_TO_GET_JOB_STATUS                          = 42;
+  const long ERROR_TYPE_BITS_STATE_ERROR                                  = 43;
+  const long ERROR_TYPE_BITS_STATE_TRANSIENT_ERROR                        = 44;
+  const long ERROR_TYPE_BITS_STATE_CANCELLED                              = 45;
+  const long ERROR_TYPE_BITS_STATE_UNEXPECTED                             = 46;
+  const long ERROR_TYPE_VERIFICATION_FAILURE                              = 47;
+  const long ERROR_TYPE_ACCESS_DENIED_EXPECTED                            = 48;
+
+  /**
+   * nsBitsErrorAction values
+   * These values indicate where the error occurred.
+   */
+  const long ERROR_ACTION_UNKNOWN                                         = 1;
+  const long ERROR_ACTION_NONE                                            = 2;
+  const long ERROR_ACTION_START_DOWNLOAD                                  = 3;
+  const long ERROR_ACTION_MONITOR_DOWNLOAD                                = 4;
+  const long ERROR_ACTION_CHANGE_MONITOR_INTERVAL                         = 5;
+  const long ERROR_ACTION_CANCEL                                          = 6;
+  const long ERROR_ACTION_SET_PRIORITY                                    = 7;
+  const long ERROR_ACTION_COMPLETE                                        = 8;
+  const long ERROR_ACTION_SUSPEND                                         = 9;
+  const long ERROR_ACTION_RESUME                                          = 10;
+
+  /**
+   * nsBitsErrorStage values
+   * These values allow the caller to determine at what point in the download
+   * mechanism a failure occurred.
+   */
+  const long ERROR_STAGE_UNKNOWN                                          = 1;
+  const long ERROR_STAGE_PRETASK                                          = 2;
+  const long ERROR_STAGE_COMMAND_THREAD                                   = 3;
+  const long ERROR_STAGE_AGENT_COMMUNICATION                              = 4;
+  const long ERROR_STAGE_BITS_CLIENT                                      = 5;
+  const long ERROR_STAGE_MAIN_THREAD                                      = 6;
+  const long ERROR_STAGE_MONITOR                                          = 7;
+  const long ERROR_STAGE_VERIFICATION                                     = 8;
+
+  /**
+   * These values indicate what type of error code was returned. These are used
+   * to allow the different types taken by the different callback failure
+   * functions to be made into one generic error type in Javascript.
+   */
+  const long ERROR_CODE_TYPE_NONE                                         = 1;
+  const long ERROR_CODE_TYPE_NSRESULT                                     = 2;
+  const long ERROR_CODE_TYPE_HRESULT                                      = 3;
+  const long ERROR_CODE_TYPE_STRING                                       = 4;
+  const long ERROR_CODE_TYPE_EXCEPTION                                    = 5;
+
+  /**
+   * Indicates whether init() has been called.
+   */
+  readonly attribute boolean initialized;
+
+  /**
+   * Initializes the BITS interface. Unlike other functions here, this happens
+   * synchronously.
+   * init() should only be called only once.
+   *
+   * @param jobName
+   *        The name of the BITS job. This is used both to set the name during
+   *        job creation and to verify that a job is ours.
+   * @param savePathPrefix
+   *        The directory that downloads will be saved to. Providing a safe
+   *        directory here ensures that the download path cannot be manipulated
+   *        to save files to a malicious location. Downloads are guaranteed to
+   *        be saved to this directory or a subdirectory.
+   * @param monitorTimeoutMs
+   *        The amount of time to wait between download monitor notifications.
+   *        This should be larger than the largest monitorIntervalMs that will
+   *        be passed to startDownload(), monitorDownload(), or
+   *        changeMonitorInterval(). This value may not be 0.
+   */
+  void init(in AUTF8String jobName,
+            in AUTF8String savePathPrefix,
+            in unsigned long monitorTimeoutMs);
+
+  /**
+   * Downloads the specified URL to the specified location within the
+   * savePathPrefix passed to init().
+   *
+   * @param downloadURL
+   *        The URL to be downloaded.
+   * @param saveRelativePath
+   *        The location to download to. The path given should be a path
+   *        relative to the savePathPrefix passed to init(). If this attempts to
+   *        escape the directory specified by savePathPrefix, this call will
+   *        fail (ex: Don't pass "../filename").
+   * @param proxy
+   *        Specifies what proxy to use when downloading. Valid values are
+   *        listed below.
+   * @param monitorIntervalMs
+   *        The number of milliseconds between download status notifications.
+   * @param observer
+   *        An observer to be notified of various events. OnStartRequest is
+   *        called once the BITS job has been created. OnStopRequest is called
+   *        when the file transfer has completed or when an error occurs. If
+   *        this object implements nsIProgressEventSink, then its OnProgress
+   *        method will be called as data is transferred.
+   *        IMPORTANT NOTE: When OnStopRequest is called, the download has
+   *                        completed, but nsIBitsRequest::complete() still
+   *                        needs to be called to save the file to the
+   *                        filesystem.
+   * @param context
+   *        User defined object forwarded to the observer's onProgress method.
+   *        This parameter, unlike others for this interface, can be passed a
+   *        null pointer.
+   * @param callback
+   *        The callback used to relay the response from BITS.
+   */
+  void startDownload(in AUTF8String downloadURL,
+                     in AUTF8String saveRelativePath,
+                     in nsProxyUsage proxy,
+                     in unsigned long monitorIntervalMs,
+                     in nsIRequestObserver observer,
+                     in nsISupports context,
+                     in nsIBitsNewRequestCallback callback);
+
+  // nsProxyUsage values
+  const long PROXY_NONE = 1;
+  const long PROXY_PRECONFIG = 2;
+  const long PROXY_AUTODETECT = 3;
+
+  /**
+   * Similar to startDownload, but connects to a BITS transfer that has already
+   * been started.
+   *
+   * @param id
+   *        The GUID of the download to monitor.
+   * @param monitorIntervalMs
+   *        The number of milliseconds between download status notifications.
+   * @param observer
+   *        An observer to be notified of various events. OnStartRequest is
+   *        called once the BITS job has been created. OnStopRequest is called
+   *        when the file transfer has completed or when an error occurs. If
+   *        this object implements nsIProgressEventSink, then its OnProgress
+   *        method will be called as data is transferred.
+   *        IMPORTANT NOTE: When OnStopRequest is called, the download has
+   *                        completed, but nsIBitsRequest::complete() still
+   *                        needs to be called to save the file to the
+   *                        filesystem.
+   * @param context
+   *        User defined object forwarded to the observer's onProgress method.
+   *        This parameter, unlike others for this interface, can be passed a
+   *        null pointer.
+   * @param callback
+   *        The callback used to relay the response from BITS.
+   */
+  void monitorDownload(in AUTF8String id,
+                       in unsigned long monitorIntervalMs,
+                       in nsIRequestObserver observer,
+                       in nsISupports context,
+                       in nsIBitsNewRequestCallback callback);
+};
+
+/**
+ * This callback interface is for use by the nsIBits interface for returning
+ * results asynchronously to the caller.
+ */
+[scriptable, uuid(aa12e433-5b9f-452d-b5c9-840a9541328b)]
+interface nsIBitsNewRequestCallback : nsISupports
+{
+  void success(in nsIBitsRequest request);
+  void failure(in nsBitsErrorType errorType,
+               in nsBitsErrorAction errorAction,
+               in nsBitsErrorStage errorStage);
+  void failureNsresult(in nsBitsErrorType errorType,
+                       in nsBitsErrorAction errorAction,
+                       in nsBitsErrorStage errorStage,
+                       in nsresult errorCode);
+  void failureHresult(in nsBitsErrorType errorType,
+                      in nsBitsErrorAction errorAction,
+                      in nsBitsErrorStage errorStage,
+                      in long errorCode);
+  void failureString(in nsBitsErrorType errorType,
+                     in nsBitsErrorAction errorAction,
+                     in nsBitsErrorStage errorStage,
+                     in AUTF8String errorMessage);
+};
+
+/*
+ * An interface for managing a running BITS download.
+ *
+ * It would be preferable for the functions in this interface to return
+ * Promises, but this interface is implemented in Rust, which does not yet have
+ * support for Promises. There is a JS wrapper around this class that should be
+ * preferred over using this interface directly, located in Bits.jsm.
+ *
+ * Methods of this class that take a nsIBitsCallback do not return or throw
+ * errors. All errors will be reported through the callback. The only
+ * things that should cause methods to directly throw errors are null arguments.
+ *
+ * Note: Although the nsIBits interface derives from nsIRequest, implementations
+ *       may not implement the loadGroup or loadFlags attributes.
+ *
+ * Note: Once the file transfer has stopped (due to completion or error),
+ *       calling any method besides complete() or cancel() will result in an
+ *       error with type nsIBits::ERROR_TYPE_TRANSFER_ALREADY_COMPLETE.
+ *       Calling complete() or cancel() again after either has already been
+ *       called will also result in an ERROR_TYPE_TRANSFER_ALREADY_COMPLETE
+ *       error.
+ *       Attributes and nsIRequest::isPending() can still be accessed at any
+ *       time.
+ */
+[scriptable, uuid(ab9da0e9-06bf-4e73-bb1b-c0f2ea9ecc3e)]
+interface nsIBitsRequest : nsIRequest
+{
+  /**
+   * The BITS id of the download. This will be a string representing a UUID.
+   */
+  readonly attribute AUTF8String bitsId;
+
+  /**
+   * The transfer result of the download, meant to be accessed after the
+   * transfer has stopped (i.e. after the observer's onStopRequest method has
+   * been called). Will be nsIBits::ERROR_TYPE_SUCCESS if the transfer is
+   * successful (and before transfer completion). If the transfer failed, this
+   * will be a different nsBitsErrorType value indicating the cause of the
+   * failure.
+   */
+  readonly attribute nsBitsErrorType transferError;
+
+  /**
+   * Requests a change to the frequency that Firefox is receiving download
+   * status notifications.
+   *
+   * @param monitorIntervalMs
+   *        The new number of milliseconds between download status
+   *        notifications.
+   * @param callback
+   *        The callback function used to relay success or failure.
+   */
+  void changeMonitorInterval(in unsigned long monitorIntervalMs,
+                             in nsIBitsCallback callback);
+
+  /**
+   * Cancels the download. This function is named this way to avoid conflict
+   * with nsIRequest::cancel.
+   *
+   * @param status
+   *        The reason for cancelling the request. This must be a failure code
+   *        rather than a success code like NS_OK.
+   * @param callback
+   *        The callback function used to relay success or failure.
+   */
+  void cancelAsync(in nsresult status,
+                   in nsIBitsCallback callback);
+
+  /**
+   * Sets the priority of the BITS job to high (i.e. foreground download).
+   *
+   * @param callback
+   *        The callback function used to relay success or failure.
+   */
+  void setPriorityHigh(in nsIBitsCallback callback);
+
+  /**
+   * Sets the priority of the BITS job to low (i.e. background download).
+   *
+   * @param callback
+   *        The callback function used to relay success or failure.
+   */
+  void setPriorityLow(in nsIBitsCallback callback);
+
+  /*
+   * Completes the download, moving it out of the BITS system and onto the
+   * disk location specified when startDownload was called.
+   *
+   * @param callback
+   *        The callback function used to relay success or failure.
+   */
+  void complete(in nsIBitsCallback callback);
+
+  /*
+   * Suspends the download, preventing more data from being transferred until
+   * the download is resumed. This function is named this way to avoid conflict
+   * with nsIRequest::suspend.
+   *
+   * @param callback
+   *        The callback function used to relay success or failure.
+   */
+  void suspendAsync(in nsIBitsCallback callback);
+
+  /*
+   * Resumes a previously suspended download. This function is named this way
+   * to avoid conflict with nsIRequest::resume.
+   *
+   * @param callback
+   *        The callback function used to relay success or failure.
+   */
+  void resumeAsync(in nsIBitsCallback callback);
+};
+
+/**
+ * This callback interface is for use by the nsIBitsRequest interface for
+ * returning results asynchronously to the caller.
+ */
+[scriptable, uuid(ea657e66-6bad-4e41-84d9-c6d107e9799d)]
+interface nsIBitsCallback : nsISupports
+{
+  void success();
+  void failure(in nsBitsErrorType errorType,
+               in nsBitsErrorAction errorAction,
+               in nsBitsErrorStage errorStage);
+  void failureNsresult(in nsBitsErrorType errorType,
+                       in nsBitsErrorAction errorAction,
+                       in nsBitsErrorStage errorStage,
+                       in nsresult errorCode);
+  void failureHresult(in nsBitsErrorType errorType,
+                      in nsBitsErrorAction errorAction,
+                      in nsBitsErrorStage errorStage,
+                      in long errorCode);
+  void failureString(in nsBitsErrorType errorType,
+                     in nsBitsErrorAction errorAction,
+                     in nsBitsErrorStage errorStage,
+                     in AUTF8String errorMessage);
+};
+
+%{C++
+#define NS_BITS_CID \
+  { 0xa334de05, 0xb9de, 0x46a1, \
+    { 0x98, 0xa9, 0x3f, 0x5c, 0xed, 0x82, 0x1e, 0x68 } }
+#define NS_BITS_CONTRACTID "@mozilla.org/bits;1"
+%}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/bitsdownload/src/bits_interface/action.rs
@@ -0,0 +1,114 @@
+/* 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/. */
+
+//! `Action` is an enum describing what BITS action is being processed. This is
+//! used mostly for logging and error reporting reasons.
+//! The values of `Action` describe actions that could be in progress for
+//! BitsService or BitsRequest. When specifying a type, `ServiceAction` or
+//! `RequestAction`, can be used to restrict the action type to one of the two
+//! categories.
+//! A value of type `ServiceAction` or `RequestAction` can easily be converted
+//! to an `Action` using the `into()` method.
+
+use std::convert::From;
+use xpcom::interfaces::nsIBits;
+
+#[derive(Debug, PartialEq, Eq, Clone, Copy)]
+pub enum Action {
+    StartDownload,
+    MonitorDownload,
+    Complete,
+    Cancel,
+    SetMonitorInterval,
+    SetPriority,
+    Resume,
+    Suspend,
+}
+
+impl Action {
+    pub fn description(&self) -> &'static str {
+        match self {
+            Action::StartDownload => "starting download",
+            Action::MonitorDownload => "monitoring download",
+            Action::Complete => "completing download",
+            Action::Cancel => "cancelling download",
+            Action::SetMonitorInterval => "changing monitor interval",
+            Action::SetPriority => "setting download priority",
+            Action::Resume => "resuming download",
+            Action::Suspend => "suspending download",
+        }
+    }
+
+    pub fn as_error_code(&self) -> i32 {
+        let val = match self {
+            Action::StartDownload => nsIBits::ERROR_ACTION_START_DOWNLOAD,
+            Action::MonitorDownload => nsIBits::ERROR_ACTION_MONITOR_DOWNLOAD,
+            Action::Complete => nsIBits::ERROR_ACTION_COMPLETE,
+            Action::Cancel => nsIBits::ERROR_ACTION_CANCEL,
+            Action::SetMonitorInterval => nsIBits::ERROR_ACTION_CHANGE_MONITOR_INTERVAL,
+            Action::SetPriority => nsIBits::ERROR_ACTION_SET_PRIORITY,
+            Action::Resume => nsIBits::ERROR_ACTION_RESUME,
+            Action::Suspend => nsIBits::ERROR_ACTION_SUSPEND,
+        };
+        val as i32
+    }
+}
+
+#[derive(Debug, PartialEq, Clone, Copy)]
+pub enum ServiceAction {
+    StartDownload,
+    MonitorDownload,
+}
+
+impl From<ServiceAction> for Action {
+    fn from(action: ServiceAction) -> Action {
+        match action {
+            ServiceAction::StartDownload => Action::StartDownload,
+            ServiceAction::MonitorDownload => Action::MonitorDownload,
+        }
+    }
+}
+
+impl ServiceAction {
+    pub fn as_error_code(&self) -> i32 {
+        Action::as_error_code(&(self.clone()).into())
+    }
+
+    pub fn description(&self) -> &'static str {
+        Action::description(&(self.clone()).into())
+    }
+}
+
+#[derive(Debug, PartialEq, Clone, Copy)]
+pub enum RequestAction {
+    Complete,
+    Cancel,
+    SetMonitorInterval,
+    SetPriority,
+    Resume,
+    Suspend,
+}
+
+impl From<RequestAction> for Action {
+    fn from(action: RequestAction) -> Action {
+        match action {
+            RequestAction::Complete => Action::Complete,
+            RequestAction::Cancel => Action::Cancel,
+            RequestAction::SetMonitorInterval => Action::SetMonitorInterval,
+            RequestAction::SetPriority => Action::SetPriority,
+            RequestAction::Resume => Action::Resume,
+            RequestAction::Suspend => Action::Suspend,
+        }
+    }
+}
+
+impl RequestAction {
+    pub fn as_error_code(&self) -> i32 {
+        Action::as_error_code(&(self.clone()).into())
+    }
+
+    pub fn description(&self) -> &'static str {
+        Action::description(&(self.clone()).into())
+    }
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/bitsdownload/src/bits_interface/dispatch_callback.rs
@@ -0,0 +1,199 @@
+/* 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 super::{
+    error::{BitsTaskError, ErrorCode, ErrorType},
+    BitsRequest,
+};
+use log::{error, info, warn};
+use nserror::{nsresult, NS_ERROR_FAILURE, NS_OK};
+use xpcom::{
+    interfaces::{nsIBitsCallback, nsIBitsNewRequestCallback},
+    RefPtr,
+};
+
+#[derive(Debug, PartialEq, Clone, Copy)]
+pub enum IsCallbackExpected {
+    CallbackExpected,
+    CallbackOptional,
+}
+pub use self::IsCallbackExpected::{CallbackExpected, CallbackOptional};
+
+// This is meant to be called at the end of a nsIBits Task. It attempts to
+// return the result via the callback given. If the callback is unavailable, a
+// log message will be printed indicating the results and (possibly) warning
+// than an expected callback was missing.
+pub fn maybe_dispatch_request_via_callback(
+    result: Result<RefPtr<BitsRequest>, BitsTaskError>,
+    maybe_callback: Result<&nsIBitsNewRequestCallback, BitsTaskError>,
+    expected: IsCallbackExpected,
+) -> Result<(), nsresult> {
+    if let Err(error) = maybe_callback.as_ref() {
+        if expected == CallbackExpected || error.error_type == ErrorType::CallbackOnWrongThread {
+            error!(
+                "Unexpected error when {} - No callback: {:?}",
+                error.error_action.description(),
+                error,
+            );
+        }
+    }
+    match result {
+        Ok(request) => match (maybe_callback, expected) {
+            (Ok(callback), _) => unsafe { callback.Success(request.coerce()) },
+            (Err(error), CallbackExpected) => {
+                error!(
+                    "Success {} but there is no callback to return the result with",
+                    error.error_action.description(),
+                );
+                NS_ERROR_FAILURE
+            }
+            (Err(error), CallbackOptional) => {
+                info!("Success {}", error.error_action.description());
+                NS_OK
+            }
+        },
+        Err(error) => match (maybe_callback, expected) {
+            (Ok(callback), _) => match error.error_code {
+                ErrorCode::None => unsafe {
+                    callback.Failure(
+                        error.error_type.bits_code(),
+                        error.error_action.as_error_code(),
+                        error.error_stage.bits_code(),
+                    )
+                },
+                ErrorCode::Hresult(error_code) => unsafe {
+                    callback.FailureHresult(
+                        error.error_type.bits_code(),
+                        error.error_action.as_error_code(),
+                        error.error_stage.bits_code(),
+                        error_code,
+                    )
+                },
+                ErrorCode::Nsresult(error_code) => unsafe {
+                    callback.FailureNsresult(
+                        error.error_type.bits_code(),
+                        error.error_action.as_error_code(),
+                        error.error_stage.bits_code(),
+                        error_code,
+                    )
+                },
+                ErrorCode::Message(message) => unsafe {
+                    callback.FailureString(
+                        error.error_type.bits_code(),
+                        error.error_action.as_error_code(),
+                        error.error_stage.bits_code(),
+                        &*message,
+                    )
+                },
+            },
+            (Err(_), CallbackExpected) => {
+                error!("Error {}: {:?}", error.error_action.description(), error);
+                NS_ERROR_FAILURE
+            }
+            (Err(_), CallbackOptional) => {
+                warn!("Error {}: {:?}", error.error_action.description(), error);
+                NS_ERROR_FAILURE
+            }
+        },
+    }
+    .to_result()
+}
+
+// Intended to be used by an nsIBits XPCOM wrapper to return errors that occur
+// before dispatching a task off-thread. No return value is returned because it
+// will represent the return value of the callback function, which should not be
+// propagated.
+pub fn dispatch_pretask_interface_error(
+    error: BitsTaskError,
+    callback: &nsIBitsNewRequestCallback,
+) {
+    let _ = maybe_dispatch_request_via_callback(Err(error), Ok(callback), CallbackExpected);
+}
+
+// This is meant to be called at the end of a nsIBitsRequest Task. It attempts
+// to return the result via the callback given. If the callback is unavailable,
+// a log message will be printed indicating the results and (possibly) warning
+// than an expected callback was missing.
+pub fn maybe_dispatch_via_callback(
+    result: Result<(), BitsTaskError>,
+    maybe_callback: Result<&nsIBitsCallback, BitsTaskError>,
+    expected: IsCallbackExpected,
+) -> Result<(), nsresult> {
+    if let Err(error) = maybe_callback.as_ref() {
+        if expected == CallbackExpected || error.error_type == ErrorType::CallbackOnWrongThread {
+            error!(
+                "Unexpected error when {} - No callback: {:?}",
+                error.error_action.description(),
+                error,
+            );
+        }
+    }
+    match result {
+        Ok(()) => match (maybe_callback, expected) {
+            (Ok(callback), _) => unsafe { callback.Success() },
+            (Err(error), CallbackExpected) => {
+                error!(
+                    "Success {} but there is no callback to return the result with",
+                    error.error_action.description(),
+                );
+                NS_ERROR_FAILURE
+            }
+            (Err(error), CallbackOptional) => {
+                info!("Success {}", error.error_action.description());
+                NS_OK
+            }
+        },
+        Err(error) => match (maybe_callback, expected) {
+            (Ok(callback), _) => match error.error_code {
+                ErrorCode::None => unsafe {
+                    callback.Failure(
+                        error.error_type.bits_code(),
+                        error.error_action.as_error_code(),
+                        error.error_stage.bits_code(),
+                    )
+                },
+                ErrorCode::Hresult(error_code) => unsafe {
+                    callback.FailureHresult(
+                        error.error_type.bits_code(),
+                        error.error_action.as_error_code(),
+                        error.error_stage.bits_code(),
+                        error_code,
+                    )
+                },
+                ErrorCode::Nsresult(error_code) => unsafe {
+                    callback.FailureNsresult(
+                        error.error_type.bits_code(),
+                        error.error_action.as_error_code(),
+                        error.error_stage.bits_code(),
+                        error_code,
+                    )
+                },
+                ErrorCode::Message(message) => unsafe {
+                    callback.FailureString(
+                        error.error_type.bits_code(),
+                        error.error_action.as_error_code(),
+                        error.error_stage.bits_code(),
+                        &*message,
+                    )
+                },
+            },
+            (Err(_), CallbackExpected) => {
+                error!("Error {}: {:?}", error.error_action.description(), error);
+                NS_ERROR_FAILURE
+            }
+            (Err(_), CallbackOptional) => {
+                warn!("Error {}: {:?}", error.error_action.description(), error);
+                NS_ERROR_FAILURE
+            }
+        },
+    }
+    .to_result()
+}
+
+// Intended to be used by an nsIBitsRequest XPCOM wrapper to return errors that
+// occur before dispatching a task off-thread. No return value is returned
+// because it will represent the return value of the callback function, which
+// should not be propagated.
+pub fn dispatch_pretask_request_error(error: BitsTaskError, callback: &nsIBitsCallback) {
+    let _ = maybe_dispatch_via_callback(Err(error), Ok(callback), CallbackExpected);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/bitsdownload/src/bits_interface/error.rs
@@ -0,0 +1,595 @@
+/* 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 super::action::Action;
+use bits_client::{
+    bits_protocol::{
+        CancelJobFailure, CompleteJobFailure, HResultMessage, MonitorJobFailure, ResumeJobFailure,
+        SetJobPriorityFailure, SetUpdateIntervalFailure, StartJobFailure, SuspendJobFailure,
+    },
+    PipeError,
+};
+use comedy::error::HResult as ComedyError;
+use nserror::{nsresult, NS_ERROR_FAILURE};
+use nsstring::nsCString;
+use std::convert::From;
+use xpcom::interfaces::nsIBits;
+
+#[derive(Debug, PartialEq, Clone, Copy)]
+pub enum ErrorType {
+    NullArgument,
+    InvalidArgument,
+    NotInitialized,
+    NoUtf8Conversion,
+    InvalidGuid,
+    PipeNotConnected,
+    PipeTimeout,
+    PipeBadWriteCount,
+    PipeApiError,
+    FailedToCreateBitsJob,
+    FailedToAddFileToJob,
+    FailedToApplyBitsJobSettings,
+    FailedToResumeBitsJob,
+    OtherBitsError,
+    OtherBitsClientError,
+    BitsJobNotFound,
+    FailedToGetBitsJob,
+    FailedToSuspendBitsJob,
+    FailedToCompleteBitsJob,
+    PartiallyCompletedBitsJob,
+    FailedToCancelBitsJob,
+    MissingResultData,
+    MissingCallback,
+    CallbackOnWrongThread,
+    MissingBitsService,
+    BitsServiceOnWrongThread,
+    MissingBitsRequest,
+    BitsRequestOnWrongThread,
+    MissingObserver,
+    ObserverOnWrongThread,
+    MissingContext,
+    ContextOnWrongThread,
+    FailedToStartThread,
+    FailedToConstructTaskRunnable,
+    FailedToDispatchRunnable,
+    TransferAlreadyComplete,
+    OperationAlreadyInProgress,
+    MissingBitsClient,
+    FailedToGetJobStatus,
+    BitsStateError,
+    BitsStateTransientError,
+    BitsStateCancelled,
+    BitsStateUnexpected,
+}
+
+impl ErrorType {
+    pub fn bits_code(&self) -> i32 {
+        let val = match self {
+            ErrorType::NullArgument => nsIBits::ERROR_TYPE_NULL_ARGUMENT,
+            ErrorType::InvalidArgument => nsIBits::ERROR_TYPE_INVALID_ARGUMENT,
+            ErrorType::NotInitialized => nsIBits::ERROR_TYPE_NOT_INITIALIZED,
+            ErrorType::NoUtf8Conversion => nsIBits::ERROR_TYPE_NO_UTF8_CONVERSION,
+            ErrorType::InvalidGuid => nsIBits::ERROR_TYPE_INVALID_GUID,
+            ErrorType::PipeNotConnected => nsIBits::ERROR_TYPE_PIPE_NOT_CONNECTED,
+            ErrorType::PipeTimeout => nsIBits::ERROR_TYPE_PIPE_TIMEOUT,
+            ErrorType::PipeBadWriteCount => nsIBits::ERROR_TYPE_PIPE_BAD_WRITE_COUNT,
+            ErrorType::PipeApiError => nsIBits::ERROR_TYPE_PIPE_API_ERROR,
+            ErrorType::FailedToCreateBitsJob => nsIBits::ERROR_TYPE_FAILED_TO_CREATE_BITS_JOB,
+            ErrorType::FailedToAddFileToJob => nsIBits::ERROR_TYPE_FAILED_TO_ADD_FILE_TO_JOB,
+            ErrorType::FailedToApplyBitsJobSettings => {
+                nsIBits::ERROR_TYPE_FAILED_TO_APPLY_BITS_JOB_SETTINGS
+            }
+            ErrorType::FailedToResumeBitsJob => nsIBits::ERROR_TYPE_FAILED_TO_RESUME_BITS_JOB,
+            ErrorType::OtherBitsError => nsIBits::ERROR_TYPE_OTHER_BITS_ERROR,
+            ErrorType::OtherBitsClientError => nsIBits::ERROR_TYPE_OTHER_BITS_CLIENT_ERROR,
+            ErrorType::BitsJobNotFound => nsIBits::ERROR_TYPE_BITS_JOB_NOT_FOUND,
+            ErrorType::FailedToGetBitsJob => nsIBits::ERROR_TYPE_FAILED_TO_GET_BITS_JOB,
+            ErrorType::FailedToSuspendBitsJob => nsIBits::ERROR_TYPE_FAILED_TO_SUSPEND_BITS_JOB,
+            ErrorType::FailedToCompleteBitsJob => nsIBits::ERROR_TYPE_FAILED_TO_COMPLETE_BITS_JOB,
+            ErrorType::PartiallyCompletedBitsJob => {
+                nsIBits::ERROR_TYPE_PARTIALLY_COMPLETED_BITS_JOB
+            }
+            ErrorType::FailedToCancelBitsJob => nsIBits::ERROR_TYPE_FAILED_TO_CANCEL_BITS_JOB,
+            ErrorType::MissingResultData => nsIBits::ERROR_TYPE_MISSING_RESULT_DATA,
+            ErrorType::MissingCallback => nsIBits::ERROR_TYPE_MISSING_CALLBACK,
+            ErrorType::CallbackOnWrongThread => nsIBits::ERROR_TYPE_CALLBACK_ON_WRONG_THREAD,
+            ErrorType::MissingBitsService => nsIBits::ERROR_TYPE_MISSING_BITS_SERVICE,
+            ErrorType::BitsServiceOnWrongThread => nsIBits::ERROR_TYPE_BITS_SERVICE_ON_WRONG_THREAD,
+            ErrorType::MissingBitsRequest => nsIBits::ERROR_TYPE_MISSING_BITS_REQUEST,
+            ErrorType::BitsRequestOnWrongThread => nsIBits::ERROR_TYPE_BITS_REQUEST_ON_WRONG_THREAD,
+            ErrorType::MissingObserver => nsIBits::ERROR_TYPE_MISSING_OBSERVER,
+            ErrorType::ObserverOnWrongThread => nsIBits::ERROR_TYPE_OBSERVER_ON_WRONG_THREAD,
+            ErrorType::MissingContext => nsIBits::ERROR_TYPE_MISSING_CONTEXT,
+            ErrorType::ContextOnWrongThread => nsIBits::ERROR_TYPE_CONTEXT_ON_WRONG_THREAD,
+            ErrorType::FailedToStartThread => nsIBits::ERROR_TYPE_FAILED_TO_START_THREAD,
+            ErrorType::FailedToConstructTaskRunnable => {
+                nsIBits::ERROR_TYPE_FAILED_TO_CONSTRUCT_TASK_RUNNABLE
+            }
+            ErrorType::FailedToDispatchRunnable => nsIBits::ERROR_TYPE_FAILED_TO_DISPATCH_RUNNABLE,
+            ErrorType::TransferAlreadyComplete => nsIBits::ERROR_TYPE_TRANSFER_ALREADY_COMPLETE,
+            ErrorType::OperationAlreadyInProgress => {
+                nsIBits::ERROR_TYPE_OPERATION_ALREADY_IN_PROGRESS
+            }
+            ErrorType::MissingBitsClient => nsIBits::ERROR_TYPE_MISSING_BITS_CLIENT,
+            ErrorType::FailedToGetJobStatus => nsIBits::ERROR_TYPE_FAILED_TO_GET_JOB_STATUS,
+            ErrorType::BitsStateError => nsIBits::ERROR_TYPE_BITS_STATE_ERROR,
+            ErrorType::BitsStateTransientError => nsIBits::ERROR_TYPE_BITS_STATE_TRANSIENT_ERROR,
+            ErrorType::BitsStateCancelled => nsIBits::ERROR_TYPE_BITS_STATE_CANCELLED,
+            ErrorType::BitsStateUnexpected => nsIBits::ERROR_TYPE_BITS_STATE_UNEXPECTED,
+        };
+        val as i32
+    }
+}
+
+impl From<&PipeError> for ErrorType {
+    fn from(error: &PipeError) -> Self {
+        match error {
+            PipeError::NotConnected => ErrorType::PipeNotConnected,
+            PipeError::Timeout => ErrorType::PipeTimeout,
+            PipeError::WriteCount(_, _) => ErrorType::PipeBadWriteCount,
+            PipeError::Api(_) => ErrorType::PipeApiError,
+        }
+    }
+}
+
+#[derive(Debug, PartialEq, Clone, Copy)]
+pub enum ErrorStage {
+    Pretask,
+    CommandThread,
+    AgentCommunication,
+    BitsClient,
+    MainThread,
+}
+
+impl ErrorStage {
+    pub fn bits_code(&self) -> i32 {
+        let val = match self {
+            ErrorStage::Pretask => nsIBits::ERROR_STAGE_PRETASK,
+            ErrorStage::CommandThread => nsIBits::ERROR_STAGE_COMMAND_THREAD,
+            ErrorStage::AgentCommunication => nsIBits::ERROR_STAGE_AGENT_COMMUNICATION,
+            ErrorStage::BitsClient => nsIBits::ERROR_STAGE_BITS_CLIENT,
+            ErrorStage::MainThread => nsIBits::ERROR_STAGE_MAIN_THREAD,
+        };
+        val as i32
+    }
+}
+
+#[derive(Debug, Clone)]
+pub enum ErrorCode {
+    None,
+    Hresult(i32),
+    Nsresult(nsresult),
+    Message(nsCString),
+}
+
+impl From<ComedyError> for ErrorCode {
+    fn from(error: ComedyError) -> Self {
+        ErrorCode::Hresult(error.code())
+    }
+}
+
+impl From<HResultMessage> for ErrorCode {
+    fn from(result: HResultMessage) -> Self {
+        ErrorCode::Hresult(result.hr)
+    }
+}
+
+#[derive(Debug, Clone)]
+pub struct BitsTaskError {
+    pub error_type: ErrorType,
+    pub error_action: Action,
+    pub error_stage: ErrorStage,
+    pub error_code: ErrorCode,
+}
+
+impl BitsTaskError {
+    pub fn new(
+        error_type: ErrorType,
+        error_action: Action,
+        error_stage: ErrorStage,
+    ) -> BitsTaskError {
+        BitsTaskError {
+            error_type,
+            error_action,
+            error_stage,
+            error_code: ErrorCode::None,
+        }
+    }
+
+    pub fn missing_result(error_action: Action) -> BitsTaskError {
+        BitsTaskError {
+            error_type: ErrorType::MissingResultData,
+            error_action,
+            error_stage: ErrorStage::MainThread,
+            error_code: ErrorCode::None,
+        }
+    }
+
+    pub fn from_nsresult(
+        error_type: ErrorType,
+        error_action: Action,
+        error_stage: ErrorStage,
+        error_code: nsresult,
+    ) -> BitsTaskError {
+        BitsTaskError {
+            error_type,
+            error_action,
+            error_stage,
+            error_code: ErrorCode::Nsresult(error_code),
+        }
+    }
+
+    pub fn from_hresult(
+        error_type: ErrorType,
+        error_action: Action,
+        error_stage: ErrorStage,
+        error_code: i32,
+    ) -> BitsTaskError {
+        BitsTaskError {
+            error_type,
+            error_action,
+            error_stage,
+            error_code: ErrorCode::Hresult(error_code),
+        }
+    }
+
+    pub fn from_comedy(
+        error_type: ErrorType,
+        error_action: Action,
+        error_stage: ErrorStage,
+        comedy_error: ComedyError,
+    ) -> BitsTaskError {
+        BitsTaskError {
+            error_type,
+            error_action,
+            error_stage,
+            error_code: comedy_error.into(),
+        }
+    }
+
+    pub fn from_pipe(error_action: Action, pipe_error: PipeError) -> BitsTaskError {
+        let error_type = (&pipe_error).into();
+        match pipe_error {
+            PipeError::Api(comedy_error) => BitsTaskError {
+                error_type,
+                error_action,
+                error_stage: ErrorStage::AgentCommunication,
+                error_code: comedy_error.into(),
+            },
+            _ => BitsTaskError {
+                error_type,
+                error_action,
+                error_stage: ErrorStage::AgentCommunication,
+                error_code: ErrorCode::None,
+            },
+        }
+    }
+}
+
+impl From<BitsTaskError> for nsresult {
+    fn from(error: BitsTaskError) -> Self {
+        if let ErrorCode::Nsresult(rv) = error.error_code {
+            rv
+        } else {
+            NS_ERROR_FAILURE
+        }
+    }
+}
+
+impl From<StartJobFailure> for BitsTaskError {
+    fn from(error: StartJobFailure) -> Self {
+        let error_stage = ErrorStage::BitsClient;
+        let error_action = Action::StartDownload;
+        match error {
+            StartJobFailure::ArgumentValidation(message) => BitsTaskError {
+                error_type: ErrorType::InvalidArgument,
+                error_action,
+                error_stage,
+                error_code: ErrorCode::Message(nsCString::from(message)),
+            },
+            StartJobFailure::Create(error_code) => BitsTaskError {
+                error_type: ErrorType::FailedToCreateBitsJob,
+                error_action,
+                error_stage,
+                error_code: error_code.into(),
+            },
+            StartJobFailure::AddFile(error_code) => BitsTaskError {
+                error_type: ErrorType::FailedToAddFileToJob,
+                error_action,
+                error_stage,
+                error_code: error_code.into(),
+            },
+            StartJobFailure::ApplySettings(error_code) => BitsTaskError {
+                error_type: ErrorType::FailedToApplyBitsJobSettings,
+                error_action,
+                error_stage,
+                error_code: error_code.into(),
+            },
+            StartJobFailure::Resume(error_code) => BitsTaskError {
+                error_type: ErrorType::FailedToResumeBitsJob,
+                error_action,
+                error_stage,
+                error_code: error_code.into(),
+            },
+            StartJobFailure::OtherBITS(error_code) => BitsTaskError {
+                error_type: ErrorType::OtherBitsError,
+                error_action,
+                error_stage,
+                error_code: error_code.into(),
+            },
+            StartJobFailure::Other(message) => BitsTaskError {
+                error_type: ErrorType::OtherBitsClientError,
+                error_action,
+                error_stage,
+                error_code: ErrorCode::Message(nsCString::from(message)),
+            },
+        }
+    }
+}
+
+impl From<MonitorJobFailure> for BitsTaskError {
+    fn from(error: MonitorJobFailure) -> Self {
+        let error_stage = ErrorStage::BitsClient;
+        let error_action = Action::MonitorDownload;
+        match error {
+            MonitorJobFailure::ArgumentValidation(message) => BitsTaskError {
+                error_type: ErrorType::InvalidArgument,
+                error_action,
+                error_stage,
+                error_code: ErrorCode::Message(nsCString::from(message)),
+            },
+            MonitorJobFailure::NotFound => BitsTaskError {
+                error_type: ErrorType::BitsJobNotFound,
+                error_action,
+                error_stage,
+                error_code: ErrorCode::None,
+            },
+            MonitorJobFailure::GetJob(error_code) => BitsTaskError {
+                error_type: ErrorType::FailedToGetBitsJob,
+                error_action,
+                error_stage,
+                error_code: error_code.into(),
+            },
+            MonitorJobFailure::OtherBITS(error_code) => BitsTaskError {
+                error_type: ErrorType::OtherBitsError,
+                error_action,
+                error_stage,
+                error_code: error_code.into(),
+            },
+            MonitorJobFailure::Other(message) => BitsTaskError {
+                error_type: ErrorType::OtherBitsClientError,
+                error_action,
+                error_stage,
+                error_code: ErrorCode::Message(nsCString::from(message)),
+            },
+        }
+    }
+}
+
+impl From<SuspendJobFailure> for BitsTaskError {
+    fn from(error: SuspendJobFailure) -> Self {
+        let error_stage = ErrorStage::BitsClient;
+        let error_action = Action::Suspend;
+        match error {
+            SuspendJobFailure::NotFound => BitsTaskError {
+                error_type: ErrorType::BitsJobNotFound,
+                error_action,
+                error_stage,
+                error_code: ErrorCode::None,
+            },
+            SuspendJobFailure::GetJob(error_code) => BitsTaskError {
+                error_type: ErrorType::FailedToGetBitsJob,
+                error_action,
+                error_stage,
+                error_code: error_code.into(),
+            },
+            SuspendJobFailure::SuspendJob(error_code) => BitsTaskError {
+                error_type: ErrorType::FailedToSuspendBitsJob,
+                error_action,
+                error_stage,
+                error_code: error_code.into(),
+            },
+            SuspendJobFailure::OtherBITS(error_code) => BitsTaskError {
+                error_type: ErrorType::OtherBitsError,
+                error_action,
+                error_stage,
+                error_code: error_code.into(),
+            },
+            SuspendJobFailure::Other(message) => BitsTaskError {
+                error_type: ErrorType::OtherBitsClientError,
+                error_action,
+                error_stage,
+                error_code: ErrorCode::Message(nsCString::from(message)),
+            },
+        }
+    }
+}
+
+impl From<ResumeJobFailure> for BitsTaskError {
+    fn from(error: ResumeJobFailure) -> Self {
+        let error_stage = ErrorStage::BitsClient;
+        let error_action = Action::Resume;
+        match error {
+            ResumeJobFailure::NotFound => BitsTaskError {
+                error_type: ErrorType::BitsJobNotFound,
+                error_action,
+                error_stage,
+                error_code: ErrorCode::None,
+            },
+            ResumeJobFailure::GetJob(error_code) => BitsTaskError {
+                error_type: ErrorType::FailedToGetBitsJob,
+                error_action,
+                error_stage,
+                error_code: error_code.into(),
+            },
+            ResumeJobFailure::ResumeJob(error_code) => BitsTaskError {
+                error_type: ErrorType::FailedToResumeBitsJob,
+                error_action,
+                error_stage,
+                error_code: error_code.into(),
+            },
+            ResumeJobFailure::OtherBITS(error_code) => BitsTaskError {
+                error_type: ErrorType::OtherBitsError,
+                error_action,
+                error_stage,
+                error_code: error_code.into(),
+            },
+            ResumeJobFailure::Other(message) => BitsTaskError {
+                error_type: ErrorType::OtherBitsClientError,
+                error_action,
+                error_stage,
+                error_code: ErrorCode::Message(nsCString::from(message)),
+            },
+        }
+    }
+}
+
+impl From<SetJobPriorityFailure> for BitsTaskError {
+    fn from(error: SetJobPriorityFailure) -> Self {
+        let error_stage = ErrorStage::BitsClient;
+        let error_action = Action::SetPriority;
+        match error {
+            SetJobPriorityFailure::NotFound => BitsTaskError {
+                error_type: ErrorType::BitsJobNotFound,
+                error_action,
+                error_stage,
+                error_code: ErrorCode::None,
+            },
+            SetJobPriorityFailure::GetJob(error_code) => BitsTaskError {
+                error_type: ErrorType::FailedToGetBitsJob,
+                error_action,
+                error_stage,
+                error_code: error_code.into(),
+            },
+            SetJobPriorityFailure::ApplySettings(error_code) => BitsTaskError {
+                error_type: ErrorType::FailedToApplyBitsJobSettings,
+                error_action,
+                error_stage,
+                error_code: error_code.into(),
+            },
+            SetJobPriorityFailure::OtherBITS(error_code) => BitsTaskError {
+                error_type: ErrorType::OtherBitsError,
+                error_action,
+                error_stage,
+                error_code: error_code.into(),
+            },
+            SetJobPriorityFailure::Other(message) => BitsTaskError {
+                error_type: ErrorType::OtherBitsClientError,
+                error_action,
+                error_stage,
+                error_code: ErrorCode::Message(nsCString::from(message)),
+            },
+        }
+    }
+}
+
+impl From<SetUpdateIntervalFailure> for BitsTaskError {
+    fn from(error: SetUpdateIntervalFailure) -> Self {
+        let error_stage = ErrorStage::BitsClient;
+        let error_action = Action::SetMonitorInterval;
+        match error {
+            SetUpdateIntervalFailure::ArgumentValidation(message) => BitsTaskError {
+                error_type: ErrorType::InvalidArgument,
+                error_action,
+                error_stage,
+                error_code: ErrorCode::Message(nsCString::from(message)),
+            },
+            SetUpdateIntervalFailure::NotFound => BitsTaskError {
+                error_type: ErrorType::BitsJobNotFound,
+                error_action,
+                error_stage,
+                error_code: ErrorCode::None,
+            },
+            SetUpdateIntervalFailure::Other(message) => BitsTaskError {
+                error_type: ErrorType::OtherBitsClientError,
+                error_action,
+                error_stage,
+                error_code: ErrorCode::Message(nsCString::from(message)),
+            },
+        }
+    }
+}
+
+impl From<CompleteJobFailure> for BitsTaskError {
+    fn from(error: CompleteJobFailure) -> Self {
+        let error_stage = ErrorStage::BitsClient;
+        let error_action = Action::Complete;
+        match error {
+            CompleteJobFailure::NotFound => BitsTaskError {
+                error_type: ErrorType::BitsJobNotFound,
+                error_action,
+                error_stage,
+                error_code: ErrorCode::None,
+            },
+            CompleteJobFailure::GetJob(error_code) => BitsTaskError {
+                error_type: ErrorType::FailedToGetBitsJob,
+                error_action,
+                error_stage,
+                error_code: error_code.into(),
+            },
+            CompleteJobFailure::CompleteJob(error_code) => BitsTaskError {
+                error_type: ErrorType::FailedToCompleteBitsJob,
+                error_action,
+                error_stage,
+                error_code: error_code.into(),
+            },
+            CompleteJobFailure::PartialComplete => BitsTaskError {
+                error_type: ErrorType::PartiallyCompletedBitsJob,
+                error_action,
+                error_stage,
+                error_code: ErrorCode::None,
+            },
+            CompleteJobFailure::OtherBITS(error_code) => BitsTaskError {
+                error_type: ErrorType::OtherBitsError,
+                error_action,
+                error_stage,
+                error_code: error_code.into(),
+            },
+            CompleteJobFailure::Other(message) => BitsTaskError {
+                error_type: ErrorType::OtherBitsClientError,
+                error_action,
+                error_stage,
+                error_code: ErrorCode::Message(nsCString::from(message)),
+            },
+        }
+    }
+}
+
+impl From<CancelJobFailure> for BitsTaskError {
+    fn from(error: CancelJobFailure) -> Self {
+        let error_stage = ErrorStage::BitsClient;
+        let error_action = Action::Cancel;
+        match error {
+            CancelJobFailure::NotFound => BitsTaskError {
+                error_type: ErrorType::BitsJobNotFound,
+                error_action,
+                error_stage,
+                error_code: ErrorCode::None,
+            },
+            CancelJobFailure::GetJob(error_code) => BitsTaskError {
+                error_type: ErrorType::FailedToGetBitsJob,
+                error_action,
+                error_stage,
+                error_code: error_code.into(),
+            },
+            CancelJobFailure::CancelJob(error_code) => BitsTaskError {
+                error_type: ErrorType::FailedToCancelBitsJob,
+                error_action,
+                error_stage,
+                error_code: error_code.into(),
+            },
+            CancelJobFailure::OtherBITS(error_code) => BitsTaskError {
+                error_type: ErrorType::OtherBitsError,
+                error_action,
+                error_stage,
+                error_code: error_code.into(),
+            },
+            CancelJobFailure::Other(message) => BitsTaskError {
+                error_type: ErrorType::OtherBitsClientError,
+                error_action,
+                error_stage,
+                error_code: ErrorCode::Message(nsCString::from(message)),
+            },
+        }
+    }
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/bitsdownload/src/bits_interface/mod.rs
@@ -0,0 +1,328 @@
+/* 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/. */
+mod action;
+mod dispatch_callback;
+mod error;
+mod monitor;
+mod request;
+mod string;
+mod task;
+mod xpcom_methods;
+
+pub use self::error::BitsTaskError;
+use self::{
+    action::Action,
+    error::{
+        ErrorStage::Pretask,
+        ErrorType::{
+            FailedToConstructTaskRunnable, FailedToDispatchRunnable, FailedToStartThread,
+            InvalidArgument, NotInitialized,
+        },
+    },
+    request::BitsRequest,
+    string::Guid_from_nsCString,
+    task::{ClientInitData, MonitorDownloadTask, StartDownloadTask},
+};
+use nsIBits_method; // From xpcom_method.rs
+
+use bits_client::BitsProxyUsage;
+use log::{info, warn};
+use moz_task::{create_thread, Task, TaskRunnable};
+use nserror::{nsresult, NS_ERROR_ALREADY_INITIALIZED, NS_OK};
+use nsstring::{nsACString, nsCString};
+use std::cell::Cell;
+use xpcom::{
+    interfaces::{
+        nsIBits, nsIBitsNewRequestCallback, nsIRequestObserver, nsISupports, nsIThread,
+        nsProxyUsage,
+    },
+    xpcom, xpcom_method, RefPtr,
+};
+
+#[no_mangle]
+pub unsafe extern "C" fn new_bits_service(result: *mut *const nsIBits) {
+    let service: RefPtr<BitsService> = BitsService::new();
+    RefPtr::new(service.coerce::<nsIBits>()).forget(&mut *result);
+}
+
+#[derive(xpcom)]
+#[xpimplements(nsIBits)]
+#[refcnt = "nonatomic"]
+pub struct InitBitsService {
+    // This command thread will be used to send commands (ex: Suspend, Resume)
+    // to a running job. It will be started up when the first job is created and
+    // shutdown when all jobs have been completed or cancelled.
+    command_thread: Cell<Option<RefPtr<nsIThread>>>,
+    client_init_data: Cell<Option<ClientInitData>>,
+    // This count will track the number of in-progress requests so that the
+    // service knows when the command_thread is no longer being used and can be
+    // shut down.
+    // `BitsRequest::new()` will increment this and it will be decremented
+    // either when cancel/complete is called, or when the request is dropped
+    // (if it didn't decrement it already).
+    // The count will also be incremented when an action to create a request
+    // starts and decremented when the action ends and returns the result via
+    // the callback. This prevents the command thread from being shut down while
+    // a job is being created.
+    request_count: Cell<u32>,
+}
+
+/// This implements the nsIBits interface, documented in nsIBits.idl, to enable
+/// BITS job management. Specifically, this interface can start a download or
+/// connect to an existing download. Doing so will create a BitsRequest through
+/// which the transfer can be further manipulated.
+///
+/// This is a primarily asynchronous interface, which is accomplished via
+/// callbacks of type nsIBitsNewRequestCallback. The callback is passed in as
+/// an argument and is then passed off-thread via a Task. The Task interacts
+/// with BITS and is dispatched back to the main thread with the BITS result.
+/// Back on the main thread, it returns that result via the callback including,
+/// if successful, a BitsRequest.
+impl BitsService {
+    pub fn new() -> RefPtr<BitsService> {
+        BitsService::allocate(InitBitsService {
+            command_thread: Cell::new(None),
+            client_init_data: Cell::new(None),
+            request_count: Cell::new(0),
+        })
+    }
+
+    fn get_client_init(&self) -> Option<ClientInitData> {
+        let maybe_init_data = self.client_init_data.take();
+        self.client_init_data.set(maybe_init_data.clone());
+        maybe_init_data
+    }
+
+    // Returns the handle to the command thread. If it has not been started yet,
+    // the thread will be started.
+    fn get_command_thread(&self) -> Result<RefPtr<nsIThread>, nsresult> {
+        let mut command_thread = self.command_thread.take();
+        if command_thread.is_none() {
+            command_thread.replace(create_thread("BitsCommander")?);
+        }
+        self.command_thread.set(command_thread.clone());
+        Ok(command_thread.unwrap())
+    }
+
+    // Asynchronously shuts down the command thread. The thread is not shutdown
+    // until the event queue is empty, so any tasks that were dispatched before
+    // this is called will still run.
+    // Leaves None in self.command_thread
+    fn shutdown_command_thread(&self) {
+        if let Some(command_thread) = self.command_thread.take() {
+            if let Err(rv) = unsafe { command_thread.AsyncShutdown() }.to_result() {
+                warn!("Failed to shut down command thread: {}", rv);
+                warn!("Releasing reference to thread that failed to shut down!");
+            }
+        }
+    }
+
+    fn dispatch_runnable_to_command_thread(
+        &self,
+        task: Box<dyn Task + Send + Sync>,
+        task_runnable_name: &'static str,
+        action: Action,
+    ) -> Result<(), BitsTaskError> {
+        let command_thread = self
+            .get_command_thread()
+            .map_err(|rv| BitsTaskError::from_nsresult(FailedToStartThread, action, Pretask, rv))?;
+        let runnable = TaskRunnable::new(task_runnable_name, task).map_err(|rv| {
+            BitsTaskError::from_nsresult(FailedToConstructTaskRunnable, action, Pretask, rv)
+        })?;
+        runnable.dispatch(&command_thread).map_err(|rv| {
+            BitsTaskError::from_nsresult(FailedToDispatchRunnable, action, Pretask, rv)
+        })
+    }
+
+    fn inc_request_count(&self) {
+        self.request_count.set(self.request_count.get() + 1);
+    }
+
+    fn dec_request_count(&self) {
+        let mut count = self.request_count.get();
+        if count == 0 {
+            warn!("Attempted to decrement request count, but it is 0");
+            return;
+        }
+        count -= 1;
+        self.request_count.set(count);
+
+        if count == 0 {
+            self.shutdown_command_thread();
+        }
+    }
+
+    xpcom_method!(
+        get_initialized => GetInitialized() -> bool
+    );
+    fn get_initialized(&self) -> Result<bool, nsresult> {
+        Ok(self.get_client_init().is_some())
+    }
+
+    xpcom_method!(
+        init => Init(
+            job_name: *const nsACString,
+            save_path_prefix: *const nsACString,
+            monitor_timeout_ms: u32
+        )
+    );
+    fn init(
+        &self,
+        job_name: &nsACString,
+        save_path_prefix: &nsACString,
+        monitor_timeout_ms: u32,
+    ) -> Result<(), nsresult> {
+        let previous_data = self.client_init_data.take();
+        if previous_data.is_some() {
+            self.client_init_data.set(previous_data);
+            return Err(NS_ERROR_ALREADY_INITIALIZED);
+        }
+
+        info!(
+            "BitsService initialized with job_name: {}, save_path_prefix: {}, timeout: {}",
+            job_name, save_path_prefix, monitor_timeout_ms,
+        );
+
+        self.client_init_data.set(Some(ClientInitData::new(
+            nsCString::from(job_name),
+            nsCString::from(save_path_prefix),
+            monitor_timeout_ms,
+        )));
+
+        Ok(())
+    }
+
+    nsIBits_method!(
+        [Action::StartDownload]
+        start_download => StartDownload(
+            download_url: *const nsACString,
+            save_rel_path: *const nsACString,
+            proxy: nsProxyUsage,
+            update_interval_ms: u32,
+            observer: *const nsIRequestObserver,
+            [optional] context: *const nsISupports,
+        )
+    );
+    fn start_download(
+        &self,
+        download_url: &nsACString,
+        save_rel_path: &nsACString,
+        proxy: nsProxyUsage,
+        update_interval_ms: u32,
+        observer: &nsIRequestObserver,
+        context: Option<&nsISupports>,
+        callback: &nsIBitsNewRequestCallback,
+    ) -> Result<(), BitsTaskError> {
+        let client_init_data = self
+            .get_client_init()
+            .ok_or_else(|| BitsTaskError::new(NotInitialized, Action::StartDownload, Pretask))?;
+        if update_interval_ms >= client_init_data.monitor_timeout_ms {
+            return Err(BitsTaskError::new(
+                InvalidArgument,
+                Action::StartDownload,
+                Pretask,
+            ));
+        }
+        let proxy = match proxy as i64 {
+            nsIBits::PROXY_NONE => BitsProxyUsage::NoProxy,
+            nsIBits::PROXY_PRECONFIG => BitsProxyUsage::Preconfig,
+            nsIBits::PROXY_AUTODETECT => BitsProxyUsage::AutoDetect,
+            _ => {
+                return Err(BitsTaskError::new(
+                    InvalidArgument,
+                    Action::StartDownload,
+                    Pretask,
+                ));
+            }
+        };
+
+        let task: Box<StartDownloadTask> = Box::new(StartDownloadTask::new(
+            client_init_data,
+            nsCString::from(download_url),
+            nsCString::from(save_rel_path),
+            proxy,
+            update_interval_ms,
+            RefPtr::new(self),
+            RefPtr::new(observer),
+            context.map(RefPtr::new),
+            RefPtr::new(callback),
+        ));
+
+        let dispatch_result = self.dispatch_runnable_to_command_thread(
+            task,
+            "BitsService::start_download",
+            Action::StartDownload,
+        );
+
+        if dispatch_result.is_ok() {
+            // Increment the request count when we dispatch an action to start
+            // a job, decrement it when the action completes. See the
+            // declaration of InitBitsService::request_count for details.
+            self.inc_request_count();
+        }
+
+        dispatch_result
+    }
+
+    nsIBits_method!(
+        [Action::MonitorDownload]
+        monitor_download => MonitorDownload(
+            id: *const nsACString,
+            update_interval_ms: u32,
+            observer: *const nsIRequestObserver,
+            [optional] context: *const nsISupports,
+        )
+    );
+    fn monitor_download(
+        &self,
+        id: &nsACString,
+        update_interval_ms: u32,
+        observer: &nsIRequestObserver,
+        context: Option<&nsISupports>,
+        callback: &nsIBitsNewRequestCallback,
+    ) -> Result<(), BitsTaskError> {
+        let client_init_data = self
+            .get_client_init()
+            .ok_or_else(|| BitsTaskError::new(NotInitialized, Action::MonitorDownload, Pretask))?;
+        if update_interval_ms >= client_init_data.monitor_timeout_ms {
+            return Err(BitsTaskError::new(
+                InvalidArgument,
+                Action::MonitorDownload,
+                Pretask,
+            ));
+        }
+        let guid = Guid_from_nsCString(&nsCString::from(id), Action::MonitorDownload, Pretask)?;
+
+        let task: Box<MonitorDownloadTask> = Box::new(MonitorDownloadTask::new(
+            client_init_data,
+            guid,
+            update_interval_ms,
+            RefPtr::new(self),
+            RefPtr::new(observer),
+            context.map(RefPtr::new),
+            RefPtr::new(callback),
+        ));
+
+        let dispatch_result = self.dispatch_runnable_to_command_thread(
+            task,
+            "BitsService::monitor_download",
+            Action::MonitorDownload,
+        );
+
+        if dispatch_result.is_ok() {
+            // Increment the request count when we dispatch an action to start
+            // a job, decrement it when the action completes. See the
+            // declaration of InitBitsService::request_count for details.
+            self.inc_request_count();
+        }
+
+        dispatch_result
+    }
+}
+
+impl Drop for BitsService {
+    fn drop(&mut self) {
+        self.shutdown_command_thread();
+    }
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/bitsdownload/src/bits_interface/monitor.rs
@@ -0,0 +1,248 @@
+/* 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 bits_interface::{error::ErrorType, BitsRequest};
+
+use bits_client::{
+    bits_protocol::HResultMessage, BitsJobState, BitsMonitorClient, Guid, JobStatus, PipeError,
+};
+use crossbeam_utils::atomic::AtomicCell;
+use log::error;
+use moz_task::{get_main_thread, is_main_thread};
+use nserror::{nsresult, NS_ERROR_ABORT, NS_ERROR_FAILURE, NS_OK};
+use nsstring::{nsACString, nsCString};
+use xpcom::{
+    interfaces::{nsIEventTarget, nsIThread},
+    xpcom, xpcom_method, RefPtr, ThreadBoundRefPtr,
+};
+
+/// This function takes the output of BitsMonitorClient::get_status() and uses
+/// it to determine whether the the transfer has started. If the argument
+/// contains an error, the transfer is considered started because we also
+/// consider a transfer stopped on error.
+/// This function is used to determine whether the OnStartRequest and OnProgress
+/// observer functions should be called.
+fn transfer_started(status: &Result<Result<JobStatus, HResultMessage>, PipeError>) -> bool {
+    match status.as_ref() {
+        Ok(Ok(job_status)) => match job_status.state {
+            BitsJobState::Queued | BitsJobState::Connecting => false,
+            _ => true,
+        },
+        Ok(Err(_)) => true,
+        Err(_) => true,
+    }
+}
+
+/// This function takes the output of BitsMonitorClient::get_status() and uses
+/// it to determine whether the the transfer has stopped. If the argument
+/// contains an error, the transfer is considered stopped.
+/// A number of things will be done when a transfer is completed, such as
+/// calling the observer's OnStopRequest method.
+fn transfer_completed(status: &Result<Result<JobStatus, HResultMessage>, PipeError>) -> bool {
+    match status.as_ref() {
+        Ok(Ok(job_status)) => match job_status.state {
+            BitsJobState::Error
+            | BitsJobState::Transferred
+            | BitsJobState::Acknowledged
+            | BitsJobState::Cancelled => true,
+            _ => false,
+        },
+        Ok(Err(_)) => true,
+        Err(_) => true,
+    }
+}
+
+/// BitsRequest implements nsIRequest, which means that it must be able to
+/// provide an nsresult status code. This function provides such a status code
+/// based on the output of BitsMonitorClient::get_status().
+fn status_to_nsresult(status: &Result<Result<JobStatus, HResultMessage>, PipeError>) -> nsresult {
+    match status.as_ref() {
+        Ok(Ok(job_status)) => match job_status.state {
+            BitsJobState::Cancelled => NS_ERROR_ABORT,
+            BitsJobState::Transferred | BitsJobState::Acknowledged => NS_OK,
+            _ => NS_ERROR_FAILURE,
+        },
+        Ok(Err(_)) => NS_ERROR_FAILURE,
+        Err(_) => NS_ERROR_FAILURE,
+    }
+}
+
+/// This function takes the output of BitsMonitorClient::get_status() and uses
+/// it to determine the result value of the request. This will take the form of
+/// an Optional ErrorType value with a None value indicating success.
+fn status_to_request_result(
+    status: &Result<Result<JobStatus, HResultMessage>, PipeError>,
+) -> Option<ErrorType> {
+    match status.as_ref() {
+        Ok(Ok(job_status)) => match job_status.state {
+            BitsJobState::Transferred | BitsJobState::Acknowledged => None,
+            BitsJobState::Cancelled => Some(ErrorType::BitsStateCancelled),
+            BitsJobState::Error => Some(ErrorType::BitsStateError),
+            BitsJobState::TransientError => Some(ErrorType::BitsStateTransientError),
+            _ => Some(ErrorType::BitsStateUnexpected),
+        },
+        Ok(Err(_)) => Some(ErrorType::FailedToGetJobStatus),
+        Err(pipe_error) => Some(pipe_error.into()),
+    }
+}
+
+/// MonitorRunnable is an nsIRunnable meant to be dispatched off thread. It will
+/// perform the following actions:
+///   1. Call BitsMonitorClient::get_status and store the result.
+///   2. Dispatch itself back to the main thread.
+///   3. Report the status to the observer.
+///   4. If the transfer has finished, free its data and return, otherwise:
+///   5. Dispatch itself back to its original thread and repeat from step 1.
+#[derive(xpcom)]
+#[xpimplements(nsIRunnable, nsINamed)]
+#[refcnt = "atomic"]
+pub struct InitMonitorRunnable {
+    request: AtomicCell<Option<ThreadBoundRefPtr<BitsRequest>>>,
+    id: Guid,
+    timeout: u32,
+    monitor_client: AtomicCell<Option<BitsMonitorClient>>,
+    // This cell contains an Option, possibly containing the return value of
+    // BitsMonitorClient::get_status.
+    status: AtomicCell<Option<Result<Result<JobStatus, HResultMessage>, PipeError>>>,
+    request_started: AtomicCell<bool>,
+    in_error_state: AtomicCell<bool>,
+}
+
+impl MonitorRunnable {
+    pub fn new(
+        request: RefPtr<BitsRequest>,
+        id: Guid,
+        timeout: u32,
+        monitor_client: BitsMonitorClient,
+    ) -> RefPtr<MonitorRunnable> {
+        MonitorRunnable::allocate(InitMonitorRunnable {
+            request: AtomicCell::new(Some(ThreadBoundRefPtr::new(request))),
+            id,
+            timeout,
+            monitor_client: AtomicCell::new(Some(monitor_client)),
+            status: AtomicCell::new(None),
+            request_started: AtomicCell::new(false),
+            in_error_state: AtomicCell::new(false),
+        })
+    }
+
+    pub fn dispatch(&self, thread: RefPtr<nsIThread>) -> Result<(), nsresult> {
+        unsafe { thread.DispatchFromScript(self.coerce(), nsIEventTarget::DISPATCH_NORMAL as u32) }
+            .to_result()
+    }
+
+    fn free_mainthread_data(&self) {
+        if is_main_thread() {
+            // This is not safe to free unless on the main thread
+            self.request.swap(None);
+        } else {
+            error!("Attempting to free data on the main thread, but not on the main thread");
+        }
+    }
+
+    /// This method is essentially a error-handling wrapper around try_run.
+    /// This is done to make it easier to ensure that main-thread data is freed
+    /// on the main thread.
+    xpcom_method!(run => Run());
+    pub fn run(&self) -> Result<(), nsresult> {
+        if self.in_error_state.load() {
+            self.free_mainthread_data();
+            return Err(NS_ERROR_FAILURE);
+        }
+
+        self.try_run().or_else(|error_message| {
+            error!("{}", error_message);
+
+            // Once an error has been encountered, we need to free all of our
+            // data, but it all needs to be freed on the main thread.
+            self.in_error_state.store(true);
+            if is_main_thread() {
+                self.free_mainthread_data();
+                Err(NS_ERROR_FAILURE)
+            } else {
+                self.dispatch(get_main_thread()?)
+            }
+        })
+    }
+
+    /// This function performs all the primary functionality of MonitorRunnable.
+    /// See the documentation for InitMonitorRunnable/MonitorRunnable for
+    /// details.
+    pub fn try_run(&self) -> Result<(), String> {
+        if !is_main_thread() {
+            let mut monitor_client = self
+                .monitor_client
+                .swap(None)
+                .ok_or("Missing monitor client")?;
+            self.status
+                .store(Some(monitor_client.get_status(self.timeout)));
+            self.monitor_client.store(Some(monitor_client));
+
+            let main_thread =
+                get_main_thread().map_err(|rv| format!("Unable to get main thread: {}", rv))?;
+
+            self.dispatch(main_thread)
+                .map_err(|rv| format!("Unable to dispatch to main thread: {}", rv))
+        } else {
+            let status = self.status.swap(None).ok_or("Missing status object")?;
+            let tb_request = self.request.swap(None).ok_or("Missing request")?;
+
+            // This block bounds the scope for request to ensure that it ends
+            // before re-storing tb_request.
+            let maybe_next_thread: Option<RefPtr<nsIThread>> = {
+                let request = tb_request
+                    .get_ref()
+                    .ok_or("BitsRequest is on the wrong thread")?;
+
+                if !self.request_started.load() && transfer_started(&status) {
+                    self.request_started.store(true);
+                    request.on_start();
+                }
+
+                if self.request_started.load() {
+                    if let Ok(Ok(job_status)) = status.as_ref() {
+                        let transferred_bytes = job_status.progress.transferred_bytes as i64;
+                        let total_bytes = match job_status.progress.total_bytes {
+                            Some(total) => total as i64,
+                            None => -1i64,
+                        };
+                        request.on_progress(transferred_bytes, total_bytes);
+                    }
+                }
+
+                if transfer_completed(&status) {
+                    request.on_stop(Some((
+                        status_to_nsresult(&status),
+                        status_to_request_result(&status),
+                    )));
+
+                    // Transfer completed. No need to dispatch back to the monitor thread.
+                    None
+                } else {
+                    Some(
+                        request
+                            .get_monitor_thread()
+                            .ok_or("Missing monitor thread")?,
+                    )
+                }
+            };
+
+            self.request.store(Some(tb_request));
+
+            match maybe_next_thread {
+                Some(next_thread) => self
+                    .dispatch(next_thread)
+                    .map_err(|rv| format!("Unable to dispatch to thread: {}", rv)),
+                None => {
+                    self.free_mainthread_data();
+                    Ok(())
+                }
+            }
+        }
+    }
+
+    xpcom_method!(get_name => GetName() -> nsACString);
+    fn get_name(&self) -> Result<nsCString, nsresult> {
+        Ok(nsCString::from("BitsRequest::Monitor"))
+    }
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/bitsdownload/src/bits_interface/request.rs
@@ -0,0 +1,692 @@
+/* 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 super::{
+    action::{Action, ServiceAction},
+    error::{
+        ErrorStage::{MainThread, Pretask},
+        ErrorType,
+        ErrorType::{
+            BitsStateCancelled, FailedToDispatchRunnable, FailedToStartThread, InvalidArgument,
+            OperationAlreadyInProgress, TransferAlreadyComplete,
+        },
+    },
+    monitor::MonitorRunnable,
+    task::{
+        CancelTask, ChangeMonitorIntervalTask, CompleteTask, Priority, ResumeTask, SetPriorityTask,
+        SuspendTask,
+    },
+    BitsService, BitsTaskError,
+};
+use nsIBitsRequest_method; // From xpcom_method.rs
+
+use bits_client::{BitsMonitorClient, Guid};
+use log::{error, info, warn};
+use moz_task::create_thread;
+use nserror::{nsresult, NS_ERROR_NOT_IMPLEMENTED, NS_OK};
+use nsstring::{nsACString, nsCString};
+use std::{cell::Cell, fmt, ptr};
+use xpcom::{
+    interfaces::{
+        nsIBits, nsIBitsCallback, nsILoadGroup, nsIProgressEventSink, nsIRequestObserver,
+        nsISupports, nsIThread, nsLoadFlags,
+    },
+    xpcom, xpcom_method, RefPtr, XpCom,
+};
+
+/// This structure exists to resolve a race condition. If cancel is called, we
+/// don't want to immediately set the request state to cancelled, because the
+/// cancel action could fail. But it's possible that on_stop() could be called
+/// before the cancel action resolves, and the correct status should be sent to
+/// OnStopRequest.
+/// This is how this race condition will be resolved:
+///   1.  cancel() is called, which sets the CancelAction to InProgress and
+///       stores in it the status that should be set if it succeeds.
+///   2.  cancel() dispatches the cancel task off thread.
+/// At this point, things unfold in one of two ways, depending on the race
+/// condition. Either:
+///   3.  The cancel task returns to the main thread and calls
+///       BitsRequest::finish_cancel_action.
+///   4.  If the cancel action succeeded, the appropriate status codes are set
+///       and the CancelAction is set to RequestEndPending.
+///       If the cancel action failed, the CancelAction is set to NotInProgress.
+///   5.  The MonitorRunnable detects that the transfer has ended and calls
+///       BitsRequest::on_stop, passing different status codes.
+///   6.  BitsRequest::on_stop checks the CancelAction and
+///       If the cancel action succeeded and RequestEndPending is set, the
+///       status codes that were set by BitsRequest::finish_cancel_action are
+///       left untouched.
+///       If the cancel action failed and NotInProgress is set, the status codes
+///       passed to BitsRequest::on_stop are set.
+///   7.  onStopRequest is called with the correct status code.
+/// Or, if MonitorRunnable calls on_stop before the cancel task can finish:
+///   3.  The MonitorRunnable detects that the transfer has ended and calls
+///       BitsRequest::on_stop, passing status codes to it.
+///   4.  BitsRequest::on_stop checks the CancelAction, sees it is set to
+///       InProgress, and sets it to RequestEndedWhileInProgress, carrying over
+///       the status code from InProgress.
+///   5.  BitsRequest::on_stop sets the status to the value passed to it, which
+///       will be overwritten if the cancel action succeeds, but kept if it
+///       fails.
+///   6.  BitsRequest::on_stop returns early, without calling OnStopRequest.
+///   7.  The cancel task returns to the main thread and calls
+///       BitsRequest::finish_cancel_action.
+///   8.  If the cancel action succeeded, the status codes are set from the
+///       value stored in RequestEndedWhileInProgress.
+///       If the cancel action failed, the status codes are not changed.
+///   9.  The CancelAction is set to NotInProgress.
+///   10. BitsRequest::finish_cancel_action calls BitsRequest::on_stop without
+///       passing it any status codes.
+///   11. onStopRequest is called with the correct status code.
+#[derive(Clone, Copy, PartialEq)]
+enum CancelAction {
+    NotInProgress,
+    InProgress(Option<nsresult>),
+    RequestEndedWhileInProgress(Option<nsresult>),
+    RequestEndPending,
+}
+
+#[derive(xpcom)]
+#[xpimplements(nsIBitsRequest)]
+#[refcnt = "nonatomic"]
+pub struct InitBitsRequest {
+    bits_id: Guid,
+    bits_service: RefPtr<BitsService>,
+    // Stores the value to be returned by nsIRequest::IsPending.
+    download_pending: Cell<bool>,
+    // Stores the value to be returned by nsIRequest::GetStatus.
+    download_status_nsresult: Cell<nsresult>,
+    // Stores an ErrorType if the request has failed, or None to represent the
+    // success state.
+    download_status_error_type: Cell<Option<ErrorType>>,
+    // This option will be None only after OnStopRequest has been fired.
+    monitor_thread: Cell<Option<RefPtr<nsIThread>>>,
+    monitor_timeout_ms: u32,
+    observer: RefPtr<nsIRequestObserver>,
+    context: Option<RefPtr<nsISupports>>,
+    // started indicates whether or not OnStartRequest has been fired.
+    started: Cell<bool>,
+    // finished indicates whether or not we have called
+    // BitsService::dec_request_count() to (assuming that there are no other
+    // requests) shutdown the command thread.
+    finished: Cell<bool>,
+    cancel_action: Cell<CancelAction>,
+}
+
+impl fmt::Debug for BitsRequest {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "BitsRequest {{ id: {} }}", self.bits_id)
+    }
+}
+
+/// This implements the nsIBitsRequest interface, documented in nsIBits.idl, to
+/// enable BITS job management. This interface deals only with BITS jobs that
+/// already exist. Jobs can be created via BitsService, which will create a
+/// BitsRequest for that job.
+///
+/// This is a primarily asynchronous interface, which is accomplished via
+/// callbacks of type nsIBitsCallback. The callback is passed in as an argument
+/// and is then passed off-thread via a Task. The Task interacts with BITS and
+/// is dispatched back to the main thread with the BITS result. Back on the main
+/// thread, it returns that result via the callback.
+impl BitsRequest {
+    pub fn new(
+        id: Guid,
+        bits_service: RefPtr<BitsService>,
+        monitor_timeout_ms: u32,
+        observer: RefPtr<nsIRequestObserver>,
+        context: Option<RefPtr<nsISupports>>,
+        monitor_client: BitsMonitorClient,
+        action: ServiceAction,
+    ) -> Result<RefPtr<BitsRequest>, BitsTaskError> {
+        let action: Action = action.into();
+        let monitor_thread = create_thread(&format!("BitsMonitor {}", id)).map_err(|rv| {
+            BitsTaskError::from_nsresult(FailedToStartThread, action, MainThread, rv)
+        })?;
+
+        // BitsRequest.drop() will call dec_request_count
+        bits_service.inc_request_count();
+        let request: RefPtr<BitsRequest> = BitsRequest::allocate(InitBitsRequest {
+            bits_id: id.clone(),
+            bits_service,
+            download_pending: Cell::new(true),
+            download_status_nsresult: Cell::new(NS_OK),
+            download_status_error_type: Cell::new(None),
+            monitor_thread: Cell::new(Some(monitor_thread.clone())),
+            monitor_timeout_ms,
+            observer,
+            context,
+            started: Cell::new(false),
+            finished: Cell::new(false),
+            cancel_action: Cell::new(CancelAction::NotInProgress),
+        });
+
+        let monitor_runnable =
+            MonitorRunnable::new(request.clone(), id, monitor_timeout_ms, monitor_client);
+
+        if let Err(rv) = monitor_runnable.dispatch(monitor_thread.clone()) {
+            request.shutdown_monitor_thread();
+            return Err(BitsTaskError::from_nsresult(
+                FailedToDispatchRunnable,
+                action,
+                MainThread,
+                rv,
+            ));
+        }
+
+        Ok(request)
+    }
+
+    pub fn get_monitor_thread(&self) -> Option<RefPtr<nsIThread>> {
+        let monitor_thread = self.monitor_thread.take();
+        self.monitor_thread.set(monitor_thread.clone());
+        monitor_thread
+    }
+
+    fn has_monitor_thread(&self) -> bool {
+        let maybe_monitor_thread = self.monitor_thread.take();
+        let transferred = maybe_monitor_thread.is_some();
+        self.monitor_thread.set(maybe_monitor_thread);
+        transferred
+    }
+
+    /// If this returns an true, it means that:
+    ///   - The monitor thread and monitor runnable may have been shut down
+    ///   - The BITS job is not in the TRANSFERRING state
+    ///   - The download either completed, failed, or was cancelled
+    ///   - The BITS job may or may not still need complete() or cancel() to be
+    ///     called on it
+    fn request_has_transferred(&self) -> bool {
+        self.request_has_completed() || !self.has_monitor_thread()
+    }
+
+    /// If this returns an error, it means that:
+    ///   - complete() or cancel() has been called on the BITS job.
+    ///   - BitsService::dec_request_count has already been called.
+    ///   - The BitsClient object that this request was using may have been
+    ///     dropped.
+    fn request_has_completed(&self) -> bool {
+        self.finished.get()
+    }
+
+    fn shutdown_monitor_thread(&self) {
+        if let Some(monitor_thread) = self.monitor_thread.take() {
+            if let Err(rv) = unsafe { monitor_thread.AsyncShutdown() }.to_result() {
+                warn!("Failed to shut down monitor thread: {:?}", rv);
+                warn!("Releasing reference to thread that failed to shut down!");
+            }
+        }
+    }
+
+    /**
+     * To be called when the transfer starts. Fires observer.OnStartRequest exactly once.
+     */
+    pub fn on_start(&self) {
+        if self.started.get() {
+            return;
+        }
+        self.started.set(true);
+        if let Err(rv) = unsafe { self.observer.OnStartRequest(self.coerce()) }.to_result() {
+            // This behavior is specified by nsIRequestObserver.
+            // See nsIRequestObserver.idl
+            info!(
+                "Cancelling download because OnStartRequest rejected with: {:?}",
+                rv
+            );
+            if let Err(rv) = self.cancel(None, None) {
+                warn!("Failed to cancel download: {:?}", rv);
+            }
+        }
+    }
+
+    pub fn on_progress(&self, transferred_bytes: i64, total_bytes: i64) {
+        if let Some(progress_event_sink) = self.observer.query_interface::<nsIProgressEventSink>() {
+            let context: *const nsISupports = match self.context.as_ref() {
+                Some(context) => &**context,
+                None => ptr::null(),
+            };
+            unsafe {
+                progress_event_sink.OnProgress(
+                    self.coerce(),
+                    context,
+                    transferred_bytes,
+                    total_bytes,
+                );
+            }
+        }
+    }
+
+    /// To be called when the transfer stops (fails or completes). Fires
+    /// observer.OnStopRequest exactly once, though the call may be delayed to
+    /// resolve a race condition.
+    ///
+    /// The status values, if passed, will be stored in download_status_nsresult
+    /// and download_status_error_type, unless they have been overridden by a
+    /// cancel action.
+    ///
+    /// See the documentation for CancelAction for details.
+    pub fn on_stop(&self, maybe_status: Option<(nsresult, Option<ErrorType>)>) {
+        if !self.has_monitor_thread() {
+            // If the request has already stopped, don't stop it again
+            return;
+        }
+
+        match self.cancel_action.get() {
+            CancelAction::InProgress(saved_status)
+            | CancelAction::RequestEndedWhileInProgress(saved_status) => {
+                if let Some((status, result)) = maybe_status {
+                    self.download_status_nsresult.set(status);
+                    self.download_status_error_type.set(result);
+                }
+
+                info!("Deferring OnStopRequest until Cancel Task completes");
+                self.cancel_action
+                    .set(CancelAction::RequestEndedWhileInProgress(saved_status));
+                return;
+            }
+            CancelAction::NotInProgress => {
+                if let Some((status, result)) = maybe_status {
+                    self.download_status_nsresult.set(status);
+                    self.download_status_error_type.set(result);
+                }
+            }
+            CancelAction::RequestEndPending => {
+                // Don't set the status variables if the end of this request was
+                // the result of a cancel action. The cancel action already set
+                // those values and they should not be changed.
+                // See the CancelAction documentation for details.
+            }
+        }
+
+        self.download_pending.set(false);
+        self.shutdown_monitor_thread();
+        unsafe {
+            self.observer
+                .OnStopRequest(self.coerce(), self.download_status_nsresult.get());
+        }
+    }
+
+    /// To be called after a cancel or complete task has run successfully. If
+    /// this is the only BitsRequest running, this will shut down
+    /// BitsService's command thread, destroying the BitsClient.
+    pub fn on_finished(&self) {
+        if self.finished.get() {
+            return;
+        }
+        self.finished.set(true);
+        self.bits_service.dec_request_count();
+    }
+
+    // Return the same thing for GetBitsId() and GetName().
+    xpcom_method!(
+        maybe_get_bits_id => GetBitsId() -> nsACString
+    );
+    xpcom_method!(
+        maybe_get_bits_id => GetName() -> nsACString
+    );
+    fn maybe_get_bits_id(&self) -> Result<nsCString, nsresult> {
+        Ok(self.get_bits_id())
+    }
+    pub fn get_bits_id(&self) -> nsCString {
+        nsCString::from(self.bits_id.to_string())
+    }
+
+    xpcom_method!(
+        get_bits_transfer_error_nsIBitsRequest => GetTransferError() -> i32
+    );
+    #[allow(non_snake_case)]
+    fn get_bits_transfer_error_nsIBitsRequest(&self) -> Result<i32, nsresult> {
+        let error_type = match self.download_status_error_type.get() {
+            None => nsIBits::ERROR_TYPE_SUCCESS as i32,
+            Some(error_type) => error_type.bits_code(),
+        };
+        Ok(error_type)
+    }
+
+    xpcom_method!(
+        is_pending => IsPending() -> bool
+    );
+    fn is_pending(&self) -> Result<bool, nsresult> {
+        Ok(self.download_pending.get())
+    }
+
+    xpcom_method!(
+        get_status_nsIRequest => GetStatus() -> nsresult
+    );
+    #[allow(non_snake_case)]
+    fn get_status_nsIRequest(&self) -> Result<nsresult, nsresult> {
+        Ok(self.get_status())
+    }
+    pub fn get_status(&self) -> nsresult {
+        self.download_status_nsresult.get()
+    }
+
+    nsIBitsRequest_method!(
+        [Action::SetMonitorInterval]
+        change_monitor_interval => ChangeMonitorInterval(update_interval_ms: u32)
+    );
+    fn change_monitor_interval(
+        &self,
+        update_interval_ms: u32,
+        callback: &nsIBitsCallback,
+    ) -> Result<(), BitsTaskError> {
+        if update_interval_ms == 0 || update_interval_ms >= self.monitor_timeout_ms {
+            return Err(BitsTaskError::new(
+                InvalidArgument,
+                Action::SetMonitorInterval,
+                Pretask,
+            ));
+        }
+        if self.request_has_transferred() {
+            return Err(BitsTaskError::new(
+                TransferAlreadyComplete,
+                Action::SetMonitorInterval,
+                Pretask,
+            ));
+        }
+
+        let task: Box<ChangeMonitorIntervalTask> = Box::new(ChangeMonitorIntervalTask::new(
+            RefPtr::new(self),
+            self.bits_id.clone(),
+            update_interval_ms,
+            RefPtr::new(callback),
+        ));
+
+        self.bits_service.dispatch_runnable_to_command_thread(
+            task,
+            "BitsRequest::change_monitor_interval",
+            Action::SetMonitorInterval,
+        )
+    }
+
+    nsIBitsRequest_method!(
+        [Action::Cancel]
+        cancel_nsIBitsRequest => CancelAsync(status: nsresult)
+    );
+    #[allow(non_snake_case)]
+    fn cancel_nsIBitsRequest(
+        &self,
+        status: nsresult,
+        callback: &nsIBitsCallback,
+    ) -> Result<(), BitsTaskError> {
+        self.cancel(Some(status), Some(RefPtr::new(callback)))
+    }
+    xpcom_method!(
+        cancel_nsIRequest => Cancel(status: nsresult)
+    );
+    #[allow(non_snake_case)]
+    fn cancel_nsIRequest(&self, status: nsresult) -> Result<(), BitsTaskError> {
+        self.cancel(Some(status), None)
+    }
+
+    fn cancel(
+        &self,
+        status: Option<nsresult>,
+        callback: Option<RefPtr<nsIBitsCallback>>,
+    ) -> Result<(), BitsTaskError> {
+        if let Some(cancel_reason) = status.as_ref() {
+            if cancel_reason.succeeded() {
+                return Err(BitsTaskError::new(InvalidArgument, Action::Cancel, Pretask));
+            }
+        }
+        if self.request_has_completed() {
+            return Err(BitsTaskError::new(
+                TransferAlreadyComplete,
+                Action::Cancel,
+                Pretask,
+            ));
+        }
+
+        if self.cancel_action.get() != CancelAction::NotInProgress {
+            return Err(BitsTaskError::new(
+                OperationAlreadyInProgress,
+                Action::Cancel,
+                Pretask,
+            ));
+        }
+        self.cancel_action.set(CancelAction::InProgress(status));
+
+        let task: Box<CancelTask> = Box::new(CancelTask::new(
+            RefPtr::new(self),
+            self.bits_id.clone(),
+            callback,
+        ));
+
+        self.bits_service.dispatch_runnable_to_command_thread(
+            task,
+            "BitsRequest::cancel",
+            Action::Cancel,
+        )
+    }
+
+    /// This function must be called when a cancel action completes.
+    ///
+    /// See the documentation for CancelAction for details.
+    pub fn finish_cancel_action(&self, cancelled_successfully: bool) {
+        let (maybe_status, transfer_ended) = match self.cancel_action.get() {
+            CancelAction::InProgress(maybe_status) => (maybe_status, false),
+            CancelAction::RequestEndedWhileInProgress(maybe_status) => (maybe_status, true),
+            _ => {
+                error!("End of cancel action, but cancel action is not in progress!");
+                return;
+            }
+        };
+        info!(
+            "Finishing cancel action. cancel success = {}",
+            cancelled_successfully
+        );
+        if cancelled_successfully {
+            if let Some(status) = maybe_status {
+                self.download_status_nsresult.set(status);
+            }
+            self.download_status_error_type
+                .set(Some(BitsStateCancelled));
+        }
+
+        let next_stage = if cancelled_successfully && !transfer_ended {
+            // This signals on_stop not to allow the status codes set above to
+            // be overridden by the ones passed to it.
+            CancelAction::RequestEndPending
+        } else {
+            CancelAction::NotInProgress
+        };
+        self.cancel_action.set(next_stage);
+
+        if cancelled_successfully {
+            self.on_finished();
+        }
+
+        if transfer_ended {
+            info!("Running deferred OnStopRequest");
+            self.on_stop(None);
+        }
+    }
+
+    nsIBitsRequest_method!(
+        [Action::SetPriority]
+        set_priority_high => SetPriorityHigh()
+    );
+    fn set_priority_high(&self, callback: &nsIBitsCallback) -> Result<(), BitsTaskError> {
+        self.set_priority(Priority::High, callback)
+    }
+
+    nsIBitsRequest_method!(
+        [Action::SetPriority]
+        set_priority_low => SetPriorityLow()
+    );
+    fn set_priority_low(&self, callback: &nsIBitsCallback) -> Result<(), BitsTaskError> {
+        self.set_priority(Priority::Low, callback)
+    }
+
+    fn set_priority(
+        &self,
+        priority: Priority,
+        callback: &nsIBitsCallback,
+    ) -> Result<(), BitsTaskError> {
+        if self.request_has_transferred() {
+            return Err(BitsTaskError::new(
+                TransferAlreadyComplete,
+                Action::SetPriority,
+                Pretask,
+            ));
+        }
+
+        let task: Box<SetPriorityTask> = Box::new(SetPriorityTask::new(
+            RefPtr::new(self),
+            self.bits_id.clone(),
+            priority,
+            RefPtr::new(callback),
+        ));
+
+        self.bits_service.dispatch_runnable_to_command_thread(
+            task,
+            "BitsRequest::set_priority",
+            Action::SetPriority,
+        )
+    }
+
+    nsIBitsRequest_method!(
+        [Action::Complete]
+        complete => Complete()
+    );
+    fn complete(&self, callback: &nsIBitsCallback) -> Result<(), BitsTaskError> {
+        if self.request_has_completed() {
+            return Err(BitsTaskError::new(
+                TransferAlreadyComplete,
+                Action::Complete,
+                Pretask,
+            ));
+        }
+
+        let task: Box<CompleteTask> = Box::new(CompleteTask::new(
+            RefPtr::new(self),
+            self.bits_id.clone(),
+            RefPtr::new(callback),
+        ));
+
+        self.bits_service.dispatch_runnable_to_command_thread(
+            task,
+            "BitsRequest::complete",
+            Action::Complete,
+        )
+    }
+
+    nsIBitsRequest_method!(
+        [Action::Suspend]
+        suspend_nsIBitsRequest => SuspendAsync()
+    );
+    #[allow(non_snake_case)]
+    fn suspend_nsIBitsRequest(&self, callback: &nsIBitsCallback) -> Result<(), BitsTaskError> {
+        self.suspend(Some(RefPtr::new(callback)))
+    }
+    xpcom_method!(
+        suspend_nsIRequest => Suspend()
+    );
+    #[allow(non_snake_case)]
+    fn suspend_nsIRequest(&self) -> Result<(), BitsTaskError> {
+        self.suspend(None)
+    }
+
+    fn suspend(&self, callback: Option<RefPtr<nsIBitsCallback>>) -> Result<(), BitsTaskError> {
+        if self.request_has_transferred() {
+            return Err(BitsTaskError::new(
+                TransferAlreadyComplete,
+                Action::Suspend,
+                Pretask,
+            ));
+        }
+
+        let task: Box<SuspendTask> = Box::new(SuspendTask::new(
+            RefPtr::new(self),
+            self.bits_id.clone(),
+            callback,
+        ));
+
+        self.bits_service.dispatch_runnable_to_command_thread(
+            task,
+            "BitsRequest::suspend",
+            Action::Suspend,
+        )
+    }
+
+    nsIBitsRequest_method!(
+        [Action::Resume]
+        resume_nsIBitsRequest => ResumeAsync()
+    );
+    #[allow(non_snake_case)]
+    fn resume_nsIBitsRequest(&self, callback: &nsIBitsCallback) -> Result<(), BitsTaskError> {
+        self.resume(Some(RefPtr::new(callback)))
+    }
+    xpcom_method!(
+        resume_nsIRequest => Resume()
+    );
+    #[allow(non_snake_case)]
+    fn resume_nsIRequest(&self) -> Result<(), BitsTaskError> {
+        self.resume(None)
+    }
+
+    fn resume(&self, callback: Option<RefPtr<nsIBitsCallback>>) -> Result<(), BitsTaskError> {
+        if self.request_has_transferred() {
+            return Err(BitsTaskError::new(
+                TransferAlreadyComplete,
+                Action::Resume,
+                Pretask,
+            ));
+        }
+
+        let task: Box<ResumeTask> = Box::new(ResumeTask::new(
+            RefPtr::new(self),
+            self.bits_id.clone(),
+            callback,
+        ));
+
+        self.bits_service.dispatch_runnable_to_command_thread(
+            task,
+            "BitsRequest::resume",
+            Action::Resume,
+        )
+    }
+
+    /**
+     * As stated in nsIBits.idl, nsIBits interfaces are not expected to
+     * implement the loadGroup or loadFlags attributes. This implementation
+     * provides only null implementations only for these methods.
+     */
+    xpcom_method!(
+        get_load_group => GetLoadGroup() -> *const nsILoadGroup
+    );
+    fn get_load_group(&self) -> Result<RefPtr<nsILoadGroup>, nsresult> {
+        Err(NS_ERROR_NOT_IMPLEMENTED)
+    }
+
+    xpcom_method!(
+        set_load_group => SetLoadGroup(_load_group: *const nsILoadGroup)
+    );
+    fn set_load_group(&self, _load_group: &nsILoadGroup) -> Result<(), nsresult> {
+        Err(NS_ERROR_NOT_IMPLEMENTED)
+    }
+
+    xpcom_method!(
+        get_load_flags => GetLoadFlags() -> nsLoadFlags
+    );
+    fn get_load_flags(&self) -> Result<nsLoadFlags, nsresult> {
+        Err(NS_ERROR_NOT_IMPLEMENTED)
+    }
+
+    xpcom_method!(
+        set_load_flags => SetLoadFlags(_load_flags: nsLoadFlags)
+    );
+    fn set_load_flags(&self, _load_flags: nsLoadFlags) -> Result<(), nsresult> {
+        Err(NS_ERROR_NOT_IMPLEMENTED)
+    }
+}
+
+impl Drop for BitsRequest {
+    fn drop(&mut self) {
+        // Make sure that the monitor thread gets cleaned up.
+        self.shutdown_monitor_thread();
+        // Make sure we tell BitsService that we are done with the command thread.
+        self.on_finished();
+    }
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/bitsdownload/src/bits_interface/string.rs
@@ -0,0 +1,80 @@
+/* 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 super::{
+    action::Action,
+    error::{BitsTaskError, ErrorStage, ErrorType},
+};
+
+use bits_client::Guid;
+use nsstring::nsCString;
+use std::ffi::OsString;
+use std::{str, str::FromStr};
+
+/// This function is fallible, and the consumers would prefer a BitsTaskError
+/// in the failure case. To facilitate that, this function takes some params
+/// that give it the data necessary to construct the BitsTaskError if it fails.
+/// If it succeeds, those values will be unused.
+#[allow(non_snake_case)]
+pub fn nsCString_to_String(
+    value: &nsCString,
+    error_action: Action,
+    error_stage: ErrorStage,
+) -> Result<String, BitsTaskError> {
+    match String::from_utf8(value[..].to_vec()) {
+        Ok(s) => Ok(s),
+        Err(_) => Err(BitsTaskError::new(
+            ErrorType::NoUtf8Conversion,
+            error_action,
+            error_stage,
+        )),
+    }
+}
+
+/// This function is fallible, and the consumers would prefer a BitsTaskError
+/// in the failure case. To facilitate that, this function takes some params
+/// that give it the data necessary to construct the BitsTaskError if it fails.
+/// If it succeeds, those values will be unused.
+#[allow(non_snake_case)]
+pub fn nsCString_to_OsString(
+    value: &nsCString,
+    error_action: Action,
+    error_stage: ErrorStage,
+) -> Result<OsString, BitsTaskError> {
+    Ok(OsString::from(nsCString_to_String(
+        value,
+        error_action,
+        error_stage,
+    )?))
+}
+
+/// This function is fallible, and the consumers would prefer a BitsTaskError
+/// in the failure case. To facilitate that, this function takes some params
+/// that give it the data necessary to construct the BitsTaskError if it fails.
+/// If it succeeds, those values will be unused.
+#[allow(non_snake_case)]
+pub fn Guid_from_nsCString(
+    value: &nsCString,
+    error_action: Action,
+    error_stage: ErrorStage,
+) -> Result<Guid, BitsTaskError> {
+    let vector = &value[..].to_vec();
+    let string = match str::from_utf8(vector) {
+        Ok(s) => s,
+        Err(_) => {
+            return Err(BitsTaskError::new(
+                ErrorType::NoUtf8Conversion,
+                error_action,
+                error_stage,
+            ));
+        }
+    };
+    Guid::from_str(string).map_err(|comedy_error| {
+        BitsTaskError::from_comedy(
+            ErrorType::InvalidGuid,
+            error_action,
+            error_stage,
+            comedy_error,
+        )
+    })
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/bitsdownload/src/bits_interface/task/client.rs
@@ -0,0 +1,102 @@
+/* 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 super::{
+    action::Action,
+    error::{BitsTaskError, ErrorStage::CommandThread, ErrorType::MissingBitsClient},
+    string::nsCString_to_OsString,
+};
+
+use bits_client::BitsClient;
+use nsstring::nsCString;
+use std::cell::Cell;
+
+thread_local! {
+    // This is used to store the `BitsClient` on the Command thread.
+    // Keeping it here solves the problem of how to allow multiple runnables to
+    // be simultaneously queued on the Command thread while giving them all
+    // access to the same `BitsClient`.
+    static BITS_CLIENT: Cell<Option<BitsClient>> = Cell::new(None);
+}
+
+/// This structure holds the data needed to initialize `BitsClient` and
+/// `BitsMonitorClient`.
+#[derive(Debug, Clone)]
+pub struct ClientInitData {
+    pub job_name: nsCString,
+    pub save_path_prefix: nsCString,
+    pub monitor_timeout_ms: u32,
+}
+
+impl ClientInitData {
+    pub fn new(
+        job_name: nsCString,
+        save_path_prefix: nsCString,
+        monitor_timeout_ms: u32,
+    ) -> ClientInitData {
+        ClientInitData {
+            job_name,
+            save_path_prefix,
+            monitor_timeout_ms,
+        }
+    }
+}
+
+/// This function constructs a `BitsClient`, if one does not already exist. If
+/// the `BitsClient` cannot be constructed, a `BitsTaskError` will be returned.
+/// If the `BitsClient` could be obtained, then the function then calls the
+/// closure passed to it, passing a mutable reference to the `BitsClient`.
+/// This function will then return whatever the closure returned, which must be
+/// a `Result<_, BitsTaskError>`.
+pub fn with_maybe_new_bits_client<F, R>(
+    init_data: &ClientInitData,
+    action: Action,
+    closure: F,
+) -> Result<R, BitsTaskError>
+where
+    F: FnOnce(&mut BitsClient) -> Result<R, BitsTaskError>,
+{
+    _with_bits_client(Some(init_data), action, closure)
+}
+
+/// This function assumes that a `BitsClient` has already been constructed. If
+/// there is not one available, a `BitsTaskError` will be returned. Otherwise,
+/// the function calls the closure passed to it, passing a mutable reference to
+/// the `BitsClient`. This function will then return whatever the closure
+/// returned, which must be a `Result<_, BitsTaskError>`.
+pub fn with_bits_client<F, R>(action: Action, closure: F) -> Result<R, BitsTaskError>
+where
+    F: FnOnce(&mut BitsClient) -> Result<R, BitsTaskError>,
+{
+    _with_bits_client(None, action, closure)
+}
+
+fn _with_bits_client<F, R>(
+    maybe_init_data: Option<&ClientInitData>,
+    action: Action,
+    closure: F,
+) -> Result<R, BitsTaskError>
+where
+    F: FnOnce(&mut BitsClient) -> Result<R, BitsTaskError>,
+{
+    BITS_CLIENT.with(|cell| {
+        let maybe_client = cell.take();
+        let mut client = match (maybe_client, maybe_init_data) {
+            (Some(r), _) => r,
+            (None, Some(init_data)) => {
+                // Immediately invoked function to allow for the ? operator
+                BitsClient::new(
+                    nsCString_to_OsString(&init_data.job_name, action, CommandThread)?,
+                    nsCString_to_OsString(&init_data.save_path_prefix, action, CommandThread)?,
+                )
+                .map_err(|pipe_error| BitsTaskError::from_pipe(action, pipe_error))?
+            }
+            (None, None) => {
+                return Err(BitsTaskError::new(MissingBitsClient, action, CommandThread));
+            }
+        };
+        let result = closure(&mut client);
+        cell.set(Some(client));
+        result
+    })
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/bitsdownload/src/bits_interface/task/from_threadbound.rs
@@ -0,0 +1,125 @@
+/* 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 super::{
+    action::Action,
+    error::{BitsTaskError, ErrorStage, ErrorType},
+};
+use log::warn;
+use xpcom::{RefCounted, ThreadBoundRefPtr};
+
+#[derive(Debug, PartialEq, Clone, Copy)]
+pub enum DataType {
+    Callback,
+    BitsService,
+    BitsRequest,
+    Observer,
+    Context,
+}
+
+#[derive(Debug, PartialEq, Clone, Copy)]
+enum GetThreadboundError {
+    Missing,
+    WrongThread,
+}
+
+impl DataType {
+    fn error_type(&self, error: GetThreadboundError) -> ErrorType {
+        match self {
+            DataType::Callback => match error {
+                GetThreadboundError::Missing => ErrorType::MissingCallback,
+                GetThreadboundError::WrongThread => ErrorType::CallbackOnWrongThread,
+            },
+            DataType::BitsService => match error {
+                GetThreadboundError::Missing => ErrorType::MissingBitsService,
+                GetThreadboundError::WrongThread => ErrorType::BitsServiceOnWrongThread,
+            },
+            DataType::BitsRequest => match error {
+                GetThreadboundError::Missing => ErrorType::MissingBitsRequest,
+                GetThreadboundError::WrongThread => ErrorType::BitsRequestOnWrongThread,
+            },
+            DataType::Observer => match error {
+                GetThreadboundError::Missing => ErrorType::MissingObserver,
+                GetThreadboundError::WrongThread => ErrorType::ObserverOnWrongThread,
+            },
+            DataType::Context => match error {
+                GetThreadboundError::Missing => ErrorType::MissingContext,
+                GetThreadboundError::WrongThread => ErrorType::ContextOnWrongThread,
+            },
+        }
+    }
+
+    fn name(&self) -> &'static str {
+        match self {
+            DataType::Callback => "Callback",
+            DataType::BitsService => "BITS Service",
+            DataType::BitsRequest => "BITS Request",
+            DataType::Observer => "Observer",
+            DataType::Context => "Context",
+        }
+    }
+}
+
+/// Given a reference to a threadbound option
+/// (i.e. `&Option<ThreadBoundRefPtr<_>>`), this function will attempt to
+/// retrieve a reference to the value stored within. If it is not available
+/// (option is `None` or value is on the wrong thread), `None` is returned
+/// instead.
+pub fn get_from_threadbound_option<T>(
+    maybe_threadbound: &Option<ThreadBoundRefPtr<T>>,
+    data_type: DataType,
+    action: Action,
+) -> Option<&T>
+where
+    T: RefCounted + 'static,
+{
+    maybe_threadbound.as_ref().and_then(|threadbound| {
+        let maybe_reference = threadbound.get_ref();
+        if maybe_reference.is_none() {
+            warn!(
+                "Unexpected error {}: {} is on the wrong thread",
+                action.description(),
+                data_type.name(),
+            );
+        }
+        maybe_reference
+    })
+}
+
+/// Given a reference to a threadbound option
+/// (i.e. `&Option<ThreadBoundRefPtr<_>>`), this function will attempt to
+/// retrieve a reference to the value stored within. If it is not available
+/// (option is `None` or value is on the wrong thread), a `BitsTaskError` is
+/// returned instead.
+pub fn expect_from_threadbound_option<T>(
+    maybe_threadbound: &Option<ThreadBoundRefPtr<T>>,
+    data_type: DataType,
+    action: Action,
+) -> Result<&T, BitsTaskError>
+where
+    T: RefCounted + 'static,
+{
+    match maybe_threadbound.as_ref() {
+        Some(threadbound) => {
+            match threadbound.get_ref() {
+                Some(reference) => Ok(reference),
+                None => Err(BitsTaskError::new(
+                    data_type.error_type(GetThreadboundError::WrongThread),
+                    action,
+                    // Retrieving data from threadbounds all happens on the main thread.
+                    // No data is ever bound to other threads so there would be no
+                    // reason to retrieve it there.
+                    ErrorStage::MainThread,
+                )),
+            }
+        }
+        None => Err(BitsTaskError::new(
+            data_type.error_type(GetThreadboundError::Missing),
+            action,
+            // Retrieving data from threadbounds all happens on the main thread.
+            // No data is ever bound to other threads so there would be no
+            // reason to retrieve it there.
+            ErrorStage::MainThread,
+        )),
+    }
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/bitsdownload/src/bits_interface/task/mod.rs
@@ -0,0 +1,18 @@
+/* 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/. */
+mod from_threadbound;
+
+use super::{action, dispatch_callback, error, request::BitsRequest, string, BitsService};
+
+mod client;
+pub use self::client::ClientInitData;
+
+mod service_task;
+pub use self::service_task::{MonitorDownloadTask, StartDownloadTask};
+
+mod request_task;
+pub use self::request_task::{
+    CancelTask, ChangeMonitorIntervalTask, CompleteTask, Priority, ResumeTask, SetPriorityTask,
+    SuspendTask,
+};
new file mode 100644
--- /dev/null
+++ b/toolkit/components/bitsdownload/src/bits_interface/task/request_task.rs
@@ -0,0 +1,384 @@
+/* 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 super::{
+    action::{Action, RequestAction},
+    client::with_bits_client,
+    dispatch_callback::{
+        maybe_dispatch_via_callback, CallbackExpected, CallbackOptional, IsCallbackExpected,
+    },
+    error::BitsTaskError,
+    from_threadbound::{expect_from_threadbound_option, DataType},
+    BitsRequest,
+};
+
+use bits_client::{BitsClient, Guid};
+use crossbeam_utils::atomic::AtomicCell;
+use log::info;
+use moz_task::Task;
+use nserror::nsresult;
+use xpcom::{interfaces::nsIBitsCallback, RefPtr, ThreadBoundRefPtr};
+
+type RunFn<D> = fn(Guid, &D, &mut BitsClient) -> Result<(), BitsTaskError>;
+type DoneFn = fn(&BitsRequest, bool) -> Result<(), BitsTaskError>;
+
+pub struct RequestTask<D> {
+    request: AtomicCell<Option<ThreadBoundRefPtr<BitsRequest>>>,
+    guid: Guid,
+    action: RequestAction,
+    task_data: D,
+    run_fn: RunFn<D>,
+    maybe_done_fn: Option<DoneFn>,
+    callback: AtomicCell<Option<ThreadBoundRefPtr<nsIBitsCallback>>>,
+    callback_presence: IsCallbackExpected,
+    result: AtomicCell<Option<Result<(), BitsTaskError>>>,
+}
+
+impl<D> RequestTask<D>
+where
+    D: Sync + Send,
+{
+    pub fn new(
+        request: RefPtr<BitsRequest>,
+        guid: Guid,
+        action: RequestAction,
+        task_data: D,
+        run_fn: RunFn<D>,
+        maybe_done_fn: Option<DoneFn>,
+        callback: Option<RefPtr<nsIBitsCallback>>,
+        callback_presence: IsCallbackExpected,
+    ) -> RequestTask<D> {
+        RequestTask {
+            request: AtomicCell::new(Some(ThreadBoundRefPtr::new(request))),
+            guid,
+            action,
+            task_data,
+            run_fn,
+            maybe_done_fn,
+            callback: AtomicCell::new(callback.map(ThreadBoundRefPtr::new)),
+            result: AtomicCell::new(None),
+            callback_presence,
+        }
+    }
+}
+
+impl<D> Task for RequestTask<D> {
+    fn run(&self) {
+        let result = with_bits_client(self.action.into(), |client| {
+            (self.run_fn)(self.guid.clone(), &self.task_data, client)
+        });
+        self.result.store(Some(result));
+    }
+
+    fn done(&self) -> Result<(), nsresult> {
+        // If TaskRunnable.run() calls Task.done() to return a result
+        // on the main thread before TaskRunnable.run() returns on the worker
+        // thread, then the Task will get dropped on the worker thread.
+        //
+        // But the callback is an nsXPCWrappedJS that isn't safe to release
+        // on the worker thread.  So we move it out of the Task here to ensure
+        // it gets released on the main thread.
+        let maybe_tb_callback = self.callback.swap(None);
+        // It also isn't safe to drop the BitsRequest RefPtr off-thread,
+        // because BitsRequest refcounting is non-atomic
+        let maybe_tb_request = self.request.swap(None);
+
+        let action: Action = self.action.into();
+        let maybe_callback =
+            expect_from_threadbound_option(&maybe_tb_callback, DataType::Callback, action);
+
+        // Immediately invoked function expression to allow for the ? operator
+        let result: Result<(), BitsTaskError> = (|| {
+            let request =
+                expect_from_threadbound_option(&maybe_tb_request, DataType::BitsRequest, action)?;
+
+            let maybe_result = self.result.swap(None);
+
+            let success = if let Some(result) = maybe_result.as_ref() {
+                result.is_ok()
+            } else {
+                false
+            };
+
+            if let Some(done_fn) = self.maybe_done_fn {
+                done_fn(request, success)?;
+            }
+
+            maybe_result.ok_or_else(|| BitsTaskError::missing_result(action))?
+        })();
+        info!("BITS Request Task completed: {:?}", result);
+        maybe_dispatch_via_callback(result, maybe_callback, self.callback_presence)
+    }
+}
+
+pub struct CompleteTask(RequestTask<()>);
+
+impl Task for CompleteTask {
+    fn run(&self) {
+        self.0.run();
+    }
+
+    fn done(&self) -> Result<(), nsresult> {
+        self.0.done()
+    }
+}
+
+impl CompleteTask {
+    pub fn new(
+        request: RefPtr<BitsRequest>,
+        id: Guid,
+        callback: RefPtr<nsIBitsCallback>,
+    ) -> CompleteTask {
+        CompleteTask(RequestTask::new(
+            request,
+            id,
+            RequestAction::Complete,
+            (),
+            CompleteTask::run_fn,
+            Some(CompleteTask::done_fn),
+            Some(callback),
+            CallbackExpected,
+        ))
+    }
+
+    fn run_fn(id: Guid, _data: &(), client: &mut BitsClient) -> Result<(), BitsTaskError> {
+        client
+            .complete_job(id)
+            .map_err(|pipe_error| BitsTaskError::from_pipe(Action::Complete, pipe_error))??;
+        Ok(())
+    }
+
+    fn done_fn(request: &BitsRequest, success: bool) -> Result<(), BitsTaskError> {
+        if success {
+            request.on_finished();
+        }
+        Ok(())
+    }
+}
+
+pub struct CancelTask(RequestTask<()>);
+
+impl Task for CancelTask {
+    fn run(&self) {
+        self.0.run();
+    }
+
+    fn done(&self) -> Result<(), nsresult> {
+        self.0.done()
+    }
+}
+
+impl CancelTask {
+    pub fn new(
+        request: RefPtr<BitsRequest>,
+        id: Guid,
+        callback: Option<RefPtr<nsIBitsCallback>>,
+    ) -> CancelTask {
+        let callback_presence = if callback.is_some() {
+            CallbackExpected
+        } else {
+            CallbackOptional
+        };
+
+        CancelTask(RequestTask::new(
+            request,
+            id,
+            RequestAction::Cancel,
+            (),
+            CancelTask::run_fn,
+            Some(CancelTask::done_fn),
+            callback,
+            callback_presence,
+        ))
+    }
+
+    fn run_fn(id: Guid, _data: &(), client: &mut BitsClient) -> Result<(), BitsTaskError> {
+        client
+            .cancel_job(id)
+            .map_err(|pipe_error| BitsTaskError::from_pipe(Action::Cancel, pipe_error))??;
+        Ok(())
+    }
+
+    fn done_fn(request: &BitsRequest, success: bool) -> Result<(), BitsTaskError> {
+        request.finish_cancel_action(success);
+        Ok(())
+    }
+}
+
+pub struct SuspendTask(RequestTask<()>);
+
+impl Task for SuspendTask {
+    fn run(&self) {
+        self.0.run();
+    }
+
+    fn done(&self) -> Result<(), nsresult> {
+        self.0.done()
+    }
+}
+
+impl SuspendTask {
+    pub fn new(
+        request: RefPtr<BitsRequest>,
+        id: Guid,
+        callback: Option<RefPtr<nsIBitsCallback>>,
+    ) -> SuspendTask {
+        let callback_presence = if callback.is_some() {
+            CallbackExpected
+        } else {
+            CallbackOptional
+        };
+
+        SuspendTask(RequestTask::new(
+            request,
+            id,
+            RequestAction::Suspend,
+            (),
+            SuspendTask::run_fn,
+            None,
+            callback,
+            callback_presence,
+        ))
+    }
+
+    fn run_fn(id: Guid, _data: &(), client: &mut BitsClient) -> Result<(), BitsTaskError> {
+        client
+            .suspend_job(id)
+            .map_err(|pipe_error| BitsTaskError::from_pipe(Action::Suspend, pipe_error))??;
+        Ok(())
+    }
+}
+
+pub struct ResumeTask(RequestTask<()>);
+
+impl Task for ResumeTask {
+    fn run(&self) {
+        self.0.run();
+    }
+
+    fn done(&self) -> Result<(), nsresult> {
+        self.0.done()
+    }
+}
+
+impl ResumeTask {
+    pub fn new(
+        request: RefPtr<BitsRequest>,
+        id: Guid,
+        callback: Option<RefPtr<nsIBitsCallback>>,
+    ) -> ResumeTask {
+        let callback_presence = if callback.is_some() {
+            CallbackExpected
+        } else {
+            CallbackOptional
+        };
+
+        ResumeTask(RequestTask::new(
+            request,
+            id,
+            RequestAction::Resume,
+            (),
+            ResumeTask::run_fn,
+            None,
+            callback,
+            callback_presence,
+        ))
+    }
+
+    fn run_fn(id: Guid, _data: &(), client: &mut BitsClient) -> Result<(), BitsTaskError> {
+        client
+            .resume_job(id)
+            .map_err(|pipe_error| BitsTaskError::from_pipe(Action::Resume, pipe_error))??;
+        Ok(())
+    }
+}
+
+pub struct ChangeMonitorIntervalTask(RequestTask<u32>);
+
+impl Task for ChangeMonitorIntervalTask {
+    fn run(&self) {
+        self.0.run();
+    }
+
+    fn done(&self) -> Result<(), nsresult> {
+        self.0.done()
+    }
+}
+
+impl ChangeMonitorIntervalTask {
+    pub fn new(
+        request: RefPtr<BitsRequest>,
+        id: Guid,
+        update_interval_ms: u32,
+        callback: RefPtr<nsIBitsCallback>,
+    ) -> ChangeMonitorIntervalTask {
+        ChangeMonitorIntervalTask(RequestTask::new(
+            request,
+            id,
+            RequestAction::SetMonitorInterval,
+            update_interval_ms,
+            ChangeMonitorIntervalTask::run_fn,
+            None,
+            Some(callback),
+            CallbackExpected,
+        ))
+    }
+
+    fn run_fn(
+        id: Guid,
+        update_interval_ms: &u32,
+        client: &mut BitsClient,
+    ) -> Result<(), BitsTaskError> {
+        client
+            .set_update_interval(id, *update_interval_ms)
+            .map_err(|pipe_error| {
+                BitsTaskError::from_pipe(Action::SetMonitorInterval, pipe_error)
+            })??;
+        Ok(())
+    }
+}
+
+#[derive(Debug, PartialEq, Clone, Copy)]
+pub enum Priority {
+    High,
+    Low,
+}
+
+pub struct SetPriorityTask(RequestTask<Priority>);
+
+impl Task for SetPriorityTask {
+    fn run(&self) {
+        self.0.run();
+    }
+
+    fn done(&self) -> Result<(), nsresult> {
+        self.0.done()
+    }
+}
+
+impl SetPriorityTask {
+    pub fn new(
+        request: RefPtr<BitsRequest>,
+        id: Guid,
+        priority: Priority,
+        callback: RefPtr<nsIBitsCallback>,
+    ) -> SetPriorityTask {
+        SetPriorityTask(RequestTask::new(
+            request,
+            id,
+            RequestAction::SetPriority,
+            priority,
+            SetPriorityTask::run_fn,
+            None,
+            Some(callback),
+            CallbackExpected,
+        ))
+    }
+
+    fn run_fn(id: Guid, priority: &Priority, client: &mut BitsClient) -> Result<(), BitsTaskError> {
+        client
+            .set_job_priority(id, *priority == Priority::High)
+            .map_err(|pipe_error| BitsTaskError::from_pipe(Action::SetPriority, pipe_error))??;
+        Ok(())
+    }
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/bitsdownload/src/bits_interface/task/service_task.rs
@@ -0,0 +1,323 @@
+/* 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 super::{
+    action::{
+        Action,
+        Action::{MonitorDownload, StartDownload},
+        ServiceAction,
+    },
+    client::{with_maybe_new_bits_client, ClientInitData},
+    dispatch_callback::{maybe_dispatch_request_via_callback, CallbackExpected},
+    error::{BitsTaskError, ErrorStage::CommandThread},
+    from_threadbound::{expect_from_threadbound_option, get_from_threadbound_option, DataType},
+    string::nsCString_to_OsString,
+    BitsRequest, BitsService,
+};
+
+use bits_client::{BitsClient, BitsMonitorClient, BitsProxyUsage, Guid};
+use crossbeam_utils::atomic::AtomicCell;
+use log::{info, warn};
+use moz_task::Task;
+use nserror::nsresult;
+use nsstring::nsCString;
+use xpcom::{
+    interfaces::{nsIBitsNewRequestCallback, nsIRequestObserver, nsISupports},
+    RefPtr, ThreadBoundRefPtr,
+};
+
+// D is the Data Type that the RunFn function needs to make S.
+// S is the Success Type that the RunFn returns on success and that the
+//   DoneFn needs to make the BitsRequest.
+type RunFn<D, S> = fn(&D, &mut BitsClient) -> Result<S, BitsTaskError>;
+type DoneFn<D, S> = fn(
+    &D,
+    S,
+    &ClientInitData,
+    &BitsService,
+    &nsIRequestObserver,
+    Option<&nsISupports>,
+) -> Result<RefPtr<BitsRequest>, BitsTaskError>;
+
+pub struct ServiceTask<D, S> {
+    client_init_data: ClientInitData,
+    action: ServiceAction,
+    task_data: D,
+    run_fn: RunFn<D, S>,
+    done_fn: DoneFn<D, S>,
+    bits_service: AtomicCell<Option<ThreadBoundRefPtr<BitsService>>>,
+    observer: AtomicCell<Option<ThreadBoundRefPtr<nsIRequestObserver>>>,
+    context: AtomicCell<Option<ThreadBoundRefPtr<nsISupports>>>,
+    callback: AtomicCell<Option<ThreadBoundRefPtr<nsIBitsNewRequestCallback>>>,
+    result: AtomicCell<Option<Result<S, BitsTaskError>>>,
+}
+
+impl<D, S> ServiceTask<D, S>
+where
+    D: Sync + Send,
+    S: Sync + Send,
+{
+    pub fn new(
+        client_init_data: ClientInitData,
+        action: ServiceAction,
+        task_data: D,
+        run_fn: RunFn<D, S>,
+        done_fn: DoneFn<D, S>,
+        bits_service: RefPtr<BitsService>,
+        observer: RefPtr<nsIRequestObserver>,
+        context: Option<RefPtr<nsISupports>>,
+        callback: RefPtr<nsIBitsNewRequestCallback>,
+    ) -> ServiceTask<D, S> {
+        ServiceTask {
+            client_init_data,
+            action,
+            task_data,
+            run_fn,
+            done_fn,
+            bits_service: AtomicCell::new(Some(ThreadBoundRefPtr::new(bits_service))),
+            observer: AtomicCell::new(Some(ThreadBoundRefPtr::new(observer))),
+            context: AtomicCell::new(context.map(ThreadBoundRefPtr::new)),
+            callback: AtomicCell::new(Some(ThreadBoundRefPtr::new(callback))),
+            result: AtomicCell::new(None),
+        }
+    }
+}
+
+impl<D, S> Task for ServiceTask<D, S> {
+    fn run(&self) {
+        let result =
+            with_maybe_new_bits_client(&self.client_init_data, self.action.into(), |client| {
+                (self.run_fn)(&self.task_data, client)
+            });
+        self.result.store(Some(result));
+    }
+
+    fn done(&self) -> Result<(), nsresult> {
+        // If TaskRunnable.run() calls Task.done() to return a result
+        // on the main thread before TaskRunnable.run() returns on the worker
+        // thread, then the Task will get dropped on the worker thread.
+        //
+        // But the callback is an nsXPCWrappedJS that isn't safe to release
+        // on the worker thread.  So we move it out of the Task here to ensure
+        // it gets released on the main thread.
+        let maybe_tb_callback = self.callback.swap(None);
+        // It also isn't safe to drop the BitsService RefPtr off-thread,
+        // because BitsService refcounting is non-atomic
+        let maybe_tb_service = self.bits_service.swap(None);
+        // The observer and context are also an nsXPCWrappedJS that aren't safe
+        // to release on the worker thread.
+        let maybe_tb_observer = self.observer.swap(None);
+        let maybe_tb_context = self.context.swap(None);
+
+        let action: Action = self.action.into();
+        let maybe_callback =
+            expect_from_threadbound_option(&maybe_tb_callback, DataType::Callback, action);
+
+        // Immediately invoked function expression to allow for the ? operator
+        let result: Result<RefPtr<BitsRequest>, BitsTaskError> = (|| {
+            let bits_service =
+                expect_from_threadbound_option(&maybe_tb_service, DataType::BitsService, action)?;
+            let observer =
+                expect_from_threadbound_option(&maybe_tb_observer, DataType::Observer, action)?;
+            let maybe_context =
+                get_from_threadbound_option(&maybe_tb_context, DataType::Context, action);
+            let success = self
+                .result
+                .swap(None)
+                .ok_or_else(|| BitsTaskError::missing_result(action))??;
+
+            (self.done_fn)(
+                &self.task_data,
+                success,
+                &self.client_init_data,
+                bits_service,
+                observer,
+                maybe_context,
+            )
+        })();
+        info!("BITS Interface Task completed: {:?}", result);
+        // We incremented the request count when we dispatched an action to
+        // start the job. Now we will decrement since the action completed.
+        // See the declaration of InitBitsService::request_count for details.
+        let bits_service_result =
+            expect_from_threadbound_option(&maybe_tb_service, DataType::BitsService, action);
+        match bits_service_result {
+            Ok(bits_service) => {
+                bits_service.dec_request_count();
+            }
+            Err(error) => {
+                warn!(
+                    concat!(
+                        "Unable to decrement the request count when finishing ServiceTask. ",
+                        "The command thread may not be shut down. Error: {:?}"
+                    ),
+                    error
+                );
+            }
+        }
+
+        maybe_dispatch_request_via_callback(result, maybe_callback, CallbackExpected)
+    }
+}
+
+struct StartDownloadData {
+    download_url: nsCString,
+    save_rel_path: nsCString,
+    proxy: BitsProxyUsage,
+    update_interval_ms: u32,
+}
+
+struct StartDownloadSuccess {
+    guid: Guid,
+    monitor_client: BitsMonitorClient,
+}
+
+pub struct StartDownloadTask(ServiceTask<StartDownloadData, StartDownloadSuccess>);
+
+impl Task for StartDownloadTask {
+    fn run(&self) {
+        self.0.run();
+    }
+
+    fn done(&self) -> Result<(), nsresult> {
+        self.0.done()
+    }
+}
+
+impl StartDownloadTask {
+    pub fn new(
+        client_init_data: ClientInitData,
+        download_url: nsCString,
+        save_rel_path: nsCString,
+        proxy: BitsProxyUsage,
+        update_interval_ms: u32,
+        bits_service: RefPtr<BitsService>,
+        observer: RefPtr<nsIRequestObserver>,
+        context: Option<RefPtr<nsISupports>>,
+        callback: RefPtr<nsIBitsNewRequestCallback>,
+    ) -> StartDownloadTask {
+        StartDownloadTask(ServiceTask::new(
+            client_init_data,
+            ServiceAction::StartDownload,
+            StartDownloadData {
+                download_url,
+                save_rel_path,
+                proxy,
+                update_interval_ms,
+            },
+            StartDownloadTask::run_fn,
+            StartDownloadTask::done_fn,
+            bits_service,
+            observer,
+            context,
+            callback,
+        ))
+    }
+
+    fn run_fn(
+        data: &StartDownloadData,
+        client: &mut BitsClient,
+    ) -> Result<StartDownloadSuccess, BitsTaskError> {
+        let url = nsCString_to_OsString(&data.download_url, StartDownload, CommandThread)?;
+        let path = nsCString_to_OsString(&data.save_rel_path, StartDownload, CommandThread)?;
+        let (success, monitor_client) = client
+            .start_job(url, path, data.proxy, data.update_interval_ms)
+            .map_err(|pipe_error| BitsTaskError::from_pipe(StartDownload, pipe_error))??;
+        Ok(StartDownloadSuccess {
+            guid: success.guid,
+            monitor_client,
+        })
+    }
+
+    fn done_fn(
+        _data: &StartDownloadData,
+        success: StartDownloadSuccess,
+        client_init_data: &ClientInitData,
+        bits_service: &BitsService,
+        observer: &nsIRequestObserver,
+        maybe_context: Option<&nsISupports>,
+    ) -> Result<RefPtr<BitsRequest>, BitsTaskError> {
+        BitsRequest::new(
+            success.guid.clone(),
+            RefPtr::new(bits_service),
+            client_init_data.monitor_timeout_ms,
+            RefPtr::new(&observer),
+            maybe_context.map(RefPtr::new),
+            success.monitor_client,
+            ServiceAction::StartDownload,
+        )
+    }
+}
+
+struct MonitorDownloadData {
+    guid: Guid,
+    update_interval_ms: u32,
+}
+
+pub struct MonitorDownloadTask(ServiceTask<MonitorDownloadData, BitsMonitorClient>);
+
+impl Task for MonitorDownloadTask {
+    fn run(&self) {
+        self.0.run();
+    }
+
+    fn done(&self) -> Result<(), nsresult> {
+        self.0.done()
+    }
+}
+
+impl MonitorDownloadTask {
+    pub fn new(
+        client_init_data: ClientInitData,
+        guid: Guid,
+        update_interval_ms: u32,
+        bits_service: RefPtr<BitsService>,
+        observer: RefPtr<nsIRequestObserver>,
+        context: Option<RefPtr<nsISupports>>,
+        callback: RefPtr<nsIBitsNewRequestCallback>,
+    ) -> MonitorDownloadTask {
+        MonitorDownloadTask(ServiceTask::new(
+            client_init_data,
+            ServiceAction::MonitorDownload,
+            MonitorDownloadData {
+                guid,
+                update_interval_ms,
+            },
+            MonitorDownloadTask::run_fn,
+            MonitorDownloadTask::done_fn,
+            bits_service,
+            observer,
+            context,
+            callback,
+        ))
+    }
+
+    fn run_fn(
+        data: &MonitorDownloadData,
+        client: &mut BitsClient,
+    ) -> Result<BitsMonitorClient, BitsTaskError> {
+        let result = client
+            .monitor_job(data.guid.clone(), data.update_interval_ms)
+            .map_err(|pipe_error| BitsTaskError::from_pipe(MonitorDownload, pipe_error));
+        Ok(result??)
+    }
+
+    fn done_fn(
+        data: &MonitorDownloadData,
+        monitor_client: BitsMonitorClient,
+        client_init_data: &ClientInitData,
+        bits_service: &BitsService,
+        observer: &nsIRequestObserver,
+        maybe_context: Option<&nsISupports>,
+    ) -> Result<RefPtr<BitsRequest>, BitsTaskError> {
+        BitsRequest::new(
+            data.guid.clone(),
+            RefPtr::new(bits_service),
+            client_init_data.monitor_timeout_ms,
+            RefPtr::new(&observer),
+            maybe_context.map(RefPtr::new),
+            monitor_client,
+            ServiceAction::MonitorDownload,
+        )
+    }
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/bitsdownload/src/bits_interface/xpcom_methods.rs
@@ -0,0 +1,195 @@
+/* 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 macro is very similar to xpcom_macro, but works a bit differently:
+///
+/// When possible, it returns errors via the callback rather than via the return
+/// value.
+///
+/// It implicitly adds the callback argument of type: nsIBitsNewRequestCallback
+///
+/// It needs an action type, to be specified before the rust name, in square
+/// brackets.
+///
+/// The rustic implementation that the xpcom method calls is expected to return
+/// the type: Result<_, BitsTaskError>. If this value is Ok, it will be ignored.
+/// If the value is Err, it will be returned via the callback passed.
+///
+/// Usage like this:
+///
+/// ```ignore
+///     nsIBits_method!(
+///         [ActionType]
+///         rust_method => XpcomMethod(
+///             foo: *const nsACString,
+///             bar: *const nsIBar,
+///             baz: bool,
+///             [optional] qux: *const nsIQux,
+///         )
+///     );
+/// ```
+///
+/// Results in the macro generating a method like:
+///
+/// ```ignore
+///     unsafe fn XpcomMethod(
+///         &self,
+///         foo: *const nsACString,
+///         bar: *const nsIBar,
+///         baz: bool,
+///         qux: *const nsIQux,
+///         callback: *const nsIBitsNewRequestCallback,
+///     ) -> nsresult {
+///         let callback: &nsIBitsNewRequestCallback = match xpcom::Ensure::ensure(callback) {
+///             Ok(val) => val,
+///             Err(result) => return result,
+///         };
+///         let foo = match xpcom::Ensure::ensure(foo) {
+///             Ok(val) => val,
+///             Err(_) => {
+///                 dispatch_pretask_interface_error(BitsTaskError::new(ErrorType::NullArgument, ActionType.into(), ErrorStage::Pretask), callback);
+///                 return NS_OK;
+///             }
+///         };
+///         let bar = match xpcom::Ensure::ensure(bar) {
+///             Ok(val) => val,
+///             Err(_) => {
+///                 dispatch_pretask_interface_error(BitsTaskError::new(ErrorType::NullArgument, ActionType.into(), ErrorStage::Pretask), callback);
+///                 return NS_OK;
+///             }
+///         };
+///         let baz = match xpcom::Ensure::ensure(baz) {
+///             Ok(val) => val,
+///             Err(_) => {
+///                 dispatch_pretask_interface_error(BitsTaskError::new(ErrorType::NullArgument, ActionType.into(), ErrorStage::Pretask), callback);
+///                 return NS_OK;
+///             }
+///         };
+///         let qux = match xpcom::Ensure::ensure(qux) {
+///             Ok(val) => Some(val),
+///             Err(_) => None,
+///         };
+///
+///         if let Err(error) = self.rust_method(foo, bar, baz, qux, callback) {
+///             dispatch_pretask_interface_error(error, callback);
+///         }
+///
+///         NS_OK
+///     }
+/// ```
+///
+/// Which expects a Rustic implementation method like:
+///
+/// ```ignore
+///     fn rust_method(
+///         &self,
+///         foo: &nsACString,
+///         bar: &nsIBar,
+///         baz: bool,
+///         qux: Option<&nsIQux>,
+///         callback: &nsIBitsNewRequestCallback,
+///     ) -> Result<(), BitsTaskError> {
+///         do_something()
+///     }
+/// ```
+#[macro_export]
+macro_rules! nsIBits_method {
+    // The internal rule @ensure_param converts raw pointer arguments to
+    // references, calling dispatch_pretask_interface_error and returning if the
+    // argument is null.
+    // If, however, the type is optional, the reference will also be wrapped
+    // in an option and null pointers will be converted to None.
+    (@ensure_param [optional] $name:ident, $action:expr, $callback:ident) => {
+        let $name = match Ensure::ensure($name) {
+            Ok(val) => Some(val),
+            Err(_) => None,
+        };
+    };
+    (@ensure_param $name:ident, $action:expr, $callback:ident) => {
+        let $name = match Ensure::ensure($name) {
+            Ok(val) => val,
+            Err(_) => {
+                dispatch_pretask_interface_error(BitsTaskError::new(NullArgument, $action.into(), Pretask), $callback);
+                return NS_OK;
+            }
+        };
+    };
+
+    ([$action:expr] $rust_name:ident => $xpcom_name:ident($($([$param_required:ident])* $param_name:ident: $param_type:ty $(,)*)*)) => {
+        #[allow(non_snake_case)]
+        unsafe fn $xpcom_name(&self, $($param_name: $param_type, )* callback: *const nsIBitsNewRequestCallback) -> nsresult {
+            use xpcom::Ensure;
+            use nserror::NS_OK;
+            // When no params are passed, the imports below will not be used, so silence the
+            // warning
+            #[allow(unused_imports)]
+            use bits_interface::{
+                dispatch_callback::dispatch_pretask_interface_error,
+                error::{BitsTaskError, ErrorStage::Pretask, ErrorType::NullArgument},
+            };
+
+            let callback: &nsIBitsNewRequestCallback = match Ensure::ensure(callback) {
+                Ok(val) => val,
+                Err(result) => return result,
+            };
+            $(nsIBits_method!(@ensure_param $([$param_required])* $param_name, $action, callback);)*
+            if let Err(error) = self.$rust_name($($param_name, )* callback) {
+                dispatch_pretask_interface_error(error, callback);
+            }
+            NS_OK
+        }
+    };
+}
+
+/*
+ * Same as above, but expects a nsIBitsCallback as its callback.
+ */
+#[macro_export]
+macro_rules! nsIBitsRequest_method {
+    // The internal rule @ensure_param converts raw pointer arguments to
+    // references, calling dispatch_pretask_interface_error and returning if the
+    // argument is null.
+    // If, however, the type is optional, the reference will also be wrapped
+    // in an option and null pointers will be converted to None.
+    (@ensure_param [optional] $name:ident, $action:expr, $callback:ident) => {
+        let $name = match Ensure::ensure($name) {
+            Ok(val) => Some(val),
+            Err(_) => None,
+        };
+    };
+    (@ensure_param $name:ident, $action:expr, $callback:ident) => {
+        let $name = match Ensure::ensure($name) {
+            Ok(val) => val,
+            Err(_) => {
+                dispatch_pretask_request_error(BitsTaskError::new(NullArgument, $action.into(), Pretask), $callback);
+                return NS_OK;
+            }
+        };
+    };
+
+    ([$action:expr] $rust_name:ident => $xpcom_name:ident($($([$param_required:ident])* $param_name:ident: $param_type:ty $(,)*)*)) => {
+        #[allow(non_snake_case)]
+        unsafe fn $xpcom_name(&self, $($param_name: $param_type, )* callback: *const nsIBitsCallback) -> nsresult {
+            use xpcom::Ensure;
+            use nserror::NS_OK;
+            // When no params are passed, the imports below will not be used, so silence the
+            // warning
+            #[allow(unused_imports)]
+            use bits_interface::{
+                dispatch_callback::dispatch_pretask_request_error,
+                error::{BitsTaskError, ErrorStage::Pretask, ErrorType::NullArgument},
+            };
+
+            let callback: &nsIBitsCallback = match Ensure::ensure(callback) {
+                Ok(val) => val,
+                Err(result) => return result,
+            };
+            $(nsIBitsRequest_method!(@ensure_param $([$param_required])* $param_name, $action, callback);)*
+            if let Err(error) = self.$rust_name($($param_name, )* callback) {
+                dispatch_pretask_request_error(error, callback);
+            }
+            NS_OK
+        }
+    };
+}
--- a/toolkit/components/bitsdownload/src/lib.rs
+++ b/toolkit/components/bitsdownload/src/lib.rs
@@ -1,1 +1,24 @@
+/* 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 crate is meant to be used in Windows only. It provides the
+//! bits_interface module, which implements the nsIBits an nsIBitsRequest
+//! XPCOM interfaces. These interfaces allow usage of the Windows component:
+//! BITS (Background Intelligent Transfer Service). Further documentation can
+//! be found in the XPCOM interface definition, located in nsIBits.idl
+
+#![cfg(target_os = "windows")]
+
 extern crate bits_client;
+extern crate comedy;
+extern crate crossbeam_utils;
+extern crate failure;
+extern crate libc;
+extern crate log;
+extern crate moz_task;
+extern crate nserror;
+extern crate nsstring;
+extern crate xpcom;
+
+pub mod bits_interface;
--- a/toolkit/components/moz.build
+++ b/toolkit/components/moz.build
@@ -16,16 +16,17 @@ DIRS += [
     'aboutcheckerboard',
     'aboutmemory',
     'aboutperformance',
     'alerts',
     'antitracking',
     'apppicker',
     'asyncshutdown',
     'backgroundhangmonitor',
+    'bitsdownload',
     'browser',
     'cleardata',
     'clearsitedata',
     'cloudstorage',
     'commandlines',
     'contentprefs',
     'contextualidentity',
     'crashes',
--- a/xpcom/build/Services.py
+++ b/xpcom/build/Services.py
@@ -45,16 +45,18 @@ service('URIClassifier', 'nsIURIClassifi
 service('ActivityDistributor', 'nsIHttpActivityDistributor',
         "@mozilla.org/network/http-activity-distributor;1")
 service('HistoryService', 'mozilla::IHistory',
         "@mozilla.org/browser/history;1")
 service('ThirdPartyUtil', 'mozIThirdPartyUtil',
         "@mozilla.org/thirdpartyutil;1")
 service('URIFixup', 'nsIURIFixup',
         "@mozilla.org/docshell/urifixup;1")
+service('Bits', 'nsIBits',
+        "@mozilla.org/bits;1")
 
 # The definition file needs access to the definitions of the particular
 # interfaces. If you add a new interface here, make sure the necessary includes
 # are also listed in the following code snippet.
 CPP_INCLUDES = """
 #include "mozilla/Likely.h"
 #include "mozilla/Services.h"
 #include "mozIThirdPartyUtil.h"
@@ -77,16 +79,17 @@ CPP_INCLUDES = """
 #include "nsIStreamTransportService.h"
 #include "nsISocketTransportService.h"
 #include "nsIURIClassifier.h"
 #include "nsIHttpActivityObserver.h"
 #include "nsIAsyncShutdown.h"
 #include "nsIUUIDGenerator.h"
 #include "nsIGfxInfo.h"
 #include "nsIURIFixup.h"
+#include "nsIBits.h"
 """
 
 #####
 # Codegen Logic
 #
 # The following code consumes the data listed above to generate the files
 # Services.h, Services.cpp, and services.rs which provide access to these
 # service getters in both rust and C++ code.
--- a/xpcom/rust/xpcom/src/refptr.rs
+++ b/xpcom/rust/xpcom/src/refptr.rs
@@ -1,15 +1,16 @@
 /* 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 std::mem;
 use std::ptr;
 use std::ops::Deref;
+use std::fmt;
 use std::marker::PhantomData;
 use std::cell::Cell;
 use std::sync::atomic::{self, AtomicUsize, Ordering};
 
 use nserror::{nsresult, NS_OK};
 
 use libc;
 
@@ -107,16 +108,22 @@ impl <T: RefCounted + 'static> Drop for 
 
 impl <T: RefCounted + 'static> Clone for RefPtr<T> {
     #[inline]
     fn clone(&self) -> RefPtr<T> {
         RefPtr::new(self)
     }
 }
 
+impl <T: RefCounted + 'static + fmt::Debug> fmt::Debug for RefPtr<T> {
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        write!(f, "RefPtr<{:?}>", self.deref())
+    }
+}
+
 /// A wrapper that binds a RefCounted value to its original thread,
 /// preventing retrieval from other threads and panicking if the value
 /// is dropped on a different thread.
 ///
 /// These limitations enable values of this type to be Send + Sync, which is
 /// useful when creating a struct that holds a RefPtr<T> type while being
 /// Send + Sync.  Such a struct can hold a ThreadBoundRefPtr<T> type instead.
 pub struct ThreadBoundRefPtr<T: RefCounted + 'static>(ThreadBound<*const T>);