Merge inbound to mozilla-central r=merge a=merge
authorbtara <btara@mozilla.com>
Tue, 14 Nov 2017 22:37:02 +0200
changeset 443492 f0c0fb9182d695081edf170d8e3bcb8164f2c96a
parent 443481 53e6f0322b9cad3a0a5d97433528bbfcad98a8f0 (current diff)
parent 443491 309536e6c69c7d2bcb0db255edfd8687afe48d64 (diff)
child 443532 0855fc7ce11e2ae83c6eaab45c5dc8b69192d1f8
child 443598 35a8d93c2142342da86cd5614acbcdc5fa188067
push id8527
push userCallek@gmail.com
push dateThu, 11 Jan 2018 21:05:50 +0000
treeherdermozilla-beta@95342d212a7a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge, merge
milestone59.0a1
first release with
nightly linux32
f0c0fb9182d6 / 59.0a1 / 20171114220116 / files
nightly linux64
f0c0fb9182d6 / 59.0a1 / 20171114220116 / files
nightly win32
f0c0fb9182d6 / 59.0a1 / 20171114220116 / files
nightly win64
f0c0fb9182d6 / 59.0a1 / 20171114220116 / files
nightly mac
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly win32
nightly win64
Merge inbound to mozilla-central r=merge a=merge
--- a/accessible/base/nsAccessibilityService.cpp
+++ b/accessible/base/nsAccessibilityService.cpp
@@ -1872,34 +1872,42 @@ nsAccessibilityService::UnsetConsumers(u
     return;
   }
 
   gConsumers &= ~aConsumers;
   NotifyOfConsumersChange();
 }
 
 void
+nsAccessibilityService::GetConsumers(nsAString& aString)
+{
+  const char16_t* kJSONFmt =
+    u"{ \"XPCOM\": %s, \"MainProcess\": %s, \"PlatformAPI\": %s }";
+  nsString json;
+  nsTextFormatter::ssprintf(json, kJSONFmt,
+    gConsumers & eXPCOM ? "true" : "false",
+    gConsumers & eMainProcess ? "true" : "false",
+    gConsumers & ePlatformAPI ? "true" : "false");
+  aString.Assign(json);
+}
+
+void
 nsAccessibilityService::NotifyOfConsumersChange()
 {
   nsCOMPtr<nsIObserverService> observerService =
     mozilla::services::GetObserverService();
 
   if (!observerService) {
     return;
   }
 
-  const char16_t* kJSONFmt =
-    u"{ \"XPCOM\": %s, \"MainProcess\": %s, \"PlatformAPI\": %s }";
-  nsString json;
-  nsTextFormatter::ssprintf(json, kJSONFmt,
-    gConsumers & eXPCOM ? "true" : "false",
-    gConsumers & eMainProcess ? "true" : "false",
-    gConsumers & ePlatformAPI ? "true" : "false");
+  nsAutoString consumers;
+  GetConsumers(consumers);
   observerService->NotifyObservers(
-    nullptr, "a11y-consumers-changed", json.get());
+    nullptr, "a11y-consumers-changed", consumers.get());
 }
 
 nsAccessibilityService*
 GetOrCreateAccService(uint32_t aNewConsumer)
 {
   if (!nsAccessibilityService::gAccessibilityService) {
     RefPtr<nsAccessibilityService> service = new nsAccessibilityService();
     if (!service->Init()) {
--- a/accessible/base/nsAccessibilityService.h
+++ b/accessible/base/nsAccessibilityService.h
@@ -305,16 +305,21 @@ private:
                                 Accessible* aContext);
 
   /**
    * Notify observers about change of the accessibility service's consumers.
    */
   void NotifyOfConsumersChange();
 
   /**
+   * Get a JSON string representing the accessibility service consumers.
+   */
+  void GetConsumers(nsAString& aString);
+
+  /**
    * Set accessibility service consumers.
    */
   void SetConsumers(uint32_t aConsumers);
 
   /**
    * Unset accessibility service consumers.
    */
   void UnsetConsumers(uint32_t aConsumers);
--- a/accessible/interfaces/nsIAccessibilityService.idl
+++ b/accessible/interfaces/nsIAccessibilityService.idl
@@ -11,17 +11,17 @@ interface nsIWeakReference;
 interface nsIPresShell;
 interface nsIAccessiblePivot;
 
 /**
  * An interface for in-process accessibility clients wishing to get an
  * nsIAccessible for a given DOM node.  More documentation at:
  *   http://www.mozilla.org/projects/ui/accessibility
  */
-[scriptable, builtinclass, uuid(9a6f80fe-25cc-405c-9f8f-25869bc9f94e)]
+[scriptable, builtinclass, uuid(2188e3a0-c88e-11e7-8f1a-0800200c9a66)]
 interface nsIAccessibilityService : nsISupports
 {
   /**
    * Return application accessible.
    */
   nsIAccessible getApplicationAccessible();
 
   /**
@@ -92,9 +92,15 @@ interface nsIAccessibilityService : nsIS
    * @see Logging.cpp for list of possible values.
    */
   void setLogging(in ACString aModules);
 
   /**
    * Return true if the given module is logged.
    */
   boolean isLogged(in AString aModule);
+
+  /**
+   * Get the current accessibility service consumers.
+   * @returns a JSON string representing the accessibility service consumers.
+   */
+  AString getConsumers();
 };
--- a/accessible/tests/browser/browser_shutdown_remote_no_reference.js
+++ b/accessible/tests/browser/browser_shutdown_remote_no_reference.js
@@ -34,16 +34,20 @@ add_task(async function() {
     await Promise.all([parentA11yInit, contentA11yInit]);
     await parentConsumersChanged.then(data => Assert.deepEqual(data, {
       XPCOM: true, MainProcess: false, PlatformAPI: false
     }, "Accessibility service consumers in parent are correct."));
     await contentConsumersChanged.then(data => Assert.deepEqual(data, {
       XPCOM: false, MainProcess: true, PlatformAPI: false
     }, "Accessibility service consumers in content are correct."));
 
+    Assert.deepEqual(JSON.parse(accService.getConsumers()), {
+      XPCOM: true, MainProcess: false, PlatformAPI: false
+    }, "Accessibility service consumers in parent are correct.");
+
     info("Removing a service in parent and waiting for service to be shut " +
       "down in content");
     // Remove a11y service reference in the main process.
     let parentA11yShutdown = shutdownPromise();
     let contentA11yShutdown = shutdownPromise(browser);
     parentConsumersChanged = a11yConsumersChangedPromise();
     contentConsumersChanged =
       ContentTask.spawn(browser, {}, a11yConsumersChangedPromise);
--- a/accessible/tests/browser/browser_shutdown_remote_own_reference.js
+++ b/accessible/tests/browser/browser_shutdown_remote_own_reference.js
@@ -42,16 +42,22 @@ add_task(async function() {
     // Add a new reference to the a11y service inside the content process.
     loadFrameScripts(browser, `let accService = Components.classes[
       '@mozilla.org/accessibilityService;1'].getService(
         Components.interfaces.nsIAccessibilityService);`);
     await contentConsumersChanged.then(data => Assert.deepEqual(data, {
       XPCOM: true, MainProcess: true, PlatformAPI: false
     }, "Accessibility service consumers in content are correct."));
 
+    const contentConsumers = await ContentTask.spawn(browser, {}, () =>
+      accService.getConsumers());
+    Assert.deepEqual(JSON.parse(contentConsumers), {
+      XPCOM: true, MainProcess: true, PlatformAPI: false
+    }, "Accessibility service consumers in parent are correct.");
+
     info("Shutting down a service in parent and making sure the one in " +
       "content stays alive");
     let contentCanShutdown = false;
     let parentA11yShutdown = shutdownPromise();
     contentConsumersChanged =
       ContentTask.spawn(browser, {}, a11yConsumersChangedPromise);
     // This promise will resolve only if contentCanShutdown flag is set to true.
     // If 'a11y-init-or-shutdown' event with '0' flag (in content) comes before
--- a/accessible/xpcom/xpcAccessibilityService.cpp
+++ b/accessible/xpcom/xpcAccessibilityService.cpp
@@ -253,16 +253,28 @@ xpcAccessibilityService::IsLogged(const 
 
 #ifdef A11Y_LOG
   *aIsLogged = logging::IsEnabled(aModule);
 #endif
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+xpcAccessibilityService::GetConsumers(nsAString& aString)
+{
+  nsAccessibilityService* accService = GetAccService();
+  if (!accService) {
+    return NS_ERROR_SERVICE_NOT_AVAILABLE;
+  }
+
+  accService->GetConsumers(aString);
+  return NS_OK;
+}
+
 ////////////////////////////////////////////////////////////////////////////////
 // NS_GetAccessibilityService
 ////////////////////////////////////////////////////////////////////////////////
 
 nsresult
 NS_GetAccessibilityService(nsIAccessibilityService** aResult)
 {
   NS_ENSURE_TRUE(aResult, NS_ERROR_NULL_POINTER);
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1007,20 +1007,16 @@
         </body>
       </method>
 
       <field name="serializationHelper">
         Cc["@mozilla.org/network/serialization-helper;1"]
           .getService(Ci.nsISerializationHelper);
       </field>
 
-      <field name="mIconLoadingPrincipal">
-        null
-      </field>
-
       <method name="storeIcon">
         <parameter name="aBrowser"/>
         <parameter name="aURI"/>
         <parameter name="aLoadingPrincipal"/>
         <parameter name="aRequestContextID"/>
         <body>
           <![CDATA[
           try {
--- a/devtools/shim/aboutdevtools/aboutdevtools.css
+++ b/devtools/shim/aboutdevtools/aboutdevtools.css
@@ -1,25 +1,26 @@
 /* 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/. */
 
 :root {
   /* Photon color variables used on the aboutdevtools page */
+  --blue-50: #0a84ff;
+  --blue-50-alpha20: rgba(10, 132, 255, 0.2);
   --blue-60: #0060df;
   --blue-70: #003eaa;
   --blue-80: #002275;
   --grey-30: #d7d7db;
   --grey-90: #0c0c0d;
   --grey-90-alpha-10: rgba(12, 12, 13, 0.1);
   --grey-90-alpha-20: rgba(12, 12, 13, 0.2);
   --grey-90-alpha-30: rgba(12, 12, 13, 0.3);
   --grey-90-alpha-40: rgba(12, 12, 13, 0.4);
   --grey-90-alpha-50: rgba(12, 12, 13, 0.5);
-  --teal-60: #00c8d7;
   --red-50: #ff0039;
   --white: #ffffff;
 
   /* Shared variables */
   --line-height: 1.5em;
 }
 
 html, body {
@@ -31,17 +32,24 @@ p {
   line-height: var(--line-height);
 }
 
 .box {
   width: 100%;
   max-width: 850px;
   display: flex;
   flex-shrink: 0;
-  padding: 34px 0 50px 0;
+}
+
+#install-page .box {
+  padding: 17% 0 50px 0;
+}
+
+#welcome-page .box {
+  padding: 50px 0;
 }
 
 .wrapper {
   width: 100%;
   height: 100%;
   display: flex;
   flex-direction: column;
   align-items: center;
@@ -88,16 +96,17 @@ p {
 }
 
 .feature-desc {
   margin: 1em 20px;
 }
 
 .feature-link {
   display: block;
+  margin-top: 10px;
 }
 
 .external,
 .external:hover,
 .external:visited,
 .external:hover:active {
   color: var(--blue-60);
 }
@@ -124,16 +133,17 @@ p {
   font-weight: 300;
   font-size: 32px;
   margin-top: 16px;
   line-height: 44px;
 }
 
 .buttons-container {
   display: flex;
+  margin-top: 5px;
 }
 
 .buttons-container button:not(:last-child) {
   margin-right: 10px;
 }
 
 button {
   margin: 20px 0 0 0;
@@ -190,16 +200,17 @@ button::-moz-focus-inner {
 
 footer {
   display: flex;
   align-items: center;
   justify-content: center;
   width: 100%;
   min-height: 300px;
   flex-grow: 1;
+  padding-bottom: 15px;
   color: var(--white);
   background: linear-gradient(0, var(--blue-60), var(--blue-80));
 }
 
 .dev-edition-logo {
   flex-shrink: 0;
   width: 165px;
   margin: 20px 50px 0 0;
--- a/devtools/shim/aboutdevtools/aboutdevtools.xhtml
+++ b/devtools/shim/aboutdevtools/aboutdevtools.xhtml
@@ -70,17 +70,17 @@
 
             <section id="newsletter-privacy" class="newsletter-form-section">
               <input type="checkbox" id="privacy" name="privacy" required="true" />
               <label for="privacy">&aboutDevtools.newsletter.privacy.label;</label>
             </section>
             <button type="submit" id="newsletter-submit" class="primary-button">&aboutDevtools.newsletter.subscribeButton;</button>
           </form>
           <div id="newsletter-thanks">
-            <h2>&aboutDevtools.newsletter.thanks.title;</h2>
+            <h2 class="newsletter-title">&aboutDevtools.newsletter.thanks.title;</h2>
             <p>&aboutDevtools.newsletter.thanks.message;</p>
           </div>
         </div>
       </div>
     </div>
 
     <div class="features">
       <ul class="features-list">
--- a/devtools/shim/aboutdevtools/subscribe.css
+++ b/devtools/shim/aboutdevtools/subscribe.css
@@ -2,16 +2,23 @@
  * 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 file contains the styles for the newsletter subscription form on about:devtools.
  * It is largely inspired from https://mozilla.github.io/basket-example/
  */
 
+.newsletter-title {
+  font-size: 17px;
+  font-weight: 500;
+  margin-top: 26px;
+  margin-bottom: -4px;
+}
+
 #newsletter-errors {
   /* Hidden by default */
   display: none;
 
   margin-bottom: 20px;
   padding: 10px;
   border-radius: 2px;
 
@@ -47,54 +54,57 @@
   width: 320px;
 }
 
 #newsletter-privacy {
   display: flex;
 
   /* The privacy section is hidden by default and only displayed on focus */
   height: 0;
-  margin-bottom: -20px;
   overflow: hidden;
+
+  padding: 3px 0 0 3px;
+  margin: -3px 0 -20px -3px;
 }
 
 #newsletter-privacy.animate {
   transition: all 0.25s cubic-bezier(.15,.75,.35,.9);
 }
 
 #newsletter-privacy label {
   line-height: var(--line-height);
 }
 
 #privacy {
   width: 20px;
   height: 20px;
-  margin: 2px;
+  margin-top: 2px;
   margin-inline-end: 10px;
   flex-shrink: 0;
 }
 
 #email {
   width: 100%;
   box-sizing: border-box;
-  padding: 12px 15px;
+  padding: 8px;
 }
 
 #newsletter-form input {
   border-color: var(--grey-90-alpha-30);
 }
 
 #newsletter-form input:hover {
   border-color: var(--grey-90-alpha-50);
 }
 
 #newsletter-form input:focus {
-  border-color: var(--teal-60);
-  box-shadow: 0 0 2px 0 var(--teal-60);
+  border-color: var(--blue-50);
+  box-shadow: 0 0 0px 3px var(--blue-50-alpha20);
 }
 
 #newsletter-form::placeholder {
   color: var(--grey-90-alpha-40);
 }
 
 #newsletter-submit {
   display: block;
+  padding: 8px 20px;
 }
--- a/dom/base/nsCCUncollectableMarker.cpp
+++ b/dom/base/nsCCUncollectableMarker.cpp
@@ -471,17 +471,17 @@ nsCCUncollectableMarker::Observe(nsISupp
       break;
     }
   }
 
   return NS_OK;
 }
 
 void
-mozilla::dom::TraceBlackJS(JSTracer* aTrc, uint32_t aGCNumber, bool aIsShutdownGC)
+mozilla::dom::TraceBlackJS(JSTracer* aTrc, bool aIsShutdownGC)
 {
 #ifdef MOZ_XUL
   // Mark the scripts held in the XULPrototypeCache. This is required to keep
   // the JS script in the cache live across GC.
   nsXULPrototypeCache* cache = nsXULPrototypeCache::MaybeGetInstance();
   if (cache) {
     if (aIsShutdownGC) {
       cache->FlushScripts();
@@ -539,15 +539,15 @@ mozilla::dom::TraceBlackJS(JSTracer* aTr
             }
           }
         }
 
 #ifdef MOZ_XUL
         nsIDocument* doc = window->GetExtantDoc();
         if (doc && doc->IsXULDocument()) {
           XULDocument* xulDoc = static_cast<XULDocument*>(doc);
-          xulDoc->TraceProtos(aTrc, aGCNumber);
+          xulDoc->TraceProtos(aTrc);
         }
 #endif
       }
     }
   }
 }
--- a/dom/base/nsCCUncollectableMarker.h
+++ b/dom/base/nsCCUncollectableMarker.h
@@ -39,13 +39,13 @@ class nsCCUncollectableMarker final : pu
 
 private:
   nsCCUncollectableMarker() {}
   ~nsCCUncollectableMarker() {}
 };
 
 namespace mozilla {
 namespace dom {
-void TraceBlackJS(JSTracer* aTrc, uint32_t aGCNumber, bool aIsShutdownGC);
+void TraceBlackJS(JSTracer* aTrc, bool aIsShutdownGC);
 } // namespace dom
 } // namespace mozilla
 
 #endif
--- a/dom/webauthn/PublicKeyCredential.cpp
+++ b/dom/webauthn/PublicKeyCredential.cpp
@@ -79,33 +79,42 @@ PublicKeyCredential::SetRawId(CryptoBuff
 
 void
 PublicKeyCredential::SetResponse(RefPtr<AuthenticatorResponse> aResponse)
 {
   mResponse = aResponse;
 }
 
 /* static */ already_AddRefed<Promise>
-PublicKeyCredential::IsPlatformAuthenticatorAvailable(GlobalObject& aGlobal)
+PublicKeyCredential::IsUserVerifyingPlatformAuthenticatorAvailable(GlobalObject& aGlobal)
 {
   nsIGlobalObject* globalObject =
     xpc::NativeGlobal(JS::CurrentGlobalOrNull(aGlobal.Context()));
   if (NS_WARN_IF(!globalObject)) {
     return nullptr;
   }
 
   ErrorResult rv;
   RefPtr<Promise> promise = Promise::Create(globalObject, rv);
-  if(rv.Failed()) {
+  if (rv.Failed()) {
     return nullptr;
   }
 
-  // Complete in Bug 1406468. This shouldn't just always return true, it should
-  // follow the guidelines in
-  // https://w3c.github.io/webauthn/#isPlatformAuthenticatorAvailable
-  // such as ensuring that U2FTokenManager isn't in some way disabled.
-  promise->MaybeResolve(true);
+  // https://w3c.github.io/webauthn/#isUserVerifyingPlatformAuthenticatorAvailable
+  //
+  // We currently implement no platform authenticators, so this would always
+  // resolve to false. For those cases, the spec recommends a resolve timeout
+  // on the order of 10 minutes to avoid fingerprinting.
+  //
+  // A simple solution is thus to never resolve the promise, otherwise we'd
+  // have to track every single call to this method along with a promise
+  // and timer to resolve it after exactly X minutes.
+  //
+  // A Relying Party has to deal with a non-response in a timely fashion, so
+  // we can keep this as-is (and not resolve) even when we support platform
+  // authenticators but they're not available, or a user rejects a website's
+  // request to use them.
   return promise.forget();
 }
 
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/webauthn/PublicKeyCredential.h
+++ b/dom/webauthn/PublicKeyCredential.h
@@ -42,17 +42,17 @@ public:
 
   nsresult
   SetRawId(CryptoBuffer& aBuffer);
 
   void
   SetResponse(RefPtr<AuthenticatorResponse>);
 
   static already_AddRefed<Promise>
-  IsPlatformAuthenticatorAvailable(GlobalObject& aGlobal);
+  IsUserVerifyingPlatformAuthenticatorAvailable(GlobalObject& aGlobal);
 
 private:
   CryptoBuffer mRawId;
   JS::Heap<JSObject*> mRawIdCachedObj;
   RefPtr<AuthenticatorResponse> mResponse;
   // Extensions are not supported yet.
   // <some type> mClientExtensionResults;
 };
--- a/dom/webauthn/tests/test_webauthn_isplatformauthenticatoravailable.html
+++ b/dom/webauthn/tests/test_webauthn_isplatformauthenticatoravailable.html
@@ -1,47 +1,52 @@
 <!DOCTYPE html>
 <meta charset=utf-8>
 <head>
-  <title>Test for W3C Web Authentication isPlatformAuthenticatorAvailable</title>
+  <title>Test for W3C Web Authentication isUserVerifyingPlatformAuthenticatorAvailable</title>
   <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="u2futil.js"></script>
   <script type="text/javascript" src="pkijs/common.js"></script>
   <script type="text/javascript" src="pkijs/asn1.js"></script>
   <script type="text/javascript" src="pkijs/x509_schema.js"></script>
   <script type="text/javascript" src="pkijs/x509_simpl.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
-<h1>Test for W3C Web Authentication isPlatformAuthenticatorAvailable</h1>
+<h1>Test for W3C Web Authentication isUserVerifyingPlatformAuthenticatorAvailable</h1>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1309284">Mozilla Bug 1309284</a>
 
 <script class="testbody" type="text/javascript">
 "use strict";
 
 // Execute the full-scope test
 SimpleTest.waitForExplicitFinish();
 
-// Turn off all tokens. This should result in "not allowed" failures
 SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn", true],
                                    ["security.webauth.webauthn_enable_softtoken", true],
                                    ["security.webauth.webauthn_enable_usbtoken", false]]},
 function() {
-  PublicKeyCredential.isPlatformAuthenticatorAvailable()
+  // This test ensures that isUserVerifyingPlatformAuthenticatorAvailable()
+  // is a callable method, but we currently can't test that it works in an
+  // automated way. If it resolves to false, per spec, we SHOULD wait
+  // ~10 minutes before resolving.
+  let p1 = PublicKeyCredential.isUserVerifyingPlatformAuthenticatorAvailable()
   .then(function(aResult) {
-    // The specification requires this method, if will return false, to wait 10
-    // minutes for anti-fingerprinting reasons. So we really can't test that
-    // in an automated way.
-    ok(aResult, "Should be available!");
+    ok(false, "We shouldn't get here.");
   })
   .catch(function(aProblem) {
-    is(false, "Problem encountered: " + aProblem);
-  })
-  .then(function() {
+    ok(false, "Problem encountered: " + aProblem);
+  });
+
+  // Finish on the next tick.
+  let p2 = Promise.resolve();
+
+  Promise.race([p1, p2]).then(function() {
+    ok(true, "isUserVerifyingPlatformAuthenticatorAvailable() is callable");
     SimpleTest.finish();
-  })
+  });
 });
 
 </script>
 
 </body>
 </html>
--- a/dom/webauthn/u2f-hid-rs/.gitignore
+++ b/dom/webauthn/u2f-hid-rs/.gitignore
@@ -1,9 +1,10 @@
 # Generated by Cargo
 # will have compiled files and executables
 /target/
+**/*.rs.bk
 
 # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
 # More information here http://doc.crates.io/guide.html#cargotoml-vs-cargolock
 Cargo.lock
 
 .DS_Store
--- a/dom/webauthn/u2f-hid-rs/src/lib.rs
+++ b/dom/webauthn/u2f-hid-rs/src/lib.rs
@@ -22,25 +22,27 @@ pub mod platform;
 #[cfg(any(target_os = "windows"))]
 #[path = "windows/mod.rs"]
 pub mod platform;
 
 #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
 #[path = "stub/mod.rs"]
 pub mod platform;
 
+#[cfg(not(any(target_os = "macos")))]
+mod khmatcher;
+
 #[macro_use]
 extern crate log;
 extern crate rand;
 extern crate libc;
 extern crate boxfnonce;
 extern crate runloop;
 
 mod consts;
-mod khmatcher;
 mod u2ftypes;
 mod u2fprotocol;
 
 mod manager;
 pub use manager::U2FManager;
 
 mod capi;
 pub use capi::*;
--- a/dom/webauthn/u2f-hid-rs/src/macos/device.rs
+++ b/dom/webauthn/u2f-hid-rs/src/macos/device.rs
@@ -1,76 +1,37 @@
 /* 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/. */
 
-extern crate libc;
 extern crate log;
 
-use std::fmt;
-use std::io;
+use consts::{CID_BROADCAST, HID_RPT_SIZE};
+use core_foundation_sys::base::*;
+use platform::iokit::*;
+use std::{fmt, io};
 use std::io::{Read, Write};
-use std::slice;
-use std::sync::mpsc::{channel, Sender, Receiver, RecvTimeoutError};
+use std::sync::mpsc::{Receiver, RecvTimeoutError};
 use std::time::Duration;
-
-use core_foundation_sys::base::*;
-use libc::c_void;
-
-use consts::{CID_BROADCAST, HID_RPT_SIZE};
 use u2ftypes::U2FDevice;
 
-use super::iokit::*;
-
 const READ_TIMEOUT: u64 = 15;
 
 pub struct Device {
     device_ref: IOHIDDeviceRef,
     cid: [u8; 4],
     report_rx: Receiver<Vec<u8>>,
-    report_send_void: *mut c_void,
-    scratch_buf_ptr: *mut u8,
 }
 
 impl Device {
-    pub fn new(device_ref: IOHIDDeviceRef) -> Self {
-        let (report_tx, report_rx) = channel();
-        let report_send_void = Box::into_raw(Box::new(report_tx)) as *mut c_void;
-
-        let scratch_buf = [0; HID_RPT_SIZE];
-        let scratch_buf_ptr = Box::into_raw(Box::new(scratch_buf)) as *mut u8;
-
-        unsafe {
-            IOHIDDeviceRegisterInputReportCallback(
-                device_ref,
-                scratch_buf_ptr,
-                HID_RPT_SIZE as CFIndex,
-                read_new_data_cb,
-                report_send_void,
-            );
-        }
-
+    pub fn new(device_ref: IOHIDDeviceRef, report_rx: Receiver<Vec<u8>>) -> Self {
         Self {
             device_ref,
             cid: CID_BROADCAST,
             report_rx,
-            report_send_void,
-            scratch_buf_ptr,
-        }
-    }
-}
-
-impl Drop for Device {
-    fn drop(&mut self) {
-        debug!("Dropping U2F device {}", self);
-
-        unsafe {
-            // Re-allocate raw pointers for destruction.
-            let _ = Box::from_raw(self.report_send_void as *mut Sender<Vec<u8>>);
-            let _ = Box::from_raw(self.scratch_buf_ptr as *mut [u8; HID_RPT_SIZE]);
         }
     }
 }
 
 impl fmt::Display for Device {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         write!(
             f,
@@ -143,50 +104,8 @@ impl U2FDevice for Device {
     fn get_cid(&self) -> &[u8; 4] {
         &self.cid
     }
 
     fn set_cid(&mut self, cid: [u8; 4]) {
         self.cid = cid;
     }
 }
-
-// This is called from the RunLoop thread
-extern "C" fn read_new_data_cb(
-    context: *mut c_void,
-    _: IOReturn,
-    _: *mut c_void,
-    report_type: IOHIDReportType,
-    report_id: u32,
-    report: *mut u8,
-    report_len: CFIndex,
-) {
-    unsafe {
-        let tx = &mut *(context as *mut Sender<Vec<u8>>);
-
-        trace!(
-            "read_new_data_cb type={} id={} report={:?} len={}",
-            report_type,
-            report_id,
-            report,
-            report_len
-        );
-
-        let report_len = report_len as usize;
-        if report_len > HID_RPT_SIZE {
-            warn!(
-                "read_new_data_cb got too much data! {} > {}",
-                report_len,
-                HID_RPT_SIZE
-            );
-            return;
-        }
-
-        let data = slice::from_raw_parts(report, report_len).to_vec();
-
-        if let Err(e) = tx.send(data) {
-            // TOOD: This happens when the channel closes before this thread
-            // does. This is pretty common, but let's deal with stopping
-            // properly later.
-            warn!("Problem returning read_new_data_cb data for thread: {}", e);
-        };
-    }
-}
--- a/dom/webauthn/u2f-hid-rs/src/macos/iohid.rs
+++ b/dom/webauthn/u2f-hid-rs/src/macos/iohid.rs
@@ -1,26 +1,23 @@
 /* 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/. */
 
 extern crate log;
 extern crate libc;
 
-use std::io;
-
-use super::iokit::*;
+use consts::{FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID};
 use core_foundation_sys::base::*;
 use core_foundation_sys::dictionary::*;
 use core_foundation_sys::number::*;
 use core_foundation_sys::runloop::*;
 use core_foundation_sys::string::*;
-
-use consts::{FIDO_USAGE_PAGE, FIDO_USAGE_U2FHID};
-use util::io_err;
+use libc::c_void;
+use platform::iokit::{CFRunLoopObserverContext, CFRunLoopObserverCreate};
 
 pub struct IOHIDDeviceMatcher {
     dict: CFDictionaryRef,
     keys: Vec<CFStringRef>,
     values: Vec<CFNumberRef>,
 }
 
 impl IOHIDDeviceMatcher {
@@ -87,41 +84,53 @@ impl Drop for IOHIDDeviceMatcher {
         }
 
         for value in &self.values {
             unsafe { CFRelease(*value as *mut libc::c_void) };
         }
     }
 }
 
-pub struct IOHIDManager {
-    manager: IOHIDManagerRef,
+pub struct CFRunLoopEntryObserver {
+    observer: CFRunLoopObserverRef,
+    // Keep alive until the observer goes away.
+    context_ptr: *mut CFRunLoopObserverContext,
 }
 
-impl IOHIDManager {
-    pub fn new() -> io::Result<Self> {
-        let manager = unsafe { IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDManagerOptionNone) };
+impl CFRunLoopEntryObserver {
+    pub fn new(callback: CFRunLoopObserverCallBack, context: *mut c_void) -> Self {
+        let context = CFRunLoopObserverContext::new(context);
+        let context_ptr = Box::into_raw(Box::new(context));
 
-        let rv = unsafe { IOHIDManagerOpen(manager, kIOHIDManagerOptionNone) };
-        if rv != 0 {
-            return Err(io_err("Couldn't open HID Manager"));
-        }
-
-        unsafe {
-            IOHIDManagerScheduleWithRunLoop(manager, CFRunLoopGetCurrent(), kCFRunLoopDefaultMode)
+        let observer = unsafe {
+            CFRunLoopObserverCreate(
+                kCFAllocatorDefault,
+                kCFRunLoopEntry,
+                false as Boolean,
+                0,
+                callback,
+                context_ptr,
+            )
         };
 
-        Ok(Self { manager })
+        Self {
+            observer,
+            context_ptr,
+        }
     }
 
-    pub fn get(&self) -> IOHIDManagerRef {
-        self.manager
+    pub fn add_to_current_runloop(&self) {
+        unsafe {
+            CFRunLoopAddObserver(CFRunLoopGetCurrent(), self.observer, kCFRunLoopDefaultMode)
+        };
     }
 }
 
-impl Drop for IOHIDManager {
+impl Drop for CFRunLoopEntryObserver {
     fn drop(&mut self) {
-        let rv = unsafe { IOHIDManagerClose(self.manager, kIOHIDManagerOptionNone) };
-        if rv != 0 {
-            warn!("Couldn't close the HID Manager");
-        }
+        unsafe {
+            CFRelease(self.observer as *mut c_void);
+
+            // Drop the CFRunLoopObserverContext.
+            let _ = Box::from_raw(self.context_ptr);
+        };
     }
 }
--- a/dom/webauthn/u2f-hid-rs/src/macos/iokit.rs
+++ b/dom/webauthn/u2f-hid-rs/src/macos/iokit.rs
@@ -2,38 +2,39 @@
  * 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/. */
 
 #![allow(non_snake_case, non_camel_case_types, non_upper_case_globals)]
 
 extern crate core_foundation_sys;
 extern crate libc;
 
-use libc::c_void;
-use core_foundation_sys::base::{CFIndex, CFAllocatorRef};
+use core_foundation_sys::base::{Boolean, CFIndex, CFAllocatorRef, CFOptionFlags};
 use core_foundation_sys::string::CFStringRef;
-use core_foundation_sys::runloop::CFRunLoopRef;
+use core_foundation_sys::runloop::{CFRunLoopRef, CFRunLoopObserverRef, CFRunLoopObserverCallBack};
 use core_foundation_sys::dictionary::CFDictionaryRef;
+use libc::c_void;
+use std::ops::Deref;
 
 type IOOptionBits = u32;
 
 pub type IOReturn = libc::c_int;
 
 pub type IOHIDManagerRef = *mut __IOHIDManager;
 pub type IOHIDManagerOptions = IOOptionBits;
 
 pub type IOHIDDeviceCallback = extern "C" fn(context: *mut c_void,
                                              result: IOReturn,
                                              sender: *mut c_void,
                                              device: IOHIDDeviceRef);
 
 pub type IOHIDReportType = IOOptionBits;
 pub type IOHIDReportCallback = extern "C" fn(context: *mut c_void,
                                              result: IOReturn,
-                                             sender: *mut c_void,
+                                             sender: IOHIDDeviceRef,
                                              report_type: IOHIDReportType,
                                              report_id: u32,
                                              report: *mut u8,
                                              report_len: CFIndex);
 
 pub const kIOHIDManagerOptionNone: IOHIDManagerOptions = 0;
 
 pub const kIOHIDReportTypeOutput: IOHIDReportType = 1;
@@ -45,50 +46,91 @@ pub struct __IOHIDManager {
 
 #[repr(C)]
 #[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
 pub struct IOHIDDeviceRef(*const c_void);
 
 unsafe impl Send for IOHIDDeviceRef {}
 unsafe impl Sync for IOHIDDeviceRef {}
 
+pub struct SendableRunLoop(pub CFRunLoopRef);
+
+unsafe impl Send for SendableRunLoop {}
+
+impl Deref for SendableRunLoop {
+    type Target = CFRunLoopRef;
+
+    fn deref(&self) -> &CFRunLoopRef {
+        &self.0
+    }
+}
+
+#[repr(C)]
+pub struct CFRunLoopObserverContext {
+    pub version: CFIndex,
+    pub info: *mut c_void,
+    pub retain: Option<extern "C" fn(info: *const c_void) -> *const c_void>,
+    pub release: Option<extern "C" fn(info: *const c_void)>,
+    pub copyDescription: Option<extern "C" fn(info: *const c_void) -> CFStringRef>,
+}
+
+impl CFRunLoopObserverContext {
+    pub fn new(context: *mut c_void) -> Self {
+        Self {
+            version: 0 as CFIndex,
+            info: context,
+            retain: None,
+            release: None,
+            copyDescription: None,
+        }
+    }
+}
+
 #[link(name = "IOKit", kind = "framework")]
 extern "C" {
+    // CFRunLoop
+    pub fn CFRunLoopObserverCreate(
+        allocator: CFAllocatorRef,
+        activities: CFOptionFlags,
+        repeats: Boolean,
+        order: CFIndex,
+        callout: CFRunLoopObserverCallBack,
+        context: *mut CFRunLoopObserverContext,
+    ) -> CFRunLoopObserverRef;
+
     // IOHIDManager
     pub fn IOHIDManagerCreate(
         allocator: CFAllocatorRef,
         options: IOHIDManagerOptions,
     ) -> IOHIDManagerRef;
     pub fn IOHIDManagerSetDeviceMatching(manager: IOHIDManagerRef, matching: CFDictionaryRef);
     pub fn IOHIDManagerRegisterDeviceMatchingCallback(
         manager: IOHIDManagerRef,
         callback: IOHIDDeviceCallback,
         context: *mut c_void,
     );
     pub fn IOHIDManagerRegisterDeviceRemovalCallback(
         manager: IOHIDManagerRef,
         callback: IOHIDDeviceCallback,
         context: *mut c_void,
     );
+    pub fn IOHIDManagerRegisterInputReportCallback(
+        manager: IOHIDManagerRef,
+        callback: IOHIDReportCallback,
+        context: *mut c_void,
+    );
     pub fn IOHIDManagerOpen(manager: IOHIDManagerRef, options: IOHIDManagerOptions) -> IOReturn;
     pub fn IOHIDManagerClose(manager: IOHIDManagerRef, options: IOHIDManagerOptions) -> IOReturn;
     pub fn IOHIDManagerScheduleWithRunLoop(
         manager: IOHIDManagerRef,
         runLoop: CFRunLoopRef,
         runLoopMode: CFStringRef,
     );
 
     // IOHIDDevice
     pub fn IOHIDDeviceSetReport(
         device: IOHIDDeviceRef,
         reportType: IOHIDReportType,
         reportID: CFIndex,
         report: *const u8,
         reportLength: CFIndex,
     ) -> IOReturn;
-    pub fn IOHIDDeviceRegisterInputReportCallback(
-        device: IOHIDDeviceRef,
-        report: *const u8,
-        reportLength: CFIndex,
-        callback: IOHIDReportCallback,
-        context: *mut c_void,
-    );
 }
--- a/dom/webauthn/u2f-hid-rs/src/macos/mod.rs
+++ b/dom/webauthn/u2f-hid-rs/src/macos/mod.rs
@@ -1,37 +1,31 @@
 /* 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/. */
 
 extern crate log;
-extern crate libc;
-
-use std::thread;
-use std::time::Duration;
 
 mod device;
-mod devicemap;
 mod iokit;
 mod iohid;
 mod monitor;
-
-use self::devicemap::DeviceMap;
-use self::monitor::Monitor;
+mod transaction;
 
 use consts::PARAMETER_SIZE;
-use khmatcher::KeyHandleMatcher;
-use runloop::RunLoop;
+use platform::device::Device;
+use platform::transaction::Transaction;
+use std::thread;
+use std::time::Duration;
+use u2fprotocol::{u2f_init_device, u2f_register, u2f_sign, u2f_is_keyhandle_valid};
 use util::{io_err, OnceCallback};
-use u2fprotocol::{u2f_register, u2f_sign, u2f_is_keyhandle_valid};
 
 #[derive(Default)]
 pub struct PlatformManager {
-    // Handle to the thread loop.
-    thread: Option<RunLoop>,
+    transaction: Option<Transaction>,
 }
 
 impl PlatformManager {
     pub fn new() -> Self {
         Default::default()
     }
 
     pub fn register(
@@ -42,153 +36,113 @@ impl PlatformManager {
         key_handles: Vec<Vec<u8>>,
         callback: OnceCallback<Vec<u8>>,
     ) {
         // Abort any prior register/sign calls.
         self.cancel();
 
         let cbc = callback.clone();
 
-        let thread = RunLoop::new_with_timeout(
-            move |alive| {
-                let mut devices = DeviceMap::new();
-                let monitor = try_or!(Monitor::new(), |e| callback.call(Err(e)));
-                let mut matches = KeyHandleMatcher::new(&key_handles);
+        // Start a new "sign" transaction.
+        let transaction = Transaction::new(timeout, cbc.clone(), move |device_ref, rx, alive| {
+            // Create a new device.
+            let dev = &mut Device::new(device_ref, rx);
 
-                'top: while alive() && monitor.alive() {
-                    for event in monitor.events() {
-                        devices.process_event(event);
-                    }
-
-                    // Query newly added devices.
-                    matches.update(devices.iter_mut(), |device, key_handle| {
-                        u2f_is_keyhandle_valid(device, &challenge, &application, key_handle)
-                            .unwrap_or(false /* no match on failure */)
-                    });
+            // Try initializing it.
+            if !u2f_init_device(dev) {
+                return;
+            }
 
-                    // Iterate all devices that don't match any of the handles
-                    // in the exclusion list and try to register.
-                    for (path, device) in devices.iter_mut() {
-                        if matches.get(path).is_empty() {
-                            if let Ok(bytes) = u2f_register(device, &challenge, &application) {
-                                callback.call(Ok(bytes));
-                                return;
-                            }
-                        }
+            // Iterate the exlude list and see if there are any matches.
+            // Abort the state machine if we found a valid key handle.
+            if key_handles.iter().any(|key_handle| {
+                u2f_is_keyhandle_valid(dev, &challenge, &application, key_handle)
+                    .unwrap_or(false) /* no match on failure */
+            })
+            {
+                return;
+            }
 
-                        // Check to see if monitor.events has any hotplug events that we'll need
-                        // to handle
-                        if monitor.events().size_hint().0 > 0 {
-                            debug!("Hotplug event; restarting loop");
-                            continue 'top;
-                        }
-                    }
-
-                    thread::sleep(Duration::from_millis(100));
+            while alive() {
+                if let Ok(bytes) = u2f_register(dev, &challenge, &application) {
+                    callback.call(Ok(bytes));
+                    break;
                 }
 
-                callback.call(Err(io_err("aborted or timed out")));
-            },
-            timeout,
-        );
+                // Sleep a bit before trying again.
+                thread::sleep(Duration::from_millis(100));
+            }
+        });
 
-        self.thread = Some(try_or!(
-            thread,
-            |_| cbc.call(Err(io_err("couldn't create runloop")))
-        ));
+        // Store the transaction so we can cancel it, if needed.
+        self.transaction = Some(try_or!(transaction, |_| {
+            cbc.call(Err(io_err("couldn't create transaction")))
+        }));
     }
 
-
     pub fn sign(
         &mut self,
         timeout: u64,
         challenge: Vec<u8>,
         application: Vec<u8>,
         key_handles: Vec<Vec<u8>>,
         callback: OnceCallback<(Vec<u8>, Vec<u8>)>,
     ) {
         // Abort any prior register/sign calls.
         self.cancel();
 
         let cbc = callback.clone();
 
-        let thread = RunLoop::new_with_timeout(
-            move |alive| {
-                let mut devices = DeviceMap::new();
-                let monitor = try_or!(Monitor::new(), |e| callback.call(Err(e)));
-                let mut matches = KeyHandleMatcher::new(&key_handles);
+        // Start a new "register" transaction.
+        let transaction = Transaction::new(timeout, cbc.clone(), move |device_ref, rx, alive| {
+            // Create a new device.
+            let dev = &mut Device::new(device_ref, rx);
 
-                'top: while alive() && monitor.alive() {
-                    for event in monitor.events() {
-                        devices.process_event(event);
-                    }
-
-                    // Query newly added devices.
-                    matches.update(devices.iter_mut(), |device, key_handle| {
-                        u2f_is_keyhandle_valid(device, &challenge, &application, key_handle)
-                            .unwrap_or(false /* no match on failure */)
-                    });
-
-                    // Iterate all devices.
-                    for (path, device) in devices.iter_mut() {
-                        let key_handles = matches.get(path);
+            // Try initializing it.
+            if !u2f_init_device(dev) {
+                return;
+            }
 
-                        // If the device matches none of the given key handles
-                        // then just make it blink with bogus data.
-                        if key_handles.is_empty() {
-                            let blank = vec![0u8; PARAMETER_SIZE];
-                            if let Ok(_) = u2f_register(device, &blank, &blank) {
-                                callback.call(Err(io_err("invalid key")));
-                                return;
-                            }
-
-                            continue;
-                        }
+            // Find all matching key handles.
+            let key_handles = key_handles
+                .iter()
+                .filter(|key_handle| {
+                    u2f_is_keyhandle_valid(dev, &challenge, &application, key_handle)
+                        .unwrap_or(false) /* no match on failure */
+                })
+                .collect::<Vec<&Vec<u8>>>();
 
-                        // Otherwise, try to sign.
-                        for key_handle in key_handles {
-                            if let Ok(bytes) = u2f_sign(
-                                device,
-                                &challenge,
-                                &application,
-                                key_handle,
-                            )
-                            {
-                                callback.call(Ok((key_handle.to_vec(), bytes)));
-                                return;
-                            }
-                        }
-
-                        // Check to see if monitor.events has any hotplug events that we'll
-                        // need to handle
-                        if monitor.events().size_hint().0 > 0 {
-                            debug!("Hotplug event; restarting loop");
-                            continue 'top;
+            while alive() {
+                // If the device matches none of the given key handles
+                // then just make it blink with bogus data.
+                if key_handles.is_empty() {
+                    let blank = vec![0u8; PARAMETER_SIZE];
+                    if let Ok(_) = u2f_register(dev, &blank, &blank) {
+                        callback.call(Err(io_err("invalid key")));
+                        break;
+                    }
+                } else {
+                    // Otherwise, try to sign.
+                    for key_handle in &key_handles {
+                        if let Ok(bytes) = u2f_sign(dev, &challenge, &application, key_handle) {
+                            callback.call(Ok((key_handle.to_vec(), bytes)));
+                            break;
                         }
                     }
-
-                    thread::sleep(Duration::from_millis(100));
                 }
 
-                callback.call(Err(io_err("aborted or timed out")));
-            },
-            timeout,
-        );
+                // Sleep a bit before trying again.
+                thread::sleep(Duration::from_millis(100));
+            }
+        });
 
-        self.thread = Some(try_or!(
-            thread,
-            |_| cbc.call(Err(io_err("couldn't create runloop")))
-        ));
+        // Store the transaction so we can cancel it, if needed.
+        self.transaction = Some(try_or!(transaction, |_| {
+            cbc.call(Err(io_err("couldn't create transaction")))
+        }));
     }
 
     pub fn cancel(&mut self) {
-        if let Some(thread) = self.thread.take() {
-            thread.cancel();
+        if let Some(mut transaction) = self.transaction.take() {
+            transaction.cancel();
         }
     }
 }
-
-impl Drop for PlatformManager {
-    fn drop(&mut self) {
-        debug!("OSX PlatformManager dropped");
-        self.cancel();
-    }
-}
--- a/dom/webauthn/u2f-hid-rs/src/macos/monitor.rs
+++ b/dom/webauthn/u2f-hid-rs/src/macos/monitor.rs
@@ -1,116 +1,176 @@
 /* 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::io;
-use std::sync::mpsc::{channel, Sender, Receiver, TryIter};
-use std::thread;
-
-use super::iohid::*;
-use super::iokit::*;
-use core_foundation_sys::runloop::*;
-use runloop::RunLoop;
-
 extern crate log;
 extern crate libc;
-use libc::c_void;
 
-pub enum Event {
-    Add(IOHIDDeviceRef),
-    Remove(IOHIDDeviceRef),
+use core_foundation_sys::base::*;
+use core_foundation_sys::runloop::*;
+use libc::c_void;
+use platform::iohid::*;
+use platform::iokit::*;
+use runloop::RunLoop;
+use std::{io, slice};
+use std::collections::HashMap;
+use std::sync::mpsc::{channel, Receiver, Sender};
+use util::io_err;
+
+struct DeviceData {
+    tx: Sender<Vec<u8>>,
+    runloop: RunLoop,
 }
 
-pub struct Monitor {
-    // Receive events from the thread.
-    rx: Receiver<Event>,
-    // Handle to the thread loop.
-    thread: RunLoop,
+pub struct Monitor<F>
+where
+    F: Fn(IOHIDDeviceRef, Receiver<Vec<u8>>, &Fn() -> bool) + Sync,
+{
+    manager: IOHIDManagerRef,
+    // Keep alive until the monitor goes away.
+    _matcher: IOHIDDeviceMatcher,
+    map: HashMap<IOHIDDeviceRef, DeviceData>,
+    new_device_cb: F,
 }
 
-impl Monitor {
-    pub fn new() -> io::Result<Self> {
-        let (tx, rx) = channel();
-
-        let thread = RunLoop::new(move |alive| -> io::Result<()> {
-            let tx_box = Box::new(tx);
-            let tx_ptr = Box::into_raw(tx_box) as *mut libc::c_void;
-
-            // This will keep `tx` alive only for the scope.
-            let _tx = unsafe { Box::from_raw(tx_ptr as *mut Sender<Event>) };
-
-            // Create and initialize a scoped HID manager.
-            let manager = IOHIDManager::new()?;
-
-            // Match only U2F devices.
-            let dict = IOHIDDeviceMatcher::new();
-            unsafe { IOHIDManagerSetDeviceMatching(manager.get(), dict.get()) };
+impl<F> Monitor<F>
+where
+    F: Fn(IOHIDDeviceRef, Receiver<Vec<u8>>, &Fn() -> bool) + Sync + 'static,
+{
+    pub fn new(new_device_cb: F) -> Self {
+        let manager = unsafe { IOHIDManagerCreate(kCFAllocatorDefault, kIOHIDManagerOptionNone) };
 
-            // Register callbacks.
-            unsafe {
-                IOHIDManagerRegisterDeviceMatchingCallback(
-                    manager.get(),
-                    Monitor::device_add_cb,
-                    tx_ptr,
-                );
-                IOHIDManagerRegisterDeviceRemovalCallback(
-                    manager.get(),
-                    Monitor::device_remove_cb,
-                    tx_ptr,
-                );
-            }
+        // Match FIDO devices only.
+        let _matcher = IOHIDDeviceMatcher::new();
+        unsafe { IOHIDManagerSetDeviceMatching(manager, _matcher.get()) };
 
-            // Run the Event Loop. CFRunLoopRunInMode() will dispatch HID
-            // input reports into the various callbacks
-            while alive() {
-                trace!("OSX Runloop running, handle={:?}", thread::current());
-
-                if unsafe { CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0.1, 0) } ==
-                    kCFRunLoopRunStopped
-                {
-                    debug!("OSX Runloop device stopped.");
-                    break;
-                }
-            }
-            debug!("OSX Runloop completed, handle={:?}", thread::current());
-
-            Ok(())
-        })?;
-
-        Ok(Self { rx, thread })
+        Self {
+            manager,
+            _matcher,
+            new_device_cb,
+            map: HashMap::new(),
+        }
     }
 
-    pub fn events(&self) -> TryIter<Event> {
-        self.rx.try_iter()
+    pub fn start(&mut self) -> io::Result<()> {
+        let context = self as *mut Self as *mut c_void;
+
+        unsafe {
+            IOHIDManagerRegisterDeviceMatchingCallback(
+                self.manager,
+                Monitor::<F>::on_device_matching,
+                context,
+            );
+            IOHIDManagerRegisterDeviceRemovalCallback(
+                self.manager,
+                Monitor::<F>::on_device_removal,
+                context,
+            );
+            IOHIDManagerRegisterInputReportCallback(
+                self.manager,
+                Monitor::<F>::on_input_report,
+                context,
+            );
+
+            IOHIDManagerScheduleWithRunLoop(
+                self.manager,
+                CFRunLoopGetCurrent(),
+                kCFRunLoopDefaultMode,
+            );
+
+            let rv = IOHIDManagerOpen(self.manager, kIOHIDManagerOptionNone);
+            if rv == 0 {
+                Ok(())
+            } else {
+                Err(io_err(&format!("Couldn't open HID Manager, rv={}", rv)))
+            }
+        }
     }
 
-    pub fn alive(&self) -> bool {
-        self.thread.alive()
+    pub fn stop(&mut self) {
+        // Remove all devices.
+        while !self.map.is_empty() {
+            let device_ref = *self.map.keys().next().unwrap();
+            self.remove_device(&device_ref);
+        }
+
+        // Close the manager and its devices.
+        unsafe { IOHIDManagerClose(self.manager, kIOHIDManagerOptionNone) };
+    }
+
+    fn remove_device(&mut self, device_ref: &IOHIDDeviceRef) {
+        if let Some(DeviceData { tx, runloop }) = self.map.remove(device_ref) {
+            // Dropping `tx` will make Device::read() fail eventually.
+            drop(tx);
+
+            // Wait until the runloop stopped.
+            runloop.cancel();
+        }
     }
 
-    extern "C" fn device_add_cb(
+    extern "C" fn on_input_report(
+        context: *mut c_void,
+        _: IOReturn,
+        device_ref: IOHIDDeviceRef,
+        _: IOHIDReportType,
+        _: u32,
+        report: *mut u8,
+        report_len: CFIndex,
+    ) {
+        let this = unsafe { &mut *(context as *mut Self) };
+        let mut send_failed = false;
+
+        // Ignore the report if we can't find a device for it.
+        if let Some(&DeviceData { ref tx, .. }) = this.map.get(&device_ref) {
+            let data = unsafe { slice::from_raw_parts(report, report_len as usize).to_vec() };
+            send_failed = tx.send(data).is_err();
+        }
+
+        // Remove the device if sending fails.
+        if send_failed {
+            this.remove_device(&device_ref);
+        }
+    }
+
+    extern "C" fn on_device_matching(
         context: *mut c_void,
         _: IOReturn,
         _: *mut c_void,
-        device: IOHIDDeviceRef,
+        device_ref: IOHIDDeviceRef,
     ) {
-        let tx = unsafe { &*(context as *mut Sender<Event>) };
-        let _ = tx.send(Event::Add(device));
+        let this = unsafe { &mut *(context as *mut Self) };
+
+        let (tx, rx) = channel();
+        let f = &this.new_device_cb;
+
+        // Create a new per-device runloop.
+        let runloop = RunLoop::new(move |alive| {
+            // Ensure that the runloop is still alive.
+            if alive() {
+                f(device_ref, rx, alive);
+            }
+        });
+
+        if let Ok(runloop) = runloop {
+            this.map.insert(device_ref, DeviceData { tx, runloop });
+        }
     }
 
-    extern "C" fn device_remove_cb(
+    extern "C" fn on_device_removal(
         context: *mut c_void,
         _: IOReturn,
         _: *mut c_void,
-        device: IOHIDDeviceRef,
+        device_ref: IOHIDDeviceRef,
     ) {
-        let tx = unsafe { &*(context as *mut Sender<Event>) };
-        let _ = tx.send(Event::Remove(device));
+        let this = unsafe { &mut *(context as *mut Self) };
+        this.remove_device(&device_ref);
     }
 }
 
-impl Drop for Monitor {
+impl<F> Drop for Monitor<F>
+where
+    F: Fn(IOHIDDeviceRef, Receiver<Vec<u8>>, &Fn() -> bool) + Sync,
+{
     fn drop(&mut self) {
-        debug!("OSX Runloop dropped");
-        self.thread.cancel();
+        unsafe { CFRelease(self.manager as *mut libc::c_void) };
     }
 }
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/u2f-hid-rs/src/macos/transaction.rs
@@ -0,0 +1,83 @@
+/* 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/. */
+
+extern crate libc;
+
+use core_foundation_sys::runloop::*;
+use libc::c_void;
+use platform::iohid::CFRunLoopEntryObserver;
+use platform::iokit::{IOHIDDeviceRef, SendableRunLoop};
+use platform::monitor::Monitor;
+use std::io;
+use std::sync::mpsc::{channel, Receiver, Sender};
+use std::thread;
+use util::{io_err, to_io_err, OnceCallback};
+
+// A transaction will run the given closure in a new thread, thereby using a
+// separate per-thread state machine for each HID. It will either complete or
+// fail through user action, timeout, or be cancelled when overridden by a new
+// transaction.
+pub struct Transaction {
+    runloop: SendableRunLoop,
+    thread: Option<thread::JoinHandle<()>>,
+}
+
+impl Transaction {
+    pub fn new<F, T>(timeout: u64, callback: OnceCallback<T>, new_device_cb: F) -> io::Result<Self>
+    where
+        F: Fn(IOHIDDeviceRef, Receiver<Vec<u8>>, &Fn() -> bool) + Sync + Send + 'static,
+        T: 'static,
+    {
+        let (tx, rx) = channel();
+        let cbc = callback.clone();
+        let timeout = (timeout as f64) / 1000.0;
+
+        let builder = thread::Builder::new();
+        let thread = builder.spawn(move || {
+            // Add a runloop observer that will be notified when we enter the
+            // runloop and tx.send() the current runloop to the owning thread.
+            // We need to ensure the runloop was entered before unblocking
+            // Transaction::new(), so we can always properly cancel.
+            let context = &tx as *const _ as *mut c_void;
+            let obs = CFRunLoopEntryObserver::new(Transaction::observe, context);
+            obs.add_to_current_runloop();
+
+            // Create a new HID device monitor and start polling.
+            let mut monitor = Monitor::new(new_device_cb);
+            try_or!(monitor.start(), |e| cbc.call(Err(e)));
+
+            // This will block until completion, abortion, or timeout.
+            unsafe { CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, 0) };
+
+            // Close the monitor and its devices.
+            monitor.stop();
+
+            // Send an error, if the callback wasn't called already.
+            cbc.call(Err(io_err("aborted or timed out")));
+        })?;
+
+        // Block until we enter the CFRunLoop.
+        let runloop = rx.recv().map_err(to_io_err)?;
+
+        Ok(Self {
+            runloop,
+            thread: Some(thread),
+        })
+    }
+
+    extern "C" fn observe(_: CFRunLoopObserverRef, _: CFRunLoopActivity, context: *mut c_void) {
+        let tx: &Sender<SendableRunLoop> = unsafe { &*(context as *mut _) };
+
+        // Send the current runloop to the receiver to unblock it.
+        let _ = tx.send(SendableRunLoop(unsafe { CFRunLoopGetCurrent() }));
+    }
+
+    pub fn cancel(&mut self) {
+        // (This call doesn't block.)
+        unsafe { CFRunLoopStop(*self.runloop) };
+
+        // This must never be None. Ignore return value.
+        let _ = self.thread.take().unwrap().join();
+    }
+}
--- a/dom/webidl/WebAuthentication.webidl
+++ b/dom/webidl/WebAuthentication.webidl
@@ -14,17 +14,17 @@ interface PublicKeyCredential : Credenti
     [SameObject] readonly attribute ArrayBuffer              rawId;
     [SameObject] readonly attribute AuthenticatorResponse    response;
     // Extensions are not supported yet.
     // [SameObject] readonly attribute AuthenticationExtensions clientExtensionResults; // Add in Bug 1406458
 };
 
 [SecureContext]
 partial interface PublicKeyCredential {
-    static Promise<boolean> isPlatformAuthenticatorAvailable();
+    static Promise<boolean> isUserVerifyingPlatformAuthenticatorAvailable();
 };
 
 [SecureContext, Pref="security.webauth.webauthn"]
 interface AuthenticatorResponse {
     [SameObject] readonly attribute ArrayBuffer clientDataJSON;
 };
 
 [SecureContext, Pref="security.webauth.webauthn"]
@@ -119,9 +119,10 @@ enum AuthenticatorTransport {
     "nfc",
     "ble"
 };
 
 typedef long COSEAlgorithmIdentifier;
 
 typedef sequence<AAGUID>      AuthenticatorSelectionList;
 
-typedef BufferSource      AAGUID;
\ No newline at end of file
+typedef BufferSource      AAGUID;
+
--- a/dom/xul/XULDocument.cpp
+++ b/dom/xul/XULDocument.cpp
@@ -2155,25 +2155,25 @@ XULDocument::ApplyPersistentAttributesTo
             rv = element->SetAttr(kNameSpaceID_None, attr, value, PR_TRUE);
         }
     }
 
     return NS_OK;
 }
 
 void
-XULDocument::TraceProtos(JSTracer* aTrc, uint32_t aGCNumber)
+XULDocument::TraceProtos(JSTracer* aTrc)
 {
     uint32_t i, count = mPrototypes.Length();
     for (i = 0; i < count; ++i) {
-        mPrototypes[i]->TraceProtos(aTrc, aGCNumber);
+        mPrototypes[i]->TraceProtos(aTrc);
     }
 
     if (mCurrentPrototype) {
-        mCurrentPrototype->TraceProtos(aTrc, aGCNumber);
+        mCurrentPrototype->TraceProtos(aTrc);
     }
 }
 
 //----------------------------------------------------------------------
 //
 // XULDocument::ContextStack
 //
 
--- a/dom/xul/XULDocument.h
+++ b/dom/xul/XULDocument.h
@@ -183,17 +183,17 @@ public:
     static bool
     MatchAttribute(Element* aContent,
                    int32_t aNameSpaceID,
                    nsAtom* aAttrName,
                    void* aData);
 
     NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(XULDocument, XMLDocument)
 
-    void TraceProtos(JSTracer* aTrc, uint32_t aGCNumber);
+    void TraceProtos(JSTracer* aTrc);
 
     // WebIDL API
     already_AddRefed<nsINode> GetPopupNode();
     void SetPopupNode(nsINode* aNode);
     already_AddRefed<nsINode> GetPopupRangeParent(ErrorResult& aRv);
     int32_t GetPopupRangeOffset(ErrorResult& aRv);
     already_AddRefed<nsINode> GetTooltipNode();
     void SetTooltipNode(nsINode* aNode) { /* do nothing */ }
--- a/dom/xul/nsXULPrototypeDocument.cpp
+++ b/dom/xul/nsXULPrototypeDocument.cpp
@@ -525,20 +525,23 @@ nsXULPrototypeDocument::NotifyLoadDone()
         if (NS_FAILED(rv)) break;
     }
     mPrototypeWaiters.Clear();
 
     return rv;
 }
 
 void
-nsXULPrototypeDocument::TraceProtos(JSTracer* aTrc, uint32_t aGCNumber)
+nsXULPrototypeDocument::TraceProtos(JSTracer* aTrc)
 {
-  // Only trace the protos once per GC.
-  if (mGCNumber == aGCNumber) {
-    return;
+  // Only trace the protos once per GC if we are marking.
+  if (aTrc->isMarkingTracer()) {
+    uint32_t currentGCNumber = aTrc->gcNumberForMarking();
+    if (mGCNumber == currentGCNumber) {
+      return;
+    }
+    mGCNumber = currentGCNumber;
   }
 
-  mGCNumber = aGCNumber;
   if (mRoot) {
     mRoot->TraceAllScripts(aTrc);
   }
 }
--- a/dom/xul/nsXULPrototypeDocument.h
+++ b/dom/xul/nsXULPrototypeDocument.h
@@ -109,17 +109,17 @@ public:
     nsresult NotifyLoadDone();
 
     nsNodeInfoManager *GetNodeInfoManager();
 
     void MarkInCCGeneration(uint32_t aCCGeneration);
 
     NS_DECL_CYCLE_COLLECTION_CLASS(nsXULPrototypeDocument)
 
-    void TraceProtos(JSTracer* aTrc, uint32_t aGCNumber);
+    void TraceProtos(JSTracer* aTrc);
 
 protected:
     nsCOMPtr<nsIURI> mURI;
     RefPtr<nsXULPrototypeElement> mRoot;
     nsTArray<RefPtr<nsXULPrototypePI> > mProcessingInstructions;
     nsCOMArray<nsIURI> mStyleSheetReferences;
 
     bool mLoaded;
--- a/gfx/layers/PaintThread.cpp
+++ b/gfx/layers/PaintThread.cpp
@@ -252,17 +252,19 @@ PaintThread::AsyncPrepareBuffer(Composit
     mInAsyncPaintGroup = true;
     PROFILER_TRACING("Paint", "Rasterize", TRACING_INTERVAL_START);
   }
 
   if (!aState->PrepareBuffer()) {
     gfxCriticalNote << "Failed to prepare buffers on the paint thread.";
   }
 
-  aBridge->NotifyFinishedAsyncPrepareBuffer(aState);
+  if (aBridge) {
+    aBridge->NotifyFinishedAsyncPrepareBuffer(aState);
+  }
 }
 
 void
 PaintThread::PaintContents(CapturedPaintState* aState,
                            PrepDrawTargetForPaintingCallback aCallback)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(aState);
--- a/gfx/layers/client/ContentClient.cpp
+++ b/gfx/layers/client/ContentClient.cpp
@@ -611,16 +611,20 @@ ContentClientBasic::CreateBuffer(gfxCont
 #endif
 
   if (!drawTarget) {
     drawTarget = gfxPlatform::GetPlatform()->CreateDrawTargetForBackend(
       mBackend, size,
       gfxPlatform::GetPlatform()->Optimal2DFormatForContent(aType));
   }
 
+  if (!drawTarget) {
+    return nullptr;
+  }
+
   return new DrawTargetRotatedBuffer(drawTarget, nullptr, aRect, IntPoint(0,0));
 }
 
 RefPtr<CapturedPaintState>
 ContentClientBasic::BorrowDrawTargetForRecording(ContentClient::PaintState& aPaintState,
                                                  RotatedBuffer::DrawIterator* aIter,
                                                  bool aSetTransform)
 {
--- a/image/imgLoader.cpp
+++ b/image/imgLoader.cpp
@@ -2903,16 +2903,22 @@ imgCacheValidator::AddProxy(imgRequestPr
 {
   // aProxy needs to be in the loadgroup since we're validating from
   // the network.
   aProxy->AddToLoadGroup();
 
   mProxies.AppendObject(aProxy);
 }
 
+void
+imgCacheValidator::RemoveProxy(imgRequestProxy* aProxy)
+{
+  mProxies.RemoveObject(aProxy);
+}
+
 /** nsIRequestObserver methods **/
 
 NS_IMETHODIMP
 imgCacheValidator::OnStartRequest(nsIRequest* aRequest, nsISupports* ctxt)
 {
   // We may be holding on to a document, so ensure that it's released.
   nsCOMPtr<nsISupports> context = mContext.forget();
 
--- a/image/imgLoader.h
+++ b/image/imgLoader.h
@@ -550,16 +550,17 @@ class imgCacheValidator : public nsIStre
                           public nsIAsyncVerifyRedirectCallback
 {
 public:
   imgCacheValidator(nsProgressNotificationProxy* progress, imgLoader* loader,
                     imgRequest* aRequest, nsISupports* aContext,
                     bool forcePrincipalCheckForCacheEntry);
 
   void AddProxy(imgRequestProxy* aProxy);
+  void RemoveProxy(imgRequestProxy* aProxy);
 
   NS_DECL_THREADSAFE_ISUPPORTS
   NS_DECL_NSITHREADRETARGETABLESTREAMLISTENER
   NS_DECL_NSISTREAMLISTENER
   NS_DECL_NSIREQUESTOBSERVER
   NS_DECL_NSICHANNELEVENTSINK
   NS_DECL_NSIINTERFACEREQUESTOR
   NS_DECL_NSIASYNCVERIFYREDIRECTCALLBACK
--- a/image/imgRequestProxy.cpp
+++ b/image/imgRequestProxy.cpp
@@ -512,18 +512,24 @@ imgRequestProxy::CancelAndForgetObserver
     return NS_ERROR_FAILURE;
   }
 
   LOG_SCOPE(gImgLog, "imgRequestProxy::CancelAndForgetObserver");
 
   mCanceled = true;
   mForceDispatchLoadGroup = true;
 
-  if (GetOwner()) {
-    GetOwner()->RemoveProxy(this, aStatus);
+  imgRequest* owner = GetOwner();
+  if (owner) {
+    imgCacheValidator* validator = owner->GetValidator();
+    if (validator) {
+      validator->RemoveProxy(this);
+    }
+
+    owner->RemoveProxy(this, aStatus);
   }
 
   RemoveFromLoadGroup();
   mForceDispatchLoadGroup = false;
 
   NullOutListener();
 
   return NS_OK;
--- a/js/public/TracingAPI.h
+++ b/js/public/TracingAPI.h
@@ -86,16 +86,20 @@ class JS_PUBLIC_API(JSTracer)
     bool isTenuringTracer() const { return tag_ == TracerKindTag::Tenuring; }
     bool isCallbackTracer() const { return tag_ == TracerKindTag::Callback; }
     inline JS::CallbackTracer* asCallbackTracer();
     bool traceWeakEdges() const { return traceWeakEdges_; }
 #ifdef DEBUG
     bool checkEdges() { return checkEdges_; }
 #endif
 
+    // Get the current GC number. Only call this method if |isMarkingTracer()|
+    // is true.
+    uint32_t gcNumberForMarking() const;
+
   protected:
     JSTracer(JSRuntime* rt, TracerKindTag tag,
              WeakMapTraceKind weakTraceKind = TraceWeakMapValues)
       : runtime_(rt)
       , weakMapAction_(weakTraceKind)
 #ifdef DEBUG
       , checkEdges_(true)
 #endif
--- a/js/src/gc/Tracer.cpp
+++ b/js/src/gc/Tracer.cpp
@@ -479,8 +479,15 @@ JS_GetTraceThingInfo(char* buf, size_t b
         }
     }
     buf[bufsize - 1] = '\0';
 }
 
 JS::CallbackTracer::CallbackTracer(JSContext* cx, WeakMapTraceKind weakTraceKind)
   : CallbackTracer(cx->runtime(), weakTraceKind)
 {}
+
+uint32_t
+JSTracer::gcNumberForMarking() const
+{
+    MOZ_ASSERT(isMarkingTracer());
+    return runtime()->gc.gcNumber();
+}
--- a/js/xpconnect/src/XPCJSRuntime.cpp
+++ b/js/xpconnect/src/XPCJSRuntime.cpp
@@ -650,19 +650,17 @@ bool XPCJSRuntime::UsefulToMergeZones() 
 void XPCJSRuntime::TraceNativeBlackRoots(JSTracer* trc)
 {
     for (CycleCollectedJSContext* ccx : Contexts()) {
         auto* cx = static_cast<const XPCJSContext*>(ccx);
         if (AutoMarkingPtr* roots = cx->mAutoRoots)
             roots->TraceJSAll(trc);
     }
 
-    JSContext* cx = XPCJSContext::Get()->Context();
-    dom::TraceBlackJS(trc, JS_GetGCParameter(cx, JSGC_NUMBER),
-                      nsXPConnect::XPConnect()->IsShuttingDown());
+    dom::TraceBlackJS(trc, nsXPConnect::XPConnect()->IsShuttingDown());
 }
 
 void XPCJSRuntime::TraceAdditionalNativeGrayRoots(JSTracer* trc)
 {
     XPCWrappedNativeScope::TraceWrappedNativesInAllScopes(trc);
 
     for (XPCRootSetElem* e = mVariantRoots; e ; e = e->GetNextRoot())
         static_cast<XPCTraceableVariant*>(e)->TraceJS(trc);
--- a/toolkit/modules/GMPInstallManager.jsm
+++ b/toolkit/modules/GMPInstallManager.jsm
@@ -424,17 +424,16 @@ GMPDownloader.prototype = {
   start() {
     let log = getScopedLogger("GMPDownloader");
     let gmpAddon = this._gmpAddon;
 
     if (!gmpAddon.isValid) {
       log.info("gmpAddon is not valid, will not continue");
       return Promise.reject({
         target: this,
-        status,
         type: "downloaderr"
       });
     }
 
     return ProductAddonChecker.downloadAddon(gmpAddon).then((zipPath) => {
       let relativePath = OS.Path.join(gmpAddon.id,
                                       gmpAddon.version);
       log.info("install to directory path: " + relativePath);