Merge autoland to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Fri, 09 Jun 2017 15:48:12 -0700
changeset 411410 c4e74cfbf7e9d8e297e214478d25e3456f858cea
parent 411382 f0c05f5e4dda7ac9d0cf597c5c3f3634a73f21e3 (current diff)
parent 411409 3c876e859603f37750de4725546d2c1dddf05e31 (diff)
child 411424 3f12cc772ae84e7d8ab6ca959b6b2f86809c0785
child 411444 f4b350008c452a890b207afac42fbc530e1e8ec0
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone55.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
Merge autoland to central, a=merge MozReview-Commit-ID: 1k7iJedKGDR
dom/webauthn/ScopedCredential.cpp
dom/webauthn/ScopedCredential.h
dom/webauthn/ScopedCredentialInfo.cpp
dom/webauthn/ScopedCredentialInfo.h
dom/webauthn/WebAuthentication.cpp
dom/webauthn/WebAuthentication.h
dom/webauthn/WebAuthnAssertion.cpp
dom/webauthn/WebAuthnAssertion.h
dom/webauthn/WebAuthnAttestation.cpp
dom/webauthn/WebAuthnAttestation.h
--- a/browser/locales/filter.py
+++ b/browser/locales/filter.py
@@ -13,17 +13,17 @@ def test(mod, path, entity = None):
                  "extensions/spellcheck",
                  "other-licenses/branding/firefox",
                  "browser/branding/official",
                  "services/sync"):
     return "ignore"
   if mod not in ("browser", "extensions/spellcheck"):
     # we only have exceptions for browser and extensions/spellcheck
     return "error"
-  if not entity:
+  if entity is None:
     # the only files to ignore are spell checkers
     if mod == "extensions/spellcheck":
       return "ignore"
     return "error"
   if mod == "extensions/spellcheck":
     # l10n ships en-US dictionary or something, do compare
     return "error"
   if path == "defines.inc":
new file mode 100644
--- /dev/null
+++ b/browser/locales/l10n.toml
@@ -0,0 +1,169 @@
+# 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/.
+
+basepath = "../.."
+
+locales = [
+    "ach",
+    "af",
+    "an",
+    "ar",
+    "as",
+    "ast",
+    "az",
+    "be",
+    "bg",
+    "bn-BD",
+    "bn-IN",
+    "br",
+    "bs",
+    "ca",
+    "cak",
+    "cs",
+    "cy",
+    "da",
+    "de",
+    "dsb",
+    "el",
+    "en-GB",
+    "en-ZA",
+    "eo",
+    "es-AR",
+    "es-CL",
+    "es-ES",
+    "es-MX",
+    "et",
+    "eu",
+    "fa",
+    "ff",
+    "fi",
+    "fr",
+    "fy-NL",
+    "ga-IE",
+    "gd",
+    "gl",
+    "gn",
+    "gu-IN",
+    "he",
+    "hi-IN",
+    "hr",
+    "hsb",
+    "hu",
+    "hy-AM",
+    "id",
+    "is",
+    "it",
+    "ja",
+    "ja-JP-mac",
+    "ka",
+    "kab",
+    "kk",
+    "km",
+    "kn",
+    "ko",
+    "lij",
+    "lo",
+    "lt",
+    "ltg",
+    "lv",
+    "mai",
+    "mk",
+    "ml",
+    "mr",
+    "ms",
+    "my",
+    "nb-NO",
+    "ne-NP",
+    "nl",
+    "nn-NO",
+    "or",
+    "pa-IN",
+    "pl",
+    "pt-BR",
+    "pt-PT",
+    "rm",
+    "ro",
+    "ru",
+    "si",
+    "sk",
+    "sl",
+    "son",
+    "sq",
+    "sr",
+    "sv-SE",
+    "ta",
+    "te",
+    "th",
+    "tl",
+    "tr",
+    "uk",
+    "ur",
+    "uz",
+    "vi",
+    "xh",
+    "zh-CN",
+    "zh-TW",
+    ]
+
+[env]
+    l = "{l10n_base}/{locale}/"
+
+
+[[paths]]
+    reference = "browser/locales/en-US/**"
+    l10n = "{l}browser/**"
+
+[[paths]]
+    reference = "browser/branding/official/locales/en-US/**"
+    l10n = "{l}browser/branding/official/**"
+
+[[paths]]
+    reference = "browser/extensions/onboarding/locales/en-US/**"
+    l10n = "{l}browser/extensions/onboarding/**"
+
+[[paths]]
+    reference = "browser/extensions/webcompat-reporter/locales/en-US/**"
+    l10n = "{l}browser/extensions/webcompat-reporter/**"
+
+[[paths]]
+    reference = "services/sync/locales/en-US/**"
+    l10n = "{l}services/sync/**"
+
+
+[[includes]]
+    path = "toolkit/locales/l10n.toml"
+
+[[includes]]
+    path = "devtools/client/locales/l10n.toml"
+
+# Filters
+# The filters below are evaluated one after the other, in the given order.
+# Enter a combination of path as in the localization, key, and an action,
+# to change the default behavior of compare-locales and l10n merge.
+#
+# For browser/locales/en-US/chrome/browser/foo.properties,
+# path would be {l}browser/chrome/browser/foo.properties
+# key: the key/id of the entity
+# If key isn't specified, the complete file can be missing.
+[[filters]]
+    path = "{l}browser/defines.inc"
+    key = "MOZ_LANGPACK_CONTRIBUTORS"
+    action = "ignore"
+
+[[filters]]
+    path = [
+        "{l}browser/defines.inc",
+        "{l}browser/firefox-l10n.js",
+    ]
+    action = "ignore"
+
+[[filters]]
+    path = "{l}browser/chrome/browser-region/region.properties"
+    key = [
+        "re:^browser\\.search\\.order\\.[1-9]$",
+        "re:^browser\\.contentHandlers\\.types\\.[0-5]\\..*$",
+        "re:^gecko\\.handlerService\\.schemes\\..*$",
+        "re:^gecko\\.handlerService\\.defaultHandlersVersion$"
+    ]
+    action = "ignore"
new file mode 100644
--- /dev/null
+++ b/devtools/client/locales/l10n.toml
@@ -0,0 +1,12 @@
+# 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/.
+
+basepath = "../../.."
+
+[env]
+    l = "{l10n_base}/{locale}/"
+
+[[paths]]
+    reference = "devtools/client/locales/en-US/**"
+    l10n = "{l}devtools/client/**"
new file mode 100644
--- /dev/null
+++ b/devtools/shared/locales/l10n.toml
@@ -0,0 +1,12 @@
+# 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/.
+
+basepath = "../../.."
+
+[env]
+    l = "{l10n_base}/{locale}/"
+
+[[paths]]
+    reference = "devtools/shared/locales/en-US/**"
+    l10n = "{l}devtools/shared/**"
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -28,32 +28,32 @@
 #include "nsICookiePermission.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsCharSeparatedTokenizer.h"
 #include "nsContentUtils.h"
 #include "nsUnicharUtils.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Telemetry.h"
 #include "BatteryManager.h"
+#include "mozilla/dom/CredentialsContainer.h"
 #include "mozilla/dom/GamepadServiceTest.h"
 #include "mozilla/dom/PowerManager.h"
 #include "mozilla/dom/WakeLock.h"
 #include "mozilla/dom/power/PowerManagerService.h"
 #include "mozilla/dom/FlyWebPublishedServer.h"
 #include "mozilla/dom/FlyWebService.h"
 #include "mozilla/dom/Permissions.h"
 #include "mozilla/dom/Presentation.h"
 #include "mozilla/dom/ServiceWorkerContainer.h"
 #include "mozilla/dom/StorageManager.h"
 #include "mozilla/dom/TCPSocket.h"
 #include "mozilla/dom/URLSearchParams.h"
 #include "mozilla/dom/VRDisplay.h"
 #include "mozilla/dom/VRDisplayEvent.h"
 #include "mozilla/dom/VRServiceTest.h"
-#include "mozilla/dom/WebAuthentication.h"
 #include "mozilla/dom/workers/RuntimeService.h"
 #include "mozilla/Hal.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/SSE.h"
 #include "mozilla/StaticPtr.h"
 #include "Connection.h"
 #include "mozilla/dom/Event.h" // for nsIDOMEvent::InternalDOMEvent()
 #include "nsGlobalWindow.h"
@@ -200,17 +200,17 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPermissions)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGeolocation)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mNotification)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBatteryManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mBatteryPromise)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPowerManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConnection)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mStorageManager)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mAuthentication)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mCredentials)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaDevices)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mTimeManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServiceWorkerContainer)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeySystemAccessManager)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPresentation)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGamepadServiceTest)
@@ -1996,19 +1996,19 @@ Navigator::GetPresentation(ErrorResult& 
       return nullptr;
     }
     mPresentation = Presentation::Create(mWindow);
   }
 
   return mPresentation;
 }
 
-WebAuthentication*
-Navigator::Authentication()
+CredentialsContainer*
+Navigator::Credentials()
 {
-  if (!mAuthentication) {
-    mAuthentication = new WebAuthentication(GetWindow());
+  if (!mCredentials) {
+    mCredentials = new CredentialsContainer(GetWindow());
   }
-  return mAuthentication;
+  return mCredentials;
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -37,17 +37,17 @@ class systemMessageCallback;
 class MediaDevices;
 struct MediaStreamConstraints;
 class WakeLock;
 class ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUSVStringOrURLSearchParams;
 class ServiceWorkerContainer;
 class DOMRequest;
 struct FlyWebPublishOptions;
 struct FlyWebFilter;
-class WebAuthentication;
+class CredentialsContainer;
 } // namespace dom
 } // namespace mozilla
 
 //*****************************************************************************
 // Navigator: Script "navigator" object
 //*****************************************************************************
 
 namespace mozilla {
@@ -220,17 +220,17 @@ public:
                               MozGetUserMediaDevicesSuccessCallback& aOnSuccess,
                               NavigatorUserMediaErrorCallback& aOnError,
                               uint64_t aInnerWindowID,
                               const nsAString& aCallID,
                               ErrorResult& aRv);
 
   already_AddRefed<ServiceWorkerContainer> ServiceWorker();
 
-  mozilla::dom::WebAuthentication* Authentication();
+  mozilla::dom::CredentialsContainer* Credentials();
 
   void GetLanguages(nsTArray<nsString>& aLanguages);
 
   bool MozE10sEnabled();
 
   StorageManager* Storage();
 
   static void GetAcceptLanguages(nsTArray<nsString>& aLanguages);
@@ -289,17 +289,17 @@ private:
   RefPtr<nsPluginArray> mPlugins;
   RefPtr<Permissions> mPermissions;
   RefPtr<Geolocation> mGeolocation;
   RefPtr<DesktopNotificationCenter> mNotification;
   RefPtr<battery::BatteryManager> mBatteryManager;
   RefPtr<Promise> mBatteryPromise;
   RefPtr<PowerManager> mPowerManager;
   RefPtr<network::Connection> mConnection;
-  RefPtr<WebAuthentication> mAuthentication;
+  RefPtr<CredentialsContainer> mCredentials;
   RefPtr<MediaDevices> mMediaDevices;
   RefPtr<time::TimeManager> mTimeManager;
   RefPtr<ServiceWorkerContainer> mServiceWorkerContainer;
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
   RefPtr<Presentation> mPresentation;
   RefPtr<GamepadServiceTest> mGamepadServiceTest;
   nsTArray<RefPtr<Promise> > mVRGetDisplaysPromises;
   RefPtr<VRServiceTest> mVRServiceTest;
new file mode 100644
--- /dev/null
+++ b/dom/credentialmanagement/Credential.cpp
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/dom/Credential.h"
+#include "mozilla/dom/CredentialManagementBinding.h"
+#include "nsCycleCollectionParticipant.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Credential, mParent)
+
+NS_IMPL_CYCLE_COLLECTING_ADDREF(Credential)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(Credential)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Credential)
+  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+Credential::Credential(nsPIDOMWindowInner* aParent)
+  : mParent(aParent)
+{}
+
+Credential::~Credential()
+{}
+
+JSObject*
+Credential::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+{
+  return CredentialBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+Credential::GetId(nsAString& aId) const
+{
+  aId.Assign(mId);
+}
+
+void
+Credential::GetType(nsAString& aType) const
+{
+  aType.Assign(mType);
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/credentialmanagement/Credential.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#ifndef mozilla_dom_Credential_h
+#define mozilla_dom_Credential_h
+
+#include "mozilla/dom/CredentialManagementBinding.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsPIDOMWindow.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+
+class Credential : public nsISupports
+                 , public nsWrapperCache
+{
+public:
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(Credential)
+
+public:
+  explicit Credential(nsPIDOMWindowInner* aParent);
+
+protected:
+  virtual ~Credential();
+
+public:
+  nsISupports*
+  GetParentObject() const
+  {
+    return mParent;
+  }
+
+  JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  void
+  GetId(nsAString& aId) const;
+
+  void
+  GetType(nsAString& aType) const;
+
+private:
+  nsCOMPtr<nsPIDOMWindowInner> mParent;
+  nsAutoString mId;
+  nsAutoString mType;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_Credential_h
rename from dom/webauthn/WebAuthentication.cpp
rename to dom/credentialmanagement/CredentialsContainer.cpp
--- a/dom/webauthn/WebAuthentication.cpp
+++ b/dom/credentialmanagement/CredentialsContainer.cpp
@@ -1,58 +1,54 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/dom/WebAuthentication.h"
+#include "mozilla/dom/CredentialsContainer.h"
+#include "mozilla/dom/Promise.h"
 #include "mozilla/dom/WebAuthnManager.h"
 
 namespace mozilla {
 namespace dom {
 
-// Only needed for refcounted objects.
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebAuthentication, mParent)
-NS_IMPL_CYCLE_COLLECTING_ADDREF(WebAuthentication)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(WebAuthentication)
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebAuthentication)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CredentialsContainer, mParent)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(CredentialsContainer)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(CredentialsContainer)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(CredentialsContainer)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
-WebAuthentication::WebAuthentication(nsPIDOMWindowInner* aParent) :
+CredentialsContainer::CredentialsContainer(nsPIDOMWindowInner* aParent) :
   mParent(aParent)
 {
   MOZ_ASSERT(aParent);
 }
 
-WebAuthentication::~WebAuthentication()
+CredentialsContainer::~CredentialsContainer()
 {}
 
 JSObject*
-WebAuthentication::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+CredentialsContainer::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
-  return WebAuthenticationBinding::Wrap(aCx, this, aGivenProto);
+  return CredentialsContainerBinding::Wrap(aCx, this, aGivenProto);
 }
 
 already_AddRefed<Promise>
-WebAuthentication::MakeCredential(JSContext* aCx, const Account& aAccount,
-                                  const Sequence<ScopedCredentialParameters>& aCryptoParameters,
-                                  const ArrayBufferViewOrArrayBuffer& aChallenge,
-                                  const ScopedCredentialOptions& aOptions)
+CredentialsContainer::Get(const CredentialRequestOptions& aOptions)
 {
   RefPtr<WebAuthnManager> mgr = WebAuthnManager::GetOrCreate();
   MOZ_ASSERT(mgr);
-  return mgr->MakeCredential(mParent, aCx, aAccount, aCryptoParameters, aChallenge, aOptions);
+  return mgr->GetAssertion(mParent, aOptions.mPublicKey);
 }
 
 already_AddRefed<Promise>
-WebAuthentication::GetAssertion(const ArrayBufferViewOrArrayBuffer& aChallenge,
-                                const AssertionOptions& aOptions)
+CredentialsContainer::Create(const CredentialCreationOptions& aOptions)
 {
   RefPtr<WebAuthnManager> mgr = WebAuthnManager::GetOrCreate();
   MOZ_ASSERT(mgr);
-  return mgr->GetAssertion(mParent, aChallenge, aOptions);
+  return mgr->MakeCredential(mParent, aOptions.mPublicKey);
 }
 
 } // namespace dom
 } // namespace mozilla
rename from dom/webauthn/WebAuthentication.h
rename to dom/credentialmanagement/CredentialsContainer.h
--- a/dom/webauthn/WebAuthentication.h
+++ b/dom/credentialmanagement/CredentialsContainer.h
@@ -1,66 +1,47 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/. */
 
-#ifndef mozilla_dom_WebAuthentication_h
-#define mozilla_dom_WebAuthentication_h
+#ifndef mozilla_dom_CredentialsContainer_h
+#define mozilla_dom_CredentialsContainer_h
 
-#include "mozilla/dom/WebAuthenticationBinding.h"
+#include "mozilla/dom/CredentialManagementBinding.h"
 
 namespace mozilla {
 namespace dom {
 
-struct Account;
-class ArrayBufferViewOrArrayBuffer;
-struct AssertionOptions;
-struct ScopedCredentialOptions;
-struct ScopedCredentialParameters;
-
-} // namespace dom
-} // namespace mozilla
-
-namespace mozilla {
-namespace dom {
-
-class WebAuthentication final : public nsISupports
-                              , public nsWrapperCache
+class CredentialsContainer final : public nsISupports
+                                 , public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WebAuthentication)
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(CredentialsContainer)
 
-  explicit WebAuthentication(nsPIDOMWindowInner* aParent);
+  explicit CredentialsContainer(nsPIDOMWindowInner* aParent);
 
   nsPIDOMWindowInner*
   GetParentObject() const
   {
     return mParent;
   }
 
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   already_AddRefed<Promise>
-  MakeCredential(JSContext* aCx, const Account& accountInformation,
-                 const Sequence<ScopedCredentialParameters>& cryptoParameters,
-                 const ArrayBufferViewOrArrayBuffer& attestationChallenge,
-                 const ScopedCredentialOptions& options);
-
+  Get(const CredentialRequestOptions& aOptions);
   already_AddRefed<Promise>
-  GetAssertion(const ArrayBufferViewOrArrayBuffer& assertionChallenge,
-               const AssertionOptions& options);
+  Create(const CredentialCreationOptions& aOptions);
+
 private:
-  ~WebAuthentication();
-
-  already_AddRefed<Promise> CreatePromise();
-  nsresult GetOrigin(/*out*/ nsAString& aOrigin);
+  ~CredentialsContainer();
 
   nsCOMPtr<nsPIDOMWindowInner> mParent;
 };
 
 } // namespace dom
 } // namespace mozilla
 
-#endif // mozilla_dom_WebAuthentication_h
+#endif // mozilla_dom_CredentialsContainer_h
new file mode 100644
--- /dev/null
+++ b/dom/credentialmanagement/moz.build
@@ -0,0 +1,22 @@
+# -*- 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/.
+
+with Files("**"):
+    BUG_COMPONENT = ("Core", "DOM: Device Interfaces")
+
+EXPORTS.mozilla.dom += [
+    'Credential.h',
+    'CredentialsContainer.h',
+]
+
+UNIFIED_SOURCES += [
+    'Credential.cpp',
+    'CredentialsContainer.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
--- a/dom/moz.build
+++ b/dom/moz.build
@@ -45,16 +45,17 @@ DIRS += [
     'animation',
     'base',
     'bindings',
     'battery',
     'browser-element',
     'cache',
     'canvas',
     'commandhandler',
+    'credentialmanagement',
     'crypto',
     'encoding',
     'events',
     'fetch',
     'file',
     'filehandle',
     'filesystem',
     'flyweb',
rename from dom/webauthn/WebAuthnAssertion.cpp
rename to dom/webauthn/AuthenticatorAssertionResponse.cpp
--- a/dom/webauthn/WebAuthnAssertion.cpp
+++ b/dom/webauthn/AuthenticatorAssertionResponse.cpp
@@ -1,98 +1,66 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/dom/WebAuthenticationBinding.h"
-#include "mozilla/dom/WebAuthnAssertion.h"
+#include "mozilla/dom/AuthenticatorAssertionResponse.h"
 
 namespace mozilla {
 namespace dom {
 
-// Only needed for refcounted objects.
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebAuthnAssertion, mParent)
-NS_IMPL_CYCLE_COLLECTING_ADDREF(WebAuthnAssertion)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(WebAuthnAssertion)
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebAuthnAssertion)
-  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
+NS_IMPL_ADDREF_INHERITED(AuthenticatorAssertionResponse, AuthenticatorResponse)
+NS_IMPL_RELEASE_INHERITED(AuthenticatorAssertionResponse, AuthenticatorResponse)
 
-WebAuthnAssertion::WebAuthnAssertion(nsPIDOMWindowInner* aParent)
-  : mParent(aParent)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AuthenticatorAssertionResponse)
+NS_INTERFACE_MAP_END_INHERITING(AuthenticatorResponse)
+
+AuthenticatorAssertionResponse::AuthenticatorAssertionResponse(nsPIDOMWindowInner* aParent)
+  : AuthenticatorResponse(aParent)
 {}
 
-WebAuthnAssertion::~WebAuthnAssertion()
+AuthenticatorAssertionResponse::~AuthenticatorAssertionResponse()
 {}
 
 JSObject*
-WebAuthnAssertion::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
+AuthenticatorAssertionResponse::WrapObject(JSContext* aCx,
+                                           JS::Handle<JSObject*> aGivenProto)
 {
-  return WebAuthnAssertionBinding::Wrap(aCx, this, aGivenProto);
-}
-
-already_AddRefed<ScopedCredential>
-WebAuthnAssertion::Credential() const
-{
-  RefPtr<ScopedCredential> temp(mCredential);
-  return temp.forget();
+  return AuthenticatorAssertionResponseBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
-WebAuthnAssertion::GetClientData(JSContext* aCx,
-                                 JS::MutableHandle<JSObject*> aRetVal) const
-{
-  aRetVal.set(mClientData.ToUint8Array(aCx));
-}
-
-void
-WebAuthnAssertion::GetAuthenticatorData(JSContext* aCx,
-                                        JS::MutableHandle<JSObject*> aRetVal) const
+AuthenticatorAssertionResponse::GetAuthenticatorData(JSContext* aCx,
+                                                     JS::MutableHandle<JSObject*> aRetVal) const
 {
   aRetVal.set(mAuthenticatorData.ToUint8Array(aCx));
 }
 
+nsresult
+AuthenticatorAssertionResponse::SetAuthenticatorData(CryptoBuffer& aBuffer)
+{
+  if (NS_WARN_IF(!mAuthenticatorData.Assign(aBuffer))) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  return NS_OK;
+}
+
 void
-WebAuthnAssertion::GetSignature(JSContext* aCx,
-                                JS::MutableHandle<JSObject*> aRetVal) const
+AuthenticatorAssertionResponse::GetSignature(JSContext* aCx,
+                                             JS::MutableHandle<JSObject*> aRetVal) const
 {
   aRetVal.set(mSignature.ToUint8Array(aCx));
 }
 
 nsresult
-WebAuthnAssertion::SetCredential(RefPtr<ScopedCredential> aCredential)
-{
-  mCredential = aCredential;
-  return NS_OK;
-}
-
-nsresult
-WebAuthnAssertion::SetClientData(CryptoBuffer& aBuffer)
+AuthenticatorAssertionResponse::SetSignature(CryptoBuffer& aBuffer)
 {
-  if (!mClientData.Assign(aBuffer)) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-  return NS_OK;
-}
-
-nsresult
-WebAuthnAssertion::SetAuthenticatorData(CryptoBuffer& aBuffer)
-{
-  if (!mAuthenticatorData.Assign(aBuffer)) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-  return NS_OK;
-}
-
-nsresult
-WebAuthnAssertion::SetSignature(CryptoBuffer& aBuffer)
-{
-  if (!mSignature.Assign(aBuffer)) {
+  if (NS_WARN_IF(!mSignature.Assign(aBuffer))) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   return NS_OK;
 }
 
 } // namespace dom
 } // namespace mozilla
rename from dom/webauthn/WebAuthnAssertion.h
rename to dom/webauthn/AuthenticatorAssertionResponse.h
--- a/dom/webauthn/WebAuthnAssertion.h
+++ b/dom/webauthn/AuthenticatorAssertionResponse.h
@@ -1,86 +1,56 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/. */
 
-#ifndef mozilla_dom_WebAuthnAssertion_h
-#define mozilla_dom_WebAuthnAssertion_h
+#ifndef mozilla_dom_AuthenticatorAssertionResponse_h
+#define mozilla_dom_AuthenticatorAssertionResponse_h
 
 #include "js/TypeDecls.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ErrorResult.h"
+#include "mozilla/dom/AuthenticatorResponse.h"
 #include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/CryptoBuffer.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsWrapperCache.h"
 
 namespace mozilla {
 namespace dom {
 
-class ScopedCredential;
-
-} // namespace dom
-} // namespace mozilla
-
-namespace mozilla {
-namespace dom {
-
-class WebAuthnAssertion final : public nsISupports
-                              , public nsWrapperCache
+class AuthenticatorAssertionResponse final : public AuthenticatorResponse
 {
 public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WebAuthnAssertion)
+  NS_DECL_ISUPPORTS_INHERITED
 
-public:
-  explicit WebAuthnAssertion(nsPIDOMWindowInner* aParent);
+  explicit AuthenticatorAssertionResponse(nsPIDOMWindowInner* aParent);
 
 protected:
-  ~WebAuthnAssertion();
+  ~AuthenticatorAssertionResponse() override;
 
 public:
-  nsPIDOMWindowInner*
-  GetParentObject() const
-  {
-    return mParent;
-  }
-
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
-  already_AddRefed<ScopedCredential>
-  Credential() const;
-
   void
-  GetClientData(JSContext* cx, JS::MutableHandle<JSObject*> aRetVal) const;
-
-  void
-  GetAuthenticatorData(JSContext* cx, JS::MutableHandle<JSObject*> aRetVal) const;
-
-  void
-  GetSignature(JSContext* cx, JS::MutableHandle<JSObject*> aRetVal) const;
-
-  nsresult
-  SetCredential(RefPtr<ScopedCredential> aCredential);
-
-  nsresult
-  SetClientData(CryptoBuffer& aBuffer);
+  GetAuthenticatorData(JSContext* aCx, JS::MutableHandle<JSObject*> aRetVal) const;
 
   nsresult
   SetAuthenticatorData(CryptoBuffer& aBuffer);
 
+  void
+  GetSignature(JSContext* aCx, JS::MutableHandle<JSObject*> aRetVal) const;
+
   nsresult
   SetSignature(CryptoBuffer& aBuffer);
 
 private:
-  nsCOMPtr<nsPIDOMWindowInner> mParent;
-  RefPtr<ScopedCredential> mCredential;
   CryptoBuffer mAuthenticatorData;
-  CryptoBuffer mClientData;
   CryptoBuffer mSignature;
 };
 
 } // namespace dom
 } // namespace mozilla
 
-#endif // mozilla_dom_WebAuthnAssertion_h
+#endif // mozilla_dom_AuthenticatorAssertionResponse_h
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/AuthenticatorAttestationResponse.cpp
@@ -0,0 +1,50 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/dom/WebAuthenticationBinding.h"
+#include "mozilla/dom/AuthenticatorAttestationResponse.h"
+
+namespace mozilla {
+namespace dom {
+
+NS_IMPL_ADDREF_INHERITED(AuthenticatorAttestationResponse, AuthenticatorResponse)
+NS_IMPL_RELEASE_INHERITED(AuthenticatorAttestationResponse, AuthenticatorResponse)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(AuthenticatorAttestationResponse)
+NS_INTERFACE_MAP_END_INHERITING(AuthenticatorResponse)
+
+AuthenticatorAttestationResponse::AuthenticatorAttestationResponse(nsPIDOMWindowInner* aParent)
+  : AuthenticatorResponse(aParent)
+{}
+
+AuthenticatorAttestationResponse::~AuthenticatorAttestationResponse()
+{}
+
+JSObject*
+AuthenticatorAttestationResponse::WrapObject(JSContext* aCx,
+                                             JS::Handle<JSObject*> aGivenProto)
+{
+  return AuthenticatorAttestationResponseBinding::Wrap(aCx, this, aGivenProto);
+}
+
+void
+AuthenticatorAttestationResponse::GetAttestationObject(JSContext* aCx,
+                                                       JS::MutableHandle<JSObject*> aRetVal) const
+{
+  aRetVal.set(mAttestationObject.ToUint8Array(aCx));
+}
+
+nsresult
+AuthenticatorAttestationResponse::SetAttestationObject(CryptoBuffer& aBuffer)
+{
+  if (NS_WARN_IF(!mAttestationObject.Assign(aBuffer))) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  return NS_OK;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/webauthn/AuthenticatorAttestationResponse.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* 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/. */
+
+#ifndef mozilla_dom_AuthenticatorAttestationResponse_h
+#define mozilla_dom_AuthenticatorAttestationResponse_h
+
+#include "js/TypeDecls.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/dom/AuthenticatorResponse.h"
+#include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/CryptoBuffer.h"
+#include "nsCycleCollectionParticipant.h"
+#include "nsWrapperCache.h"
+
+namespace mozilla {
+namespace dom {
+
+class AuthenticatorAttestationResponse final : public AuthenticatorResponse
+{
+public:
+  NS_DECL_ISUPPORTS_INHERITED
+
+  explicit AuthenticatorAttestationResponse(nsPIDOMWindowInner* aParent);
+
+protected:
+  ~AuthenticatorAttestationResponse() override;
+
+public:
+  virtual JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
+
+  void
+  GetAttestationObject(JSContext* aCx, JS::MutableHandle<JSObject*> aRetVal) const;
+
+  nsresult
+  SetAttestationObject(CryptoBuffer& aBuffer);
+
+private:
+  CryptoBuffer mAttestationObject;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_AuthenticatorAttestationResponse_h
rename from dom/webauthn/WebAuthnAttestation.cpp
rename to dom/webauthn/AuthenticatorResponse.cpp
--- a/dom/webauthn/WebAuthnAttestation.cpp
+++ b/dom/webauthn/AuthenticatorResponse.cpp
@@ -1,98 +1,53 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/dom/WebAuthenticationBinding.h"
-#include "mozilla/dom/WebAuthnAttestation.h"
+#include "mozilla/dom/AuthenticatorResponse.h"
 
 namespace mozilla {
 namespace dom {
 
 // Only needed for refcounted objects.
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(WebAuthnAttestation, mParent)
-NS_IMPL_CYCLE_COLLECTING_ADDREF(WebAuthnAttestation)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(WebAuthnAttestation)
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(WebAuthnAttestation)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(AuthenticatorResponse, mParent)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(AuthenticatorResponse)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(AuthenticatorResponse)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AuthenticatorResponse)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
-WebAuthnAttestation::WebAuthnAttestation(nsPIDOMWindowInner* aParent)
+AuthenticatorResponse::AuthenticatorResponse(nsPIDOMWindowInner* aParent)
   : mParent(aParent)
 {}
 
-WebAuthnAttestation::~WebAuthnAttestation()
+AuthenticatorResponse::~AuthenticatorResponse()
 {}
 
 JSObject*
-WebAuthnAttestation::WrapObject(JSContext* aCx,
-                                JS::Handle<JSObject*> aGivenProto)
-{
-  return WebAuthnAttestationBinding::Wrap(aCx, this, aGivenProto);
-}
-
-void
-WebAuthnAttestation::GetFormat(nsString& aRetVal) const
+AuthenticatorResponse::WrapObject(JSContext* aCx,
+                                  JS::Handle<JSObject*> aGivenProto)
 {
-  aRetVal = mFormat;
-}
-
-void
-WebAuthnAttestation::GetClientData(JSContext* aCx,
-                                   JS::MutableHandle<JSObject*> aRetVal) const
-{
-  aRetVal.set(mClientData.ToUint8Array(aCx));
-}
-
-void
-WebAuthnAttestation::GetAuthenticatorData(JSContext* aCx,
-                                          JS::MutableHandle<JSObject*> aRetVal) const
-{
-  aRetVal.set(mAuthenticatorData.ToUint8Array(aCx));
+  return AuthenticatorResponseBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
-WebAuthnAttestation::GetAttestation(JSContext* aCx,
-                                    JS::MutableHandle<JS::Value> aRetVal) const
+AuthenticatorResponse::GetClientDataJSON(JSContext* aCx,
+                                         JS::MutableHandle<JSObject*> aRetVal) const
 {
-  aRetVal.setObject(*mAttestation.ToUint8Array(aCx));
-}
-
-nsresult
-WebAuthnAttestation::SetFormat(nsString aFormat)
-{
-  mFormat = aFormat;
-  return NS_OK;
+  aRetVal.set(mClientDataJSON.ToUint8Array(aCx));
 }
 
 nsresult
-WebAuthnAttestation::SetClientData(CryptoBuffer& aBuffer)
-{
-  if (!mClientData.Assign(aBuffer)) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-  return NS_OK;
-}
-
-nsresult
-WebAuthnAttestation::SetAuthenticatorData(CryptoBuffer& aBuffer)
+AuthenticatorResponse::SetClientDataJSON(CryptoBuffer& aBuffer)
 {
-  if (!mAuthenticatorData.Assign(aBuffer)) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-  return NS_OK;
-}
-
-nsresult
-WebAuthnAttestation::SetAttestation(CryptoBuffer& aBuffer)
-{
-  if (!mAttestation.Assign(aBuffer)) {
+  if (NS_WARN_IF(!mClientDataJSON.Assign(aBuffer))) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   return NS_OK;
 }
 
 } // namespace dom
 } // namespace mozilla
rename from dom/webauthn/WebAuthnAttestation.h
rename to dom/webauthn/AuthenticatorResponse.h
--- a/dom/webauthn/WebAuthnAttestation.h
+++ b/dom/webauthn/AuthenticatorResponse.h
@@ -1,79 +1,60 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/. */
 
-#ifndef mozilla_dom_WebAuthnAttestation_h
-#define mozilla_dom_WebAuthnAttestation_h
+#ifndef mozilla_dom_AuthenticatorResponse_h
+#define mozilla_dom_AuthenticatorResponse_h
 
 #include "js/TypeDecls.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/CryptoBuffer.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsWrapperCache.h"
 
 namespace mozilla {
 namespace dom {
 
-class WebAuthnAttestation final : public nsISupports
-                                , public nsWrapperCache
+class AuthenticatorResponse : public nsISupports
+                            , public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(WebAuthnAttestation)
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AuthenticatorResponse)
 
-public:
-  explicit WebAuthnAttestation(nsPIDOMWindowInner* aParent);
+  explicit AuthenticatorResponse(nsPIDOMWindowInner* aParent);
 
 protected:
-  ~WebAuthnAttestation();
+  virtual ~AuthenticatorResponse();
 
 public:
   nsISupports*
   GetParentObject() const
   {
     return mParent;
   }
 
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   void
   GetFormat(nsString& aRetVal) const;
 
   void
-  GetClientData(JSContext* aCx, JS::MutableHandle<JSObject*> aRetVal) const;
-
-  void
-  GetAuthenticatorData(JSContext* caCxx, JS::MutableHandle<JSObject*> aRetVal) const;
-
-  void
-  GetAttestation(JSContext* aCx, JS::MutableHandle<JS::Value> aRetVal) const;
+  GetClientDataJSON(JSContext* aCx, JS::MutableHandle<JSObject*> aRetVal) const;
 
   nsresult
-  SetFormat(nsString aFormat);
-
-  nsresult
-  SetClientData(CryptoBuffer& aBuffer);
-
-  nsresult
-  SetAuthenticatorData(CryptoBuffer& aBuffer);
-
-  nsresult
-  SetAttestation(CryptoBuffer& aBuffer);
+  SetClientDataJSON(CryptoBuffer& aBuffer);
 
 private:
   nsCOMPtr<nsPIDOMWindowInner> mParent;
-  nsString mFormat;
-  CryptoBuffer mClientData;
-  CryptoBuffer mAuthenticatorData;
-  CryptoBuffer mAttestation;
+  CryptoBuffer mClientDataJSON;
 };
 
 } // namespace dom
 } // namespace mozilla
 
-#endif // mozilla_dom_WebAuthnAttestation_h
+#endif // mozilla_dom_AuthenticatorResponse_h
--- a/dom/webauthn/PWebAuthnTransaction.ipdl
+++ b/dom/webauthn/PWebAuthnTransaction.ipdl
@@ -15,21 +15,17 @@
  */
 
 include protocol PBackground;
 
 namespace mozilla {
 namespace dom {
 
 struct WebAuthnScopedCredentialDescriptor {
-  // Converted from mozilla::dom::ScopedCredentialType enum
-  uint32_t type;
   uint8_t[] id;
-  // Converted from mozilla::dom::WebAuthnTransport enum
-  uint32_t[] transports;
 };
 
 struct WebAuthnExtension {
   /* TODO Fill in with predefined extensions */
 };
 
 struct WebAuthnTransactionInfo {
   uint8_t[] RpIdHash;
rename from dom/webauthn/ScopedCredentialInfo.cpp
rename to dom/webauthn/PublicKeyCredential.cpp
--- a/dom/webauthn/ScopedCredentialInfo.cpp
+++ b/dom/webauthn/PublicKeyCredential.cpp
@@ -1,63 +1,69 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/dom/ScopedCredentialInfo.h"
+#include "mozilla/dom/PublicKeyCredential.h"
 #include "mozilla/dom/WebAuthenticationBinding.h"
+#include "nsCycleCollectionParticipant.h"
 
 namespace mozilla {
 namespace dom {
 
-// Only needed for refcounted objects.
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ScopedCredentialInfo, mParent)
-NS_IMPL_CYCLE_COLLECTING_ADDREF(ScopedCredentialInfo)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(ScopedCredentialInfo)
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScopedCredentialInfo)
-  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
+NS_IMPL_CYCLE_COLLECTION_INHERITED(PublicKeyCredential, Credential, mResponse)
+
+NS_IMPL_ADDREF_INHERITED(PublicKeyCredential, Credential)
+NS_IMPL_RELEASE_INHERITED(PublicKeyCredential, Credential)
 
-ScopedCredentialInfo::ScopedCredentialInfo(nsPIDOMWindowInner* aParent)
-  : mParent(aParent)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(PublicKeyCredential)
+NS_INTERFACE_MAP_END_INHERITING(Credential)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(PublicKeyCredential, Credential)
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+PublicKeyCredential::PublicKeyCredential(nsPIDOMWindowInner* aParent)
+  : Credential(aParent)
 {}
 
-ScopedCredentialInfo::~ScopedCredentialInfo()
+PublicKeyCredential::~PublicKeyCredential()
 {}
 
 JSObject*
-ScopedCredentialInfo::WrapObject(JSContext* aCx,
-                                 JS::Handle<JSObject*> aGivenProto)
+PublicKeyCredential::WrapObject(JSContext* aCx,
+                                JS::Handle<JSObject*> aGivenProto)
 {
-  return ScopedCredentialInfoBinding::Wrap(aCx, this, aGivenProto);
+  return PublicKeyCredentialBinding::Wrap(aCx, this, aGivenProto);
 }
 
-already_AddRefed<ScopedCredential>
-ScopedCredentialInfo::Credential() const
+void
+PublicKeyCredential::GetRawId(JSContext* aCx,
+                              JS::MutableHandle<JSObject*> aRetVal) const
 {
-  RefPtr<ScopedCredential> temp(mCredential);
+  aRetVal.set(mRawId.ToUint8Array(aCx));
+}
+
+already_AddRefed<AuthenticatorResponse>
+PublicKeyCredential::Response() const
+{
+  RefPtr<AuthenticatorResponse> temp(mResponse);
   return temp.forget();
 }
 
-already_AddRefed<WebAuthnAttestation>
-ScopedCredentialInfo::Attestation() const
+nsresult
+PublicKeyCredential::SetRawId(CryptoBuffer& aBuffer)
 {
-  RefPtr<WebAuthnAttestation> temp(mAttestation);
-  return temp.forget();
+  if (NS_WARN_IF(!mRawId.Assign(aBuffer))) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+  return NS_OK;
 }
 
 void
-ScopedCredentialInfo::SetCredential(RefPtr<ScopedCredential> aCredential)
+PublicKeyCredential::SetResponse(RefPtr<AuthenticatorResponse> aResponse)
 {
-  mCredential = aCredential;
-}
-
-void
-ScopedCredentialInfo::SetAttestation(RefPtr<WebAuthnAttestation> aAttestation)
-{
-  mAttestation = aAttestation;
+  mResponse = aResponse;
 }
 
 } // namespace dom
 } // namespace mozilla
rename from dom/webauthn/ScopedCredentialInfo.h
rename to dom/webauthn/PublicKeyCredential.h
--- a/dom/webauthn/ScopedCredentialInfo.h
+++ b/dom/webauthn/PublicKeyCredential.h
@@ -1,74 +1,59 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/. */
 
-#ifndef mozilla_dom_ScopedCredentialInfo_h
-#define mozilla_dom_ScopedCredentialInfo_h
+#ifndef mozilla_dom_PublicKeyCredential_h
+#define mozilla_dom_PublicKeyCredential_h
 
 #include "js/TypeDecls.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/Credential.h"
 #include "mozilla/ErrorResult.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsWrapperCache.h"
 
 namespace mozilla {
 namespace dom {
 
-class CryptoKey;
-class ScopedCredential;
-class WebAuthnAttestation;
-
-} // namespace dom
-} // namespace mozilla
-
-namespace mozilla {
-namespace dom {
-
-class ScopedCredentialInfo final : public nsISupports
-                                 , public nsWrapperCache
+class PublicKeyCredential final : public Credential
 {
 public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ScopedCredentialInfo)
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(PublicKeyCredential,
+                                                         Credential)
 
-public:
-  explicit ScopedCredentialInfo(nsPIDOMWindowInner* aParent);
+  explicit PublicKeyCredential(nsPIDOMWindowInner* aParent);
 
 protected:
-  ~ScopedCredentialInfo();
+  ~PublicKeyCredential() override;
 
 public:
-  nsISupports*
-  GetParentObject() const
-  {
-    return mParent;
-  }
-
   virtual JSObject*
   WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
-  already_AddRefed<ScopedCredential>
-  Credential() const;
+  void
+  GetRawId(JSContext* cx, JS::MutableHandle<JSObject*> aRetVal) const;
 
-  already_AddRefed<WebAuthnAttestation>
-  Attestation() const;
+  already_AddRefed<AuthenticatorResponse>
+  Response() const;
+
+  nsresult
+  SetRawId(CryptoBuffer& aBuffer);
 
   void
-  SetCredential(RefPtr<ScopedCredential>);
-
-  void
-  SetAttestation(RefPtr<WebAuthnAttestation>);
+  SetResponse(RefPtr<AuthenticatorResponse>);
 
 private:
-  nsCOMPtr<nsPIDOMWindowInner> mParent;
-  RefPtr<WebAuthnAttestation> mAttestation;
-  RefPtr<ScopedCredential> mCredential;
+  CryptoBuffer mRawId;
+  RefPtr<AuthenticatorResponse> mResponse;
+  // Extensions are not supported yet.
+  // <some type> mClientExtensionResults;
 };
 
 } // namespace dom
 } // namespace mozilla
 
-#endif // mozilla_dom_ScopedCredentialInfo_h
+#endif // mozilla_dom_PublicKeyCredential_h
deleted file mode 100644
--- a/dom/webauthn/ScopedCredential.cpp
+++ /dev/null
@@ -1,66 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et cindent: */
-/* 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/dom/ScopedCredential.h"
-#include "mozilla/dom/WebAuthenticationBinding.h"
-
-namespace mozilla {
-namespace dom {
-
-// Only needed for refcounted objects.
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ScopedCredential, mParent)
-NS_IMPL_CYCLE_COLLECTING_ADDREF(ScopedCredential)
-NS_IMPL_CYCLE_COLLECTING_RELEASE(ScopedCredential)
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ScopedCredential)
-  NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
-  NS_INTERFACE_MAP_ENTRY(nsISupports)
-NS_INTERFACE_MAP_END
-
-ScopedCredential::ScopedCredential(nsPIDOMWindowInner* aParent)
-  : mParent(aParent)
-  , mType(ScopedCredentialType::ScopedCred)
-{}
-
-ScopedCredential::~ScopedCredential()
-{}
-
-JSObject*
-ScopedCredential::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
-{
-  return ScopedCredentialBinding::Wrap(aCx, this, aGivenProto);
-}
-
-ScopedCredentialType
-ScopedCredential::Type() const
-{
-  return mType;
-}
-
-void
-ScopedCredential::GetId(JSContext* aCx,
-                        JS::MutableHandle<JSObject*> aRetVal) const
-{
-  aRetVal.set(mIdBuffer.ToUint8Array(aCx));
-}
-
-nsresult
-ScopedCredential::SetType(ScopedCredentialType aType)
-{
-  mType = aType;
-  return NS_OK;
-}
-
-nsresult
-ScopedCredential::SetId(CryptoBuffer& aBuffer)
-{
-  if (!mIdBuffer.Assign(aBuffer)) {
-    return NS_ERROR_OUT_OF_MEMORY;
-  }
-  return NS_OK;
-}
-
-} // namespace dom
-} // namespace mozilla
deleted file mode 100644
--- a/dom/webauthn/ScopedCredential.h
+++ /dev/null
@@ -1,66 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim:set ts=2 sw=2 sts=2 et cindent: */
-/* 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/. */
-
-#ifndef mozilla_dom_ScopedCredential_h
-#define mozilla_dom_ScopedCredential_h
-
-#include "js/TypeDecls.h"
-#include "mozilla/Attributes.h"
-#include "mozilla/ErrorResult.h"
-#include "mozilla/dom/BindingDeclarations.h"
-#include "mozilla/dom/CryptoBuffer.h"
-#include "mozilla/dom/WebAuthenticationBinding.h"
-#include "nsCycleCollectionParticipant.h"
-#include "nsWrapperCache.h"
-
-namespace mozilla {
-namespace dom {
-
-class ScopedCredential final : public nsISupports
-                             , public nsWrapperCache
-{
-public:
-  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
-  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(ScopedCredential)
-
-public:
-  explicit ScopedCredential(nsPIDOMWindowInner* aParent);
-
-protected:
-  ~ScopedCredential();
-
-public:
-  nsISupports*
-  GetParentObject() const
-  {
-    return mParent;
-  }
-
-  virtual JSObject*
-  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
-
-  ScopedCredentialType
-  Type() const;
-
-  void
-  GetId(JSContext* aCx, JS::MutableHandle<JSObject*> aRetVal) const;
-
-  nsresult
-  SetType(ScopedCredentialType aType);
-
-  nsresult
-  SetId(CryptoBuffer& aBuffer);
-
-private:
-  nsCOMPtr<nsPIDOMWindowInner> mParent;
-  CryptoBuffer mIdBuffer;
-  ScopedCredentialType mType;
-};
-
-} // namespace dom
-} // namespace mozilla
-
-#endif // mozilla_dom_ScopedCredential_h
--- a/dom/webauthn/WebAuthnManager.cpp
+++ b/dom/webauthn/WebAuthnManager.cpp
@@ -3,16 +3,17 @@
 /* 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 "hasht.h"
 #include "nsNetCID.h"
 #include "nsICryptoHash.h"
 #include "mozilla/ClearOnShutdown.h"
+#include "mozilla/dom/AuthenticatorAttestationResponse.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/WebAuthnManager.h"
 #include "mozilla/dom/WebAuthnUtil.h"
 #include "mozilla/dom/PWebAuthnTransaction.h"
 #include "mozilla/dom/WebAuthnTransactionChild.h"
 #include "mozilla/dom/WebCryptoCommon.h"
 #include "mozilla/ipc/PBackgroundChild.h"
 #include "mozilla/ipc/BackgroundChild.h"
@@ -34,17 +35,17 @@ static mozilla::LazyLogModule gWebAuthnM
 NS_IMPL_ISUPPORTS(WebAuthnManager, nsIIPCBackgroundChildCreateCallback);
 
 /***********************************************************************
  * Utility Functions
  **********************************************************************/
 
 template<class OOS>
 static nsresult
-GetAlgorithmName(JSContext* aCx, const OOS& aAlgorithm,
+GetAlgorithmName(const OOS& aAlgorithm,
                  /* out */ nsString& aName)
 {
   MOZ_ASSERT(aAlgorithm.IsString()); // TODO: remove assertion when we coerce.
 
   if (aAlgorithm.IsString()) {
     // If string, then treat as algorithm name
     aName.Assign(aAlgorithm.GetAsString());
   } else {
@@ -94,20 +95,20 @@ AssembleClientData(const nsAString& aOri
   MOZ_ASSERT(NS_IsMainThread());
 
   nsString challengeBase64;
   nsresult rv = aChallenge.ToJwkBase64(challengeBase64);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return NS_ERROR_FAILURE;
   }
 
-  WebAuthnClientData clientDataObject;
+  CollectedClientData clientDataObject;
+  clientDataObject.mChallenge.Assign(challengeBase64);
   clientDataObject.mOrigin.Assign(aOrigin);
-  clientDataObject.mHashAlg.SetAsString().Assign(NS_LITERAL_STRING("S256"));
-  clientDataObject.mChallenge.Assign(challengeBase64);
+  clientDataObject.mHashAlg.Assign(NS_LITERAL_STRING("S256"));
 
   nsAutoString temp;
   if (NS_WARN_IF(!clientDataObject.ToJSON(temp))) {
     return NS_ERROR_FAILURE;
   }
 
   aJsonOut.Assign(NS_ConvertUTF16toUTF8(temp));
   return NS_OK;
@@ -209,21 +210,18 @@ WebAuthnManager::GetOrCreate()
 WebAuthnManager*
 WebAuthnManager::Get()
 {
   MOZ_ASSERT(NS_IsMainThread());
   return gWebAuthnManager;
 }
 
 already_AddRefed<Promise>
-WebAuthnManager::MakeCredential(nsPIDOMWindowInner* aParent, JSContext* aCx,
-                                const Account& aAccountInformation,
-                                const Sequence<ScopedCredentialParameters>& aCryptoParameters,
-                                const ArrayBufferViewOrArrayBuffer& aChallenge,
-                                const ScopedCredentialOptions& aOptions)
+WebAuthnManager::MakeCredential(nsPIDOMWindowInner* aParent,
+                                const MakeCredentialOptions& aOptions)
 {
   MOZ_ASSERT(aParent);
 
   MaybeClearTransaction();
 
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aParent);
 
   ErrorResult rv;
@@ -239,37 +237,37 @@ WebAuthnManager::MakeCredential(nsPIDOMW
     return promise.forget();
   }
 
   // If timeoutSeconds was specified, check if its value lies within a
   // reasonable range as defined by the platform and if not, correct it to the
   // closest value lying within that range.
 
   double adjustedTimeout = 30.0;
-  if (aOptions.mTimeoutSeconds.WasPassed()) {
-    adjustedTimeout = aOptions.mTimeoutSeconds.Value();
+  if (aOptions.mTimeout.WasPassed()) {
+    adjustedTimeout = aOptions.mTimeout.Value();
     adjustedTimeout = std::max(15.0, adjustedTimeout);
     adjustedTimeout = std::min(120.0, adjustedTimeout);
   }
 
   nsCString rpId;
-  if (!aOptions.mRpId.WasPassed()) {
-    // If rpId is not specified, then set rpId to callerOrigin, and rpIdHash to
+  if (!aOptions.mRp.mId.WasPassed()) {
+    // If rp.id is not specified, then set rpId to callerOrigin, and rpIdHash to
     // the SHA-256 hash of rpId.
     rpId.Assign(NS_ConvertUTF16toUTF8(origin));
   } else {
     // If rpId is specified, then invoke the procedure used for relaxing the
     // same-origin restriction by setting the document.domain attribute, using
     // rpId as the given value but without changing the current document’s
     // domain. If no errors are thrown, set rpId to the value of host as
     // computed by this procedure, and rpIdHash to the SHA-256 hash of rpId.
     // Otherwise, reject promise with a DOMException whose name is
     // "SecurityError", and terminate this algorithm.
 
-    if (NS_FAILED(RelaxSameOrigin(aParent, aOptions.mRpId.Value(), rpId))) {
+    if (NS_FAILED(RelaxSameOrigin(aParent, aOptions.mRp.mId.Value(), rpId))) {
       promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
       return promise.forget();
     }
   }
 
   CryptoBuffer rpIdHash;
   if (!rpIdHash.SetLength(SHA256_LENGTH, fallible)) {
     promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
@@ -287,74 +285,75 @@ WebAuthnManager::MakeCredential(nsPIDOMW
   srv = HashCString(hashService, rpId, rpIdHash);
   if (NS_WARN_IF(NS_FAILED(srv))) {
     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     return promise.forget();
   }
 
   // Process each element of cryptoParameters using the following steps, to
   // produce a new sequence normalizedParameters.
-  nsTArray<ScopedCredentialParameters> normalizedParams;
-  for (size_t a = 0; a < aCryptoParameters.Length(); ++a) {
+  nsTArray<PublicKeyCredentialParameters> normalizedParams;
+  for (size_t a = 0; a < aOptions.mParameters.Length(); ++a) {
     // Let current be the currently selected element of
     // cryptoParameters.
 
-    // If current.type does not contain a ScopedCredentialType
+    // If current.type does not contain a PublicKeyCredentialType
     // supported by this implementation, then stop processing current and move
     // on to the next element in cryptoParameters.
-    if (aCryptoParameters[a].mType != ScopedCredentialType::ScopedCred) {
+    if (aOptions.mParameters[a].mType != PublicKeyCredentialType::Public_key) {
       continue;
     }
 
     // Let normalizedAlgorithm be the result of normalizing an algorithm using
     // the procedure defined in [WebCryptoAPI], with alg set to
     // current.algorithm and op set to 'generateKey'. If an error occurs during
     // this procedure, then stop processing current and move on to the next
     // element in cryptoParameters.
 
     nsString algName;
-    if (NS_FAILED(GetAlgorithmName(aCx, aCryptoParameters[a].mAlgorithm,
+    if (NS_FAILED(GetAlgorithmName(aOptions.mParameters[a].mAlgorithm,
                                    algName))) {
       continue;
     }
 
-    // Add a new object of type ScopedCredentialParameters to
+    // Add a new object of type PublicKeyCredentialParameters to
     // normalizedParameters, with type set to current.type and algorithm set to
     // normalizedAlgorithm.
-    ScopedCredentialParameters normalizedObj;
-    normalizedObj.mType = aCryptoParameters[a].mType;
+    PublicKeyCredentialParameters normalizedObj;
+    normalizedObj.mType = aOptions.mParameters[a].mType;
     normalizedObj.mAlgorithm.SetAsString().Assign(algName);
 
     if (!normalizedParams.AppendElement(normalizedObj, mozilla::fallible)){
       promise->MaybeReject(NS_ERROR_OUT_OF_MEMORY);
       return promise.forget();
     }
   }
 
   // If normalizedAlgorithm is empty and cryptoParameters was not empty, cancel
   // the timer started in step 2, reject promise with a DOMException whose name
   // is "NotSupportedError", and terminate this algorithm.
-  if (normalizedParams.IsEmpty() && !aCryptoParameters.IsEmpty()) {
+  if (normalizedParams.IsEmpty() && !aOptions.mParameters.IsEmpty()) {
     promise->MaybeReject(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
     return promise.forget();
   }
 
   // TODO: The following check should not be here. This is checking for
   // parameters specific to the soft key, and should be put in the soft key
   // manager in the parent process. Still need to serialize
-  // ScopedCredentialParameters first.
+  // PublicKeyCredentialParameters first.
 
-  // Check if at least one of the specified combinations of ScopedCredentialType
-  // and cryptographic parameters is supported. If not, return an error code
-  // equivalent to NotSupportedError and terminate the operation.
+  // Check if at least one of the specified combinations of
+  // PublicKeyCredentialParameters and cryptographic parameters is supported. If
+  // not, return an error code equivalent to NotSupportedError and terminate the
+  // operation.
 
   bool isValidCombination = false;
 
   for (size_t a = 0; a < normalizedParams.Length(); ++a) {
-    if (normalizedParams[a].mType == ScopedCredentialType::ScopedCred &&
+    if (normalizedParams[a].mType == PublicKeyCredentialType::Public_key &&
         normalizedParams[a].mAlgorithm.IsString() &&
         normalizedParams[a].mAlgorithm.GetAsString().EqualsLiteral(
           WEBCRYPTO_NAMED_CURVE_P256)) {
       isValidCombination = true;
       break;
     }
   }
   if (!isValidCombination) {
@@ -373,17 +372,17 @@ WebAuthnManager::MakeCredential(nsPIDOMW
   // Currently no extensions are supported
   //
   // Use attestationChallenge, callerOrigin and rpId, along with the token
   // binding key associated with callerOrigin (if any), to create a ClientData
   // structure representing this request. Choose a hash algorithm for hashAlg
   // and compute the clientDataJSON and clientDataHash.
 
   CryptoBuffer challenge;
-  if (!challenge.Assign(aChallenge)) {
+  if (!challenge.Assign(aOptions.mChallenge)) {
     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     return promise.forget();
   }
 
   nsAutoCString clientDataJSON;
   srv = AssembleClientData(origin, challenge, clientDataJSON);
   if (NS_WARN_IF(NS_FAILED(srv))) {
     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
@@ -401,25 +400,19 @@ WebAuthnManager::MakeCredential(nsPIDOMW
     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     return promise.forget();
   }
 
   nsTArray<WebAuthnScopedCredentialDescriptor> excludeList;
   if (aOptions.mExcludeList.WasPassed()) {
     for (const auto& s: aOptions.mExcludeList.Value()) {
       WebAuthnScopedCredentialDescriptor c;
-      c.type() = static_cast<uint32_t>(s.mType);
       CryptoBuffer cb;
       cb.Assign(s.mId);
       c.id() = cb;
-      if (s.mTransports.WasPassed()) {
-        for (const auto& t: s.mTransports.Value()) {
-          c.transports().AppendElement(static_cast<uint32_t>(t));
-        }
-      }
       excludeList.AppendElement(c);
     }
   }
 
   // TODO: Add extension list building
   nsTArray<WebAuthnExtension> extensions;
 
   WebAuthnTransactionInfo info(rpIdHash,
@@ -458,18 +451,17 @@ void
 WebAuthnManager::StartSign() {
   if (mChild) {
     mChild->SendRequestSign(mInfo.ref());
   }
 }
 
 already_AddRefed<Promise>
 WebAuthnManager::GetAssertion(nsPIDOMWindowInner* aParent,
-                              const ArrayBufferViewOrArrayBuffer& aChallenge,
-                              const AssertionOptions& aOptions)
+                              const PublicKeyCredentialRequestOptions& aOptions)
 {
   MOZ_ASSERT(aParent);
 
   MaybeClearTransaction();
 
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aParent);
 
   ErrorResult rv;
@@ -484,21 +476,21 @@ WebAuthnManager::GetAssertion(nsPIDOMWin
     promise->MaybeReject(rv);
     return promise.forget();
   }
 
   // If timeoutSeconds was specified, check if its value lies within a
   // reasonable range as defined by the platform and if not, correct it to the
   // closest value lying within that range.
 
-  double adjustedTimeout = 30.0;
-  if (aOptions.mTimeoutSeconds.WasPassed()) {
-    adjustedTimeout = aOptions.mTimeoutSeconds.Value();
-    adjustedTimeout = std::max(15.0, adjustedTimeout);
-    adjustedTimeout = std::min(120.0, adjustedTimeout);
+  uint32_t adjustedTimeout = 30000;
+  if (aOptions.mTimeout.WasPassed()) {
+    adjustedTimeout = aOptions.mTimeout.Value();
+    adjustedTimeout = std::max(15000u, adjustedTimeout);
+    adjustedTimeout = std::min(120000u, adjustedTimeout);
   }
 
   nsCString rpId;
   if (!aOptions.mRpId.WasPassed()) {
     // If rpId is not specified, then set rpId to callerOrigin, and rpIdHash to
     // the SHA-256 hash of rpId.
     rpId.Assign(NS_ConvertUTF16toUTF8(origin));
   } else {
@@ -536,17 +528,17 @@ WebAuthnManager::GetAssertion(nsPIDOMWin
     return promise.forget();
   }
 
   // Use assertionChallenge, callerOrigin and rpId, along with the token binding
   // key associated with callerOrigin (if any), to create a ClientData structure
   // representing this request. Choose a hash algorithm for hashAlg and compute
   // the clientDataJSON and clientDataHash.
   CryptoBuffer challenge;
-  if (!challenge.Assign(aChallenge)) {
+  if (!challenge.Assign(aOptions.mChallenge)) {
     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     return promise.forget();
   }
 
   nsAutoCString clientDataJSON;
   srv = AssembleClientData(origin, challenge, clientDataJSON);
   if (NS_WARN_IF(NS_FAILED(srv))) {
     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
@@ -562,47 +554,41 @@ WebAuthnManager::GetAssertion(nsPIDOMWin
   srv = HashCString(hashService, clientDataJSON, clientDataHash);
   if (NS_WARN_IF(NS_FAILED(srv))) {
     promise->MaybeReject(NS_ERROR_DOM_SECURITY_ERR);
     return promise.forget();
   }
 
   // Note: we only support U2F-style authentication for now, so we effectively
   // require an AllowList.
-  if (!aOptions.mAllowList.WasPassed()) {
+  if (aOptions.mAllowList.Length() < 1) {
     promise->MaybeReject(NS_ERROR_DOM_NOT_ALLOWED_ERR);
     return promise.forget();
   }
 
   nsTArray<WebAuthnScopedCredentialDescriptor> allowList;
-  for (const auto& s: aOptions.mAllowList.Value()) {
+  for (const auto& s: aOptions.mAllowList) {
     WebAuthnScopedCredentialDescriptor c;
-    c.type() = static_cast<uint32_t>(s.mType);
     CryptoBuffer cb;
     cb.Assign(s.mId);
     c.id() = cb;
-    if (s.mTransports.WasPassed()) {
-      for (const auto& t: s.mTransports.Value()) {
-        c.transports().AppendElement(static_cast<uint32_t>(t));
-      }
-    }
     allowList.AppendElement(c);
   }
 
   // TODO: Add extension list building
   // If extensions was specified, process any extensions supported by this
   // client platform, to produce the extension data that needs to be sent to the
   // authenticator. If an error is encountered while processing an extension,
   // skip that extension and do not produce any extension data for it. Call the
   // result of this processing clientExtensions.
   nsTArray<WebAuthnExtension> extensions;
 
   WebAuthnTransactionInfo info(rpIdHash,
                                clientDataHash,
-                               10000,
+                               adjustedTimeout,
                                allowList,
                                extensions);
   RefPtr<MozPromise<nsresult, nsresult, false>> p = GetOrCreateBackgroundActor();
   p->Then(AbstractThread::MainThread(), __func__,
           []() {
             WebAuthnManager* mgr = WebAuthnManager::Get();
             if (!mgr) {
               return;
@@ -669,35 +655,29 @@ WebAuthnManager::FinishMakeCredential(ns
   CryptoBuffer authenticatorDataBuf;
   rv = U2FAssembleAuthenticatorData(authenticatorDataBuf, rpIdHashBuf,
                                     signatureData);
   if (NS_WARN_IF(NS_FAILED(rv))) {
     Cancel(NS_ERROR_OUT_OF_MEMORY);
     return;
   }
 
-  // Create a new ScopedCredentialInfo object named value and populate its
-  // fields with the values returned from the authenticator as well as the
-  // clientDataJSON computed earlier.
-
-  RefPtr<ScopedCredential> credential = new ScopedCredential(mCurrentParent);
-  credential->SetType(ScopedCredentialType::ScopedCred);
-  credential->SetId(keyHandleBuf);
+  // Create a new PublicKeyCredential object and populate its fields with the
+  // values returned from the authenticator as well as the clientDataJSON
+  // computed earlier.
+  RefPtr<AuthenticatorAttestationResponse> attestation =
+      new AuthenticatorAttestationResponse(mCurrentParent);
+  attestation->SetClientDataJSON(clientDataBuf);
+  attestation->SetAttestationObject(regData);
 
-  RefPtr<WebAuthnAttestation> attestation = new WebAuthnAttestation(mCurrentParent);
-  attestation->SetFormat(NS_LITERAL_STRING("u2f"));
-  attestation->SetClientData(clientDataBuf);
-  attestation->SetAuthenticatorData(authenticatorDataBuf);
-  attestation->SetAttestation(regData);
+  RefPtr<PublicKeyCredential> credential = new PublicKeyCredential(mCurrentParent);
+  credential->SetRawId(keyHandleBuf);
+  credential->SetResponse(attestation);
 
-  RefPtr<ScopedCredentialInfo> info = new ScopedCredentialInfo(mCurrentParent);
-  info->SetCredential(credential);
-  info->SetAttestation(attestation);
-
-  mTransactionPromise->MaybeResolve(info);
+  mTransactionPromise->MaybeResolve(credential);
   MaybeClearTransaction();
 }
 
 void
 WebAuthnManager::FinishGetAssertion(nsTArray<uint8_t>& aCredentialId,
                                     nsTArray<uint8_t>& aSigBuffer)
 {
   MOZ_ASSERT(mTransactionPromise);
@@ -732,31 +712,31 @@ WebAuthnManager::FinishGetAssertion(nsTA
   CryptoBuffer credentialBuf;
   if (!credentialBuf.Assign(aCredentialId)) {
     Cancel(rv);
     return;
   }
 
   // If any authenticator returns success:
 
-  // Create a new WebAuthnAssertion object named value and populate its fields
+  // Create a new PublicKeyCredential object named value and populate its fields
   // with the values returned from the authenticator as well as the
   // clientDataJSON computed earlier.
-
-  RefPtr<ScopedCredential> credential = new ScopedCredential(mCurrentParent);
-  credential->SetType(ScopedCredentialType::ScopedCred);
-  credential->SetId(credentialBuf);
-
-  RefPtr<WebAuthnAssertion> assertion = new WebAuthnAssertion(mCurrentParent);
-  assertion->SetCredential(credential);
-  assertion->SetClientData(clientDataBuf);
+  RefPtr<AuthenticatorAssertionResponse> assertion =
+    new AuthenticatorAssertionResponse(mCurrentParent);
+  assertion->SetClientDataJSON(clientDataBuf);
   assertion->SetAuthenticatorData(authenticatorDataBuf);
   assertion->SetSignature(signatureData);
 
-  mTransactionPromise->MaybeResolve(assertion);
+  RefPtr<PublicKeyCredential> credential =
+    new PublicKeyCredential(mCurrentParent);
+  credential->SetRawId(credentialBuf);
+  credential->SetResponse(assertion);
+
+  mTransactionPromise->MaybeResolve(credential);
   MaybeClearTransaction();
 }
 
 void
 WebAuthnManager::Cancel(const nsresult& aError)
 {
   if (mChild) {
     mChild->SendRequestCancel();
--- a/dom/webauthn/WebAuthnManager.h
+++ b/dom/webauthn/WebAuthnManager.h
@@ -2,19 +2,19 @@
 /* vim:set ts=2 sw=2 sts=2 et cindent: */
 /* 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/. */
 
 #ifndef mozilla_dom_WebAuthnManager_h
 #define mozilla_dom_WebAuthnManager_h
 
-#include "nsIIPCBackgroundChildCreateCallback.h"
 #include "mozilla/MozPromise.h"
 #include "mozilla/dom/PWebAuthnTransaction.h"
+#include "nsIIPCBackgroundChildCreateCallback.h"
 
 /*
  * Content process manager for the WebAuthn protocol. Created on calls to the
  * WebAuthentication DOM object, this manager handles establishing IPC channels
  * for WebAuthn transactions, as well as keeping track of JS Promise objects
  * representing transactions in flight.
  *
  * The WebAuthn spec (https://www.w3.org/TR/webauthn/) allows for two different
@@ -68,26 +68,22 @@ public:
   void
   FinishGetAssertion(nsTArray<uint8_t>& aCredentialId,
                      nsTArray<uint8_t>& aSigBuffer);
 
   void
   Cancel(const nsresult& aError);
 
   already_AddRefed<Promise>
-  MakeCredential(nsPIDOMWindowInner* aParent, JSContext* aCx,
-                 const Account& aAccountInformation,
-                 const Sequence<ScopedCredentialParameters>& aCryptoParameters,
-                 const ArrayBufferViewOrArrayBuffer& aAttestationChallenge,
-                 const ScopedCredentialOptions& aOptions);
+  MakeCredential(nsPIDOMWindowInner* aParent,
+                 const MakeCredentialOptions& aOptions);
 
   already_AddRefed<Promise>
   GetAssertion(nsPIDOMWindowInner* aParent,
-               const ArrayBufferViewOrArrayBuffer& aAssertionChallenge,
-               const AssertionOptions& aOptions);
+               const PublicKeyCredentialRequestOptions& aOptions);
 
   void StartRegister();
   void StartSign();
 
   // nsIIPCbackgroundChildCreateCallback methods
   void ActorCreated(PBackgroundChild* aActor) override;
   void ActorFailed() override;
   void ActorDestroyed();
--- a/dom/webauthn/moz.build
+++ b/dom/webauthn/moz.build
@@ -7,41 +7,39 @@
 with Files("**"):
     BUG_COMPONENT = ("Core", "DOM: Device Interfaces")
 
 IPDL_SOURCES += [
     'PWebAuthnTransaction.ipdl'
 ]
 
 EXPORTS.mozilla.dom += [
+    'AuthenticatorAssertionResponse.h',
+    'AuthenticatorAttestationResponse.h',
+    'AuthenticatorResponse.h',
     'NSSU2FTokenRemote.h',
-    'ScopedCredential.h',
-    'ScopedCredentialInfo.h',
+    'PublicKeyCredential.h',
     'U2FSoftTokenManager.h',
     'U2FTokenManager.h',
     'U2FTokenTransport.h',
-    'WebAuthentication.h',
-    'WebAuthnAssertion.h',
-    'WebAuthnAttestation.h',
     'WebAuthnManager.h',
     'WebAuthnRequest.h',
     'WebAuthnTransactionChild.h',
     'WebAuthnTransactionParent.h',
     'WebAuthnUtil.h'
 ]
 
 UNIFIED_SOURCES += [
+    'AuthenticatorAssertionResponse.cpp',
+    'AuthenticatorAttestationResponse.cpp',
+    'AuthenticatorResponse.cpp',
     'NSSU2FTokenRemote.cpp',
-    'ScopedCredential.cpp',
-    'ScopedCredentialInfo.cpp',
+    'PublicKeyCredential.cpp',
     'U2FSoftTokenManager.cpp',
     'U2FTokenManager.cpp',
-    'WebAuthentication.cpp',
-    'WebAuthnAssertion.cpp',
-    'WebAuthnAttestation.cpp',
     'WebAuthnManager.cpp',
     'WebAuthnTransactionChild.cpp',
     'WebAuthnTransactionParent.cpp',
     'WebAuthnUtil.cpp'
 ]
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
--- a/dom/webauthn/tests/test_webauthn_get_assertion.html
+++ b/dom/webauthn/tests/test_webauthn_get_assertion.html
@@ -11,98 +11,119 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
   <h1>Tests for GetAssertion for W3C Web Authentication</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";
+    "use strict";
 
-   // Execute the full-scope test
-   SimpleTest.waitForExplicitFinish();
+    // Execute the full-scope test
+    SimpleTest.waitForExplicitFinish();
 
-   function arrivingHereIsBad(aResult) {
-     ok(false, "Bad result! Received a: " + aResult);
-     return Promise.resolve();
-   }
+    function arrivingHereIsBad(aResult) {
+      ok(false, "Bad result! Received a: " + aResult);
+      return Promise.resolve();
+    }
 
-   function expectNotAllowedError(aResult) {
-     ok(aResult.toString().startsWith("NotAllowedError"), "Expecting a NotAllowedError");
-     return Promise.resolve();
-   }
+    function expectNotAllowedError(aResult) {
+      ok(aResult.toString().startsWith("NotAllowedError"), "Expecting a NotAllowedError");
+      return Promise.resolve();
+    }
 
-   function expectTypeError(aResult) {
-     ok(aResult.toString().startsWith("TypeError"), "Expecting a TypeError");
-     return Promise.resolve();
-   }
+    function expectTypeError(aResult) {
+      ok(aResult.toString().startsWith("TypeError"), "Expecting a TypeError");
+      return Promise.resolve();
+    }
 
-   SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn", true],
-                                      ["security.webauth.webauthn_enable_softtoken", true],
-                                      ["security.webauth.webauthn_enable_usbtoken", false]]},
-                             runTests);
+    SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn", true],
+                                       ["security.webauth.webauthn_enable_softtoken", true],
+                                       ["security.webauth.webauthn_enable_usbtoken", false]]},
+                              runTests);
 
-   function runTests() {
-     isnot(navigator.authentication, undefined, "WebAuthn API endpoint must exist");
-     isnot(navigator.authentication.makeCredential, undefined, "WebAuthn makeCredential API endpoint must exist");
-     isnot(navigator.authentication.getAssertion, undefined, "WebAuthn getAssertion API endpoint must exist");
+    function runTests() {
+      is(navigator.authentication, undefined, "navigator.authentication does not exist any longer");
+      isnot(navigator.credentials, undefined, "Credential Management API endpoint must exist");
+      isnot(navigator.credentials.create, undefined, "CredentialManagement create API endpoint must exist");
+      isnot(navigator.credentials.get, undefined, "CredentialManagement get API endpoint must exist");
 
-     let authn = navigator.authentication;
+      let credm = navigator.credentials;
 
-     let gAssertionChallenge = new Uint8Array(16);
-     window.crypto.getRandomValues(gAssertionChallenge);
+      let gAssertionChallenge = new Uint8Array(16);
+      window.crypto.getRandomValues(gAssertionChallenge);
 
-     let invalidCred = { type: "Magic", id: base64ToBytes("AAA=") };
-     let unknownCred = { type: "ScopedCred", id: base64ToBytes("AAA=") };
+      let invalidCred = {type: "Magic", id: base64ToBytes("AAA=")};
+      let unknownCred = {type: "public-key", id: base64ToBytes("AAA=")};
 
-     var testFuncs = [
-       function () {
-         // Test basic good call, but without giving a credential so expect failures
-         // this is OK by the standard, but not supported by U2F-backed authenticators
-         // like the soft token in use here.
-         return authn.getAssertion(gAssertionChallenge)
-                     .then(arrivingHereIsBad)
-                     .catch(expectNotAllowedError);
-       },
-       function () {
-         // Test with an unexpected option
-         return authn.getAssertion(gAssertionChallenge, { unknownValue: "hi" })
-                     .then(arrivingHereIsBad)
-                     .catch(expectNotAllowedError);
-       },
-       function () {
-         // Test with an invalid credential
-         return authn.getAssertion(gAssertionChallenge, { allowList: [invalidCred] })
-                     .then(arrivingHereIsBad)
-                     .catch(expectTypeError);
-       },
-       function () {
-         // Test with an unknown credential
-         return authn.getAssertion(gAssertionChallenge, { allowList: [unknownCred] })
-                     .then(arrivingHereIsBad)
-                     .catch(expectNotAllowedError);
-       },
-       function () {
-         // Test with an unexpected option and an invalid credential
-         return authn.getAssertion(gAssertionChallenge, { unknownValue: "hi" })
-                     .then(arrivingHereIsBad)
-                     .catch(expectNotAllowedError);
-       }
-     ];
+      var testFuncs = [
+        function () {
+          // Test basic good call, but without giving a credential so expect failures
+          // this is OK by the standard, but not supported by U2F-backed authenticators
+          // like the soft token in use here.
+          let publicKeyCredentialRequestOptions = {
+            challenge: gAssertionChallenge
+          };
+          return credm.get({publicKey: publicKeyCredentialRequestOptions})
+                      .then(arrivingHereIsBad)
+                      .catch(expectNotAllowedError);
+        },
+        function () {
+          // Test with an unexpected option
+          let publicKeyCredentialRequestOptions = {
+            challenge: gAssertionChallenge,
+            unknownValue: "hi"
+          };
+          return credm.get({publicKey: publicKeyCredentialRequestOptions})
+                      .then(arrivingHereIsBad)
+                      .catch(expectNotAllowedError);
+        },
+        function () {
+          // Test with an invalid credential
+          let publicKeyCredentialRequestOptions = {
+            challenge: gAssertionChallenge,
+            allowList: [invalidCred]
+          };
+          return credm.get({publicKey: publicKeyCredentialRequestOptions})
+                      .then(arrivingHereIsBad)
+                      .catch(expectTypeError);
+        },
+        function () {
+          // Test with an unknown credential
+          let publicKeyCredentialRequestOptions = {
+            challenge: gAssertionChallenge,
+            allowList: [unknownCred]
+          };
+          return credm.get({publicKey: publicKeyCredentialRequestOptions})
+                      .then(arrivingHereIsBad)
+                      .catch(expectNotAllowedError);
+        },
+        function () {
+          // Test with an unexpected option and an invalid credential
+          let publicKeyCredentialRequestOptions = {
+            challenge: gAssertionChallenge,
+            unknownValue: "hi",
+            allowList: [invalidCred]
+          };
+          return credm.get({publicKey: publicKeyCredentialRequestOptions})
+                      .then(arrivingHereIsBad)
+                      .catch(expectTypeError);
+        }
+      ];
 
-     var i = 0;
-     var runNextTest = () => {
-       if (i == testFuncs.length) {
-         SimpleTest.finish();
-         return;
-       }
-       testFuncs[i]().then(() => { runNextTest(); });
-       i = i + 1;
-     };
-     runNextTest();
+      var i = 0;
+      var runNextTest = () => {
+        if (i == testFuncs.length) {
+          SimpleTest.finish();
+          return;
+        }
+        testFuncs[i]().then(() => { runNextTest(); });
+        i = i + 1;
+      };
+      runNextTest();
 
-   }
+    }
 
   </script>
 
 </body>
 </html>
--- a/dom/webauthn/tests/test_webauthn_loopback.html
+++ b/dom/webauthn/tests/test_webauthn_loopback.html
@@ -20,140 +20,151 @@
 
 // Execute the full-scope test
 SimpleTest.waitForExplicitFinish();
 
 SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn", true],
                                    ["security.webauth.webauthn_enable_softtoken", true],
                                    ["security.webauth.webauthn_enable_usbtoken", false]]},
 function() {
-  isnot(navigator.authentication, undefined, "WebAuthn API endpoint must exist");
-  isnot(navigator.authentication.makeCredential, undefined, "WebAuthn makeCredential API endpoint must exist");
-  isnot(navigator.authentication.getAssertion, undefined, "WebAuthn getAssertion API endpoint must exist");
+  is(navigator.authentication, undefined, "navigator.authentication does not exist any longer");
+  isnot(navigator.credentials, undefined, "Credential Management API endpoint must exist");
+  isnot(navigator.credentials.create, undefined, "CredentialManagement create API endpoint must exist");
+  isnot(navigator.credentials.get, undefined, "CredentialManagement get API endpoint must exist");
 
-  let authn = navigator.authentication;
+  let credm = navigator.credentials;
 
   let gCredentialChallenge = new Uint8Array(16);
   window.crypto.getRandomValues(gCredentialChallenge);
   let gAssertionChallenge = new Uint8Array(16);
   window.crypto.getRandomValues(gAssertionChallenge);
 
   testMakeCredential();
 
   function checkCredentialValid(aCredInfo) {
-    /* ScopedCredentialInfo
-    - Credential
-    -- ID: Key Handle buffer pulled from U2F Register() Response
-    -- Type: "ScopedCred"
-    - WebAuthnAttestation
-    -- Format: "u2f"
-    -- ClientData: serialized JSON
-    -- AuthenticatorData: RP ID Hash || U2F Sign() Response
-    -- Attestation: U2F Register() Response */
+    /* PublicKeyCredential : Credential
+       - rawId: Key Handle buffer pulled from U2F Register() Response
+       - response : AuthenticatorAttestationResponse : AuthenticatorResponse
+         - attestationObject: RP ID Hash || U2F Sign() Response
+         - clientDataJSON: serialized JSON
+       - clientExtensionResults: (not yet supported)
+    */
 
-    is(aCredInfo.credential.type, "ScopedCred", "Type is correct");
-    ok(aCredInfo.credential.id.length > 0, "Key ID exists");
+    ok(aCredInfo.rawId.length > 0, "Key ID exists");
 
-    is(aCredInfo.attestation.format, "u2f", "Format is correct");
-    is(aCredInfo.attestation.attestation[0], 0x05, "Reserved byte is correct");
-    ok(aCredInfo.attestation.authenticatorData.length > 0, "Authenticator data exists");
-    let clientData = JSON.parse(buffer2string(aCredInfo.attestation.clientData));
+    is(aCredInfo.response.attestationObject[0], 0x05, "Reserved byte is correct");
+    let clientData = JSON.parse(buffer2string(aCredInfo.response.clientDataJSON));
     is(clientData.challenge, bytesToBase64UrlSafe(gCredentialChallenge), "Challenge is correct");
     is(clientData.origin, window.location.origin, "Origin is correct");
     is(clientData.hashAlg, "S256", "Hash algorithm is correct");
 
-    return decodeU2FRegistration(aCredInfo.attestation.attestation)
+    return decodeU2FRegistration(aCredInfo.response.attestationObject)
     .then(function(u2fObj) {
       aCredInfo.u2fReg = u2fObj;
       return aCredInfo;
     });
   }
 
   function checkAssertionAndSigValid(aPublicKey, aAssertion) {
-    /* WebAuthnAssertion
-    - Credential
-    -- ID: ID of Credential from AllowList that succeeded
-    -- Type: "ScopedCred"
-    - ClientData: serialized JSON
-    - AuthenticatorData: RP ID Hash || U2F Sign() Response
-    - Signature: U2F Sign() Response */
+    /* PublicKeyCredential : Credential
+       - rawId: ID of Credential from AllowList that succeeded
+       - response : AuthenticatorAssertionResponse : AuthenticatorResponse
+         - clientDataJSON: serialized JSON
+         - authenticatorData: RP ID Hash || U2F Sign() Response
+         - signature: U2F Sign() Response
+    */
 
-    is(aAssertion.credential.type, "ScopedCred", "Type is correct");
-    ok(aAssertion.credential.id.length > 0, "Key ID exists");
+    ok(aAssertion.rawId.length > 0, "Key ID exists");
 
-    ok(aAssertion.authenticatorData.length > 0, "Authenticator data exists");
-    let clientData = JSON.parse(buffer2string(aAssertion.clientData));
+    ok(aAssertion.response.authenticatorData.length > 0, "Authenticator data exists");
+    let clientData = JSON.parse(buffer2string(aAssertion.response.clientDataJSON));
     is(clientData.challenge, bytesToBase64UrlSafe(gAssertionChallenge), "Challenge is correct");
     is(clientData.origin, window.location.origin, "Origin is correct");
     is(clientData.hashAlg, "S256", "Hash algorithm is correct");
 
     // Parse the signature data
-    if (aAssertion.signature[0] != 0x01) {
+    if (aAssertion.response.signature[0] != 0x01) {
       throw "User presence byte not set";
     }
-    let presenceAndCounter = aAssertion.signature.slice(0,5);
-    let signatureValue = aAssertion.signature.slice(5);
+    let presenceAndCounter = aAssertion.response.signature.slice(0,5);
+    let signatureValue = aAssertion.response.signature.slice(5);
 
-    let rpIdHash = aAssertion.authenticatorData.slice(0,32);
+    let rpIdHash = aAssertion.response.authenticatorData.slice(0,32);
 
     // Assemble the signed data and verify the signature
-    return deriveAppAndChallengeParam(clientData.origin, aAssertion.clientData)
+    return deriveAppAndChallengeParam(clientData.origin, aAssertion.response.clientDataJSON)
     .then(function(aParams) {
       console.log(aParams.appParam, rpIdHash, presenceAndCounter, aParams.challengeParam);
-      console.log("ClientData buffer: ", hexEncode(aAssertion.clientData));
+      console.log("ClientData buffer: ", hexEncode(aAssertion.response.clientDataJSON));
       console.log("ClientDataHash: ", hexEncode(aParams.challengeParam));
       return assembleSignedData(aParams.appParam, presenceAndCounter, aParams.challengeParam);
     })
     .then(function(aSignedData) {
       console.log(aPublicKey, aSignedData, signatureValue);
       return verifySignature(aPublicKey, aSignedData, signatureValue);
     })
   }
 
   function testMakeCredential() {
-    let acct = {rpDisplayName: "none", displayName: "none", id: "none"};
-    let param = {type: "ScopedCred", algorithm: "p-256"};
-
-    authn.makeCredential(acct, [param], gCredentialChallenge)
+    let rp = {id: document.origin, name: "none", icon: "none"};
+    let user = {id: "none", name: "none", icon: "none", displayName: "none"};
+    let param = {type: "public-key", algorithm: "P-256"};
+    let makeCredentialOptions = {
+      rp: rp,
+      user: user,
+      challenge: gCredentialChallenge,
+      parameters: [param]
+    };
+    credm.create({publicKey: makeCredentialOptions})
     .then(checkCredentialValid)
     .then(testMakeDuplicate)
     .catch(function(aReason) {
       ok(false, aReason);
       SimpleTest.finish();
     });
   }
 
   function testMakeDuplicate(aCredInfo) {
-    let acct = {rpDisplayName: "none", displayName: "none", id: "none"};
-    let param = {type: "ScopedCred", algorithm: "p-256"};
-    let options = {rpId: document.origin,
-                   excludeList: [aCredInfo.credential]};
-
-    authn.makeCredential(acct, [param], gCredentialChallenge, options)
+    let rp = {id: document.origin, name: "none", icon: "none"};
+    let user = {id: "none", name: "none", icon: "none", displayName: "none"};
+    let param = {type: "public-key", algorithm: "P-256"};
+    let makeCredentialOptions = {
+      rp: rp,
+      user: user,
+      challenge: gCredentialChallenge,
+      parameters: [param],
+      excludeList: [{type: "public-key", id: Uint8Array.from(aCredInfo.rawId),
+                     transports: ["usb"]}]
+    };
+    credm.create({publicKey: makeCredentialOptions})
     .then(function() {
       // We should have errored here!
       ok(false, "The excludeList didn't stop a duplicate being created!");
       SimpleTest.finish();
     })
     .catch(function(aReason) {
-      ok(aReason.toString().startsWith("NotAllowedError"), "Expect NotAllowedError, got" + aReason);
+      ok(aReason.toString().startsWith("NotAllowedError"), "Expect NotAllowedError, got " + aReason);
       testAssertion(aCredInfo);
     });
   }
 
   function testAssertion(aCredInfo) {
     let newCredential = {
-      type: aCredInfo.credential.type,
-      id: Uint8Array.from(aCredInfo.credential.id),
-      transports: [ "usb" ],
+      type: "public-key",
+      id: Uint8Array.from(aCredInfo.rawId),
+      transports: ["usb"],
     }
 
-    let assertOptions = {rpId: document.origin, timeoutSeconds: 5,
-                         allowList: [ newCredential ]};
-    authn.getAssertion(gAssertionChallenge, assertOptions)
+    let publicKeyCredentialRequestOptions = {
+      challenge: gAssertionChallenge,
+      timeout: 5000, // the minimum timeout is actually 15 seconds
+      rpId: document.origin,
+      allowList: [newCredential]
+    };
+    credm.get({publicKey: publicKeyCredentialRequestOptions})
     .then(function(aAssertion) {
       /* Pass along the pubKey. */
       return checkAssertionAndSigValid(aCredInfo.u2fReg.publicKey, aAssertion);
     })
     .then(function(aSigVerifyResult) {
       ok(aSigVerifyResult, "Signing signature verified");
       SimpleTest.finish();
     })
--- a/dom/webauthn/tests/test_webauthn_make_credential.html
+++ b/dom/webauthn/tests/test_webauthn_make_credential.html
@@ -11,186 +11,238 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
   <h1>Test for MakeCredential for W3C Web Authentication</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";
+    "use strict";
 
-   // Execute the full-scope test
-   SimpleTest.waitForExplicitFinish();
+    // Execute the full-scope test
+    SimpleTest.waitForExplicitFinish();
 
-   function arrivingHereIsGood(aResult) {
-     ok(true, "Good result! Received a: " + aResult);
-     return Promise.resolve();
-   }
+    function arrivingHereIsGood(aResult) {
+      ok(true, "Good result! Received a: " + aResult);
+      return Promise.resolve();
+    }
 
-   function arrivingHereIsBad(aResult) {
-     ok(false, "Bad result! Received a: " + aResult);
-     return Promise.resolve();
-   }
+    function arrivingHereIsBad(aResult) {
+      ok(false, "Bad result! Received a: " + aResult);
+      return Promise.resolve();
+    }
 
-   function expectNotAllowedError(aResult) {
-     ok(aResult.toString().startsWith("NotAllowedError"), "Expecting a NotAllowedError");
-     return Promise.resolve();
-   }
+    function expectNotAllowedError(aResult) {
+      ok(aResult.toString().startsWith("NotAllowedError"), "Expecting a NotAllowedError");
+      return Promise.resolve();
+    }
+
+    function expectTypeError(aResult) {
+      ok(aResult.toString().startsWith("TypeError"), "Expecting a TypeError");
+      return Promise.resolve();
+    }
 
-   function expectTypeError(aResult) {
-     ok(aResult.toString().startsWith("TypeError"), "Expecting a TypeError");
-     return Promise.resolve();
-   }
-
-   function expectNotSupportedError(aResult) {
-     ok(aResult.toString().startsWith("NotSupportedError"), "Expecting a NotSupportedError");
-     return Promise.resolve();
-   }
+    function expectNotSupportedError(aResult) {
+      ok(aResult.toString().startsWith("NotSupportedError"), "Expecting a NotSupportedError");
+      return Promise.resolve();
+    }
 
-   SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn", true],
-                                      ["security.webauth.webauthn_enable_softtoken", true],
-                                      ["security.webauth.webauthn_enable_usbtoken", false]]}, runTests);
-   function runTests() {
-     isnot(navigator.authentication, undefined, "WebAuthn API endpoint must exist");
-     isnot(navigator.authentication.makeCredential, undefined, "WebAuthn makeCredential API endpoint must exist");
-     isnot(navigator.authentication.getAssertion, undefined, "WebAuthn getAssertion API endpoint must exist");
+    SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn", true],
+                                       ["security.webauth.webauthn_enable_softtoken", true],
+                                       ["security.webauth.webauthn_enable_usbtoken", false]]}, runTests);
+    function runTests() {
+      is(navigator.authentication, undefined, "navigator.authentication does not exist any longer");
+      isnot(navigator.credentials, undefined, "Credential Management API endpoint must exist");
+      isnot(navigator.credentials.create, undefined, "CredentialManagement create API endpoint must exist");
+      isnot(navigator.credentials.get, undefined, "CredentialManagement get API endpoint must exist");
 
-     let authn = navigator.authentication;
+      let credm = navigator.credentials;
+
+      let gCredentialChallenge = new Uint8Array(16);
+      window.crypto.getRandomValues(gCredentialChallenge);
+
+      let rp = {id: "none", name: "none", icon: "none"};
+      let user = {id: "none", name: "none", icon: "none", displayName: "none"};
+      let param = {type: "public-key", algorithm: "p-256"};
+      let unsupportedParam = {type: "public-key", algorithm: "3DES"};
+      let badParam = {type: "SimplePassword", algorithm: "MaxLength=2"};
 
-     let gCredentialChallenge = new Uint8Array(16);
-     window.crypto.getRandomValues(gCredentialChallenge);
-
-     let acct = {rpDisplayName: "none", displayName: "none", id: "none"};
-     let param = {type: "ScopedCred", algorithm: "p-256"};
-     let unsupportedParam = {type: "ScopedCred", algorithm: "3DES"};
-     let badParam = {type: "SimplePassword", algorithm: "MaxLength=2"};
+      var testFuncs = [
+        // Test basic good call
+        function() {
+          let makeCredentialOptions = {
+            rp: rp, user: user, challenge: gCredentialChallenge, parameters: [param]
+          };
+          return credm.create({publicKey: makeCredentialOptions})
+                      .then(arrivingHereIsGood)
+                      .catch(arrivingHereIsBad);
+        },
 
-     var testFuncs = [
-       // Test basic good call
-       function() {
-         return authn.makeCredential(acct, [param], gCredentialChallenge)
-              .then(arrivingHereIsGood)
-              .catch(arrivingHereIsBad);
-       },
+        // Test empty account
+        function() {
+          let makeCredentialOptions = {
+            challenge: gCredentialChallenge, parameters: [param]
+          };
+          return credm.create({publicKey: makeCredentialOptions})
+                      .then(arrivingHereIsBad)
+                      .catch(expectTypeError);
+        },
+
+        // Test without a parameter
+        function() {
+          let makeCredentialOptions = {
+            rp: rp, user: user, challenge: gCredentialChallenge, parameters: []
+          };
+          return credm.create({publicKey: makeCredentialOptions})
+                      .then(arrivingHereIsBad)
+                      .catch(expectNotSupportedError);
+        },
 
-       // Test empty account
-       function() {
-         return authn.makeCredential({}, [param], gCredentialChallenge)
-                     .then(arrivingHereIsBad)
-                     .catch(expectTypeError);
-       },
-
-       // Test without a parameter
-       function() {
-         return authn.makeCredential(acct, [], gCredentialChallenge)
-                     .then(arrivingHereIsBad)
-                     .catch(expectNotSupportedError);
-       },
+        // Test without a parameter array at all
+        function() {
+          let makeCredentialOptions = {
+            rp: rp, user: user, challenge: gCredentialChallenge
+          };
+          return credm.create({publicKey: makeCredentialOptions})
+               .then(arrivingHereIsBad)
+               .catch(expectTypeError);
+        },
 
-       // Test without a parameter array at all
-       function() {
-         return authn.makeCredential(acct, null, gCredentialChallenge)
-              .then(arrivingHereIsBad)
-              .catch(expectTypeError);
-       },
+        // Test with an unsupported parameter
+        function() {
+          let makeCredentialOptions = {
+            rp: rp, user: user, challenge: gCredentialChallenge, parameters: [unsupportedParam]
+          };
+          return credm.create({publicKey: makeCredentialOptions})
+                      .then(arrivingHereIsBad)
+                      .catch(expectNotSupportedError);
+        },
 
-       // Test with an unsupported parameter
-       function() {
-         return authn.makeCredential(acct, [unsupportedParam], gCredentialChallenge)
-              .then(arrivingHereIsBad)
-              .catch(expectNotSupportedError);
-       },
+        // Test with an unsupported parameter and a good one
+        function() {
+          let makeCredentialOptions = {
+            rp: rp, user: user, challenge: gCredentialChallenge,
+            parameters: [param, unsupportedParam]
+          };
+          return credm.create({publicKey: makeCredentialOptions})
+                      .then(arrivingHereIsGood)
+                      .catch(arrivingHereIsBad);
+        },
 
-       // Test with an unsupported parameter and a good one
-       function() {
-         return authn.makeCredential(acct, [unsupportedParam, param], gCredentialChallenge)
-              .then(arrivingHereIsGood)
-              .catch(arrivingHereIsBad);
-       },
+        // Test with a bad parameter
+        function() {
+          let makeCredentialOptions = {
+            rp: rp, user: user, challenge: gCredentialChallenge, parameters: [badParam]
+          };
+          return credm.create({publicKey: makeCredentialOptions})
+               .then(arrivingHereIsBad)
+               .catch(expectTypeError);
+        },
 
-       // Test with a bad parameter
-       function() {
-         return authn.makeCredential(acct, [badParam], gCredentialChallenge)
-              .then(arrivingHereIsBad)
-              .catch(expectTypeError);
-       },
-
-       // Test with an unsupported parameter, and a bad one
-       function() {
-         return authn.makeCredential(acct, [unsupportedParam, badParam],
-                              gCredentialChallenge)
-              .then(arrivingHereIsBad)
-              .catch(expectTypeError);
-       },
+        // Test with an unsupported parameter, and a bad one
+        function() {
+          let makeCredentialOptions = {
+            rp: rp, user: user, challenge: gCredentialChallenge,
+            parameters: [unsupportedParam, badParam]
+          };
+          return credm.create({publicKey: makeCredentialOptions})
+                      .then(arrivingHereIsBad)
+                      .catch(expectTypeError);
+        },
 
-       // Test with an unsupported parameter, a bad one, and a good one. This
-       // should still fail, as anything with a badParam should fail.
-       function() {
-         return authn.makeCredential(acct, [unsupportedParam, badParam, param],
-                              gCredentialChallenge)
-              .then(arrivingHereIsBad)
-              .catch(expectTypeError);
-       },
+        // Test with an unsupported parameter, a bad one, and a good one. This
+        // should still fail, as anything with a badParam should fail.
+        function() {
+          let makeCredentialOptions = {
+            rp: rp, user: user, challenge: gCredentialChallenge,
+            parameters: [param, unsupportedParam, badParam]
+          };
+          return credm.create({publicKey: makeCredentialOptions})
+               .then(arrivingHereIsBad)
+               .catch(expectTypeError);
+        },
 
-       // Test without a challenge
-       function() {
-         return authn.makeCredential(acct, [param], null)
-              .then(arrivingHereIsBad)
-              .catch(expectTypeError);
-       },
+        // Test without a challenge
+        function() {
+          let makeCredentialOptions = {
+            rp: rp, user: user, parameters: [param]
+          };
+          return credm.create({publicKey: makeCredentialOptions})
+                      .then(arrivingHereIsBad)
+                      .catch(expectTypeError);
+        },
 
-       // Test with an invalid challenge
-       function() {
-         return authn.makeCredential(acct, [param], "begone, thou ill-fitting moist glove!")
-              .then(arrivingHereIsBad)
-              .catch(expectTypeError);
-       },
+        // Test with an invalid challenge
+        function() {
+          let makeCredentialOptions = {
+            rp: rp, user: user, challenge: "begone, thou ill-fitting moist glove!",
+            parameters: [unsupportedParam]
+          };
+          return credm.create({publicKey: makeCredentialOptions})
+               .then(arrivingHereIsBad)
+               .catch(expectTypeError);
+        },
 
-       // Test with duplicate parameters
-       function() {
-         return authn.makeCredential(acct, [param, param, param], gCredentialChallenge)
-              .then(arrivingHereIsGood)
-              .catch(arrivingHereIsBad);
-       },
+        // Test with duplicate parameters
+        function() {
+          let makeCredentialOptions = {
+            rp: rp, user: user, challenge: gCredentialChallenge,
+            parameters: [param, param, param]
+          };
+          return credm.create({publicKey: makeCredentialOptions})
+                      .then(arrivingHereIsGood)
+                      .catch(arrivingHereIsBad);
+        },
 
-       // Test an incomplete account
-       function() {
-         return authn.makeCredential({id: "none"
-         }, [param], gCredentialChallenge)
-              .then(arrivingHereIsBad)
-              .catch(expectTypeError);
-       },
-
-       function() {
-         return authn.makeCredential({name: "none", imageURL: "http://example.com/404"},
-                              [param], gCredentialChallenge)
-              .then(arrivingHereIsBad)
-              .catch(expectTypeError);
-       },
+        // Test with missing rp
+        function() {
+          let makeCredentialOptions = {
+            user: user, challenge: gCredentialChallenge, parameters: [param]
+          };
+          return credm.create({publicKey: makeCredentialOptions})
+                      .then(arrivingHereIsBad)
+                      .catch(expectTypeError);
+        },
 
-       // Test a complete account
-       function() {
-         return authn.makeCredential({rpDisplayName: "Foxxy", displayName: "Foxxy V",
-                                      id: "foxes_are_the_best@example.com",
-                                      name: "Fox F. Foxington",
-                                      imageURL: "https://example.com/fox.svg"},
-                                     [param], gCredentialChallenge)
-                     .then(arrivingHereIsGood)
-                     .catch(arrivingHereIsBad);
-       }];
+        // Test with missing user
+        function() {
+          let makeCredentialOptions = {
+            rp: rp, challenge: gCredentialChallenge, parameters: [param]
+          };
+          return credm.create({publicKey: makeCredentialOptions})
+                      .then(arrivingHereIsBad)
+                      .catch(expectTypeError);
+        },
 
-     var i = 0;
-     var runNextTest = () => {
-       if (i == testFuncs.length) {
-         SimpleTest.finish();
-         return;
-       }
-       testFuncs[i]().then(() => { runNextTest(); });
-       i = i + 1;
-     };
-     runNextTest();
-   };
+        // Test a complete account
+        function() {
+          let completeRP = {id: "Foxxy RP", name: "Foxxy Name",
+                            icon: "https://example.com/fox.svg"};
+          let completeUser = {id: "foxes_are_the_best@example.com",
+                              name: "Fox F. Foxington",
+                              icon: "https://example.com/fox.svg",
+                              displayName: "Foxxy V"};
+          let makeCredentialOptions = {
+            rp: completeRP, user: completeUser, challenge: gCredentialChallenge,
+            parameters: [param]
+          };
+          return credm.create({publicKey: makeCredentialOptions})
+                      .then(arrivingHereIsGood)
+                      .catch(arrivingHereIsBad);
+        }];
+
+      var i = 0;
+      var runNextTest = () => {
+        if (i == testFuncs.length) {
+          SimpleTest.finish();
+          return;
+        }
+        testFuncs[i]().then(() => { runNextTest(); });
+        i = i + 1;
+      };
+      runNextTest();
+    };
 
   </script>
 
 </body>
 </html>
--- a/dom/webauthn/tests/test_webauthn_no_token.html
+++ b/dom/webauthn/tests/test_webauthn_no_token.html
@@ -21,54 +21,63 @@
 // 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", false],
                                    ["security.webauth.webauthn_enable_usbtoken", false]]},
 function() {
-  isnot(navigator.authentication, undefined, "WebAuthn API endpoint must exist");
-  isnot(navigator.authentication.makeCredential, undefined, "WebAuthn makeCredential API endpoint must exist");
-  isnot(navigator.authentication.getAssertion, undefined, "WebAuthn getAssertion API endpoint must exist");
+  is(navigator.authentication, undefined, "navigator.authentication does not exist any longer");
+  isnot(navigator.credentials, undefined, "Credential Management API endpoint must exist");
+  isnot(navigator.credentials.create, undefined, "CredentialManagement create API endpoint must exist");
+  isnot(navigator.credentials.get, undefined, "CredentialManagement get API endpoint must exist");
 
-  let authn = navigator.authentication;
+  let credm = navigator.credentials;
 
   let credentialChallenge = new Uint8Array(16);
   window.crypto.getRandomValues(credentialChallenge);
   let assertionChallenge = new Uint8Array(16);
   window.crypto.getRandomValues(assertionChallenge);
   let credentialId = new Uint8Array(128);
   window.crypto.getRandomValues(credentialId);
 
   testMakeCredential();
 
   function testMakeCredential() {
-    let acct = {rpDisplayName: "none", displayName: "none", id: "none"};
-    let param = {type: "ScopedCred", algorithm: "p-256"};
-    authn.makeCredential(acct, [param], credentialChallenge)
+    let rp = {id: "none", name: "none", icon: "none"};
+    let user = {id: "none", name: "none", icon: "none", displayName: "none"};
+    let param = {type: "public-key", algorithm: "p-256"};
+    let makeCredentialOptions = {
+      rp: rp, user: user, challenge: credentialChallenge, parameters: [param]
+    };
+    credm.create({publicKey: makeCredentialOptions})
     .then(function(aResult) {
       ok(false, "Should have failed.");
       testAssertion();
     })
     .catch(function(aReason) {
       ok(aReason.toString().startsWith("NotAllowedError"), aReason);
       testAssertion();
     });
   }
 
   function testAssertion() {
     let newCredential = {
-      type: "ScopedCred",
+      type: "public-key",
       id: credentialId,
-      transports: [ "usb" ],
+      transports: ["usb"],
     }
-    let assertOptions = {rpId: document.origin, timeoutSeconds: 5,
-                         allowList: [ newCredential ]};
-    authn.getAssertion(assertionChallenge, assertOptions)
+    let publicKeyCredentialRequestOptions = {
+      challenge: assertionChallenge,
+      timeout: 5000, // the minimum timeout is actually 15 seconds
+      rpId: document.origin,
+      allowList: [newCredential]
+    };
+    credm.get({publicKey: publicKeyCredentialRequestOptions})
     .then(function(aResult) {
       ok(false, "Should have failed.");
       SimpleTest.finish();
     })
     .catch(function(aReason) {
       ok(aReason.toString().startsWith("NotAllowedError"), aReason);
       SimpleTest.finish();
     })
--- a/dom/webauthn/tests/test_webauthn_sameorigin.html
+++ b/dom/webauthn/tests/test_webauthn_sameorigin.html
@@ -11,176 +11,228 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
   <h1>Test Same Origin Policy for W3C Web Authentication</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";
+    "use strict";
 
-   // Execute the full-scope test
-   SimpleTest.waitForExplicitFinish();
+    // Execute the full-scope test
+    SimpleTest.waitForExplicitFinish();
 
-   var gTrackedCredential = {};
+    var gTrackedCredential = {};
 
-   function arrivingHereIsGood(aResult) {
-     ok(true, "Good result! Received a: " + aResult);
-     return Promise.resolve();
-   }
+    function arrivingHereIsGood(aResult) {
+      ok(true, "Good result! Received a: " + aResult);
+      return Promise.resolve();
+    }
 
-   function arrivingHereIsBad(aResult) {
-     // TODO: Change to `ok` when Bug 1329764 lands
-     todo(false, "Bad result! Received a: " + aResult);
-     return Promise.resolve();
-   }
+    function arrivingHereIsBad(aResult) {
+      // TODO: Change to `ok` when Bug 1329764 lands
+      todo(false, "Bad result! Received a: " + aResult);
+      return Promise.resolve();
+    }
 
-   function expectSecurityError(aResult) {
-     // TODO: Change to `ok` when Bug 1329764 lands
-     todo(aResult.toString().startsWith("SecurityError"), "Expecting a SecurityError");
-     return Promise.resolve();
-   }
+    function expectSecurityError(aResult) {
+      // TODO: Change to `ok` when Bug 1329764 lands
+      todo(aResult.toString().startsWith("SecurityError"), "Expecting a SecurityError");
+      return Promise.resolve();
+    }
 
-   function keepThisScopedCredential(aScopedCredInfo) {
-     gTrackedCredential = {
-       type: aScopedCredInfo.credential.type,
-       id: Uint8Array.from(aScopedCredInfo.credential.id),
-       transports: [ "usb" ],
-     }
-     return Promise.resolve(aScopedCredInfo);
-   }
+    function keepThisPublicKeyCredential(aPublicKeyCredential) {
+      gTrackedCredential = {
+        type: "public-key",
+        id: Uint8Array.from(aPublicKeyCredential.rawId),
+        transports: [ "usb" ],
+      }
+      return Promise.resolve(aPublicKeyCredential);
+    }
 
-   function runTests() {
-     isnot(navigator.authentication, undefined, "WebAuthn API endpoint must exist");
-     isnot(navigator.authentication.makeCredential, undefined,
-           "WebAuthn makeCredential API endpoint must exist");
-     isnot(navigator.authentication.getAssertion, undefined,
-           "WebAuthn getAssertion API endpoint must exist");
+    function runTests() {
+      is(navigator.authentication, undefined, "navigator.authentication does not exist any longer");
+      isnot(navigator.credentials, undefined, "Credential Management API endpoint must exist");
+      isnot(navigator.credentials.create, undefined, "CredentialManagement create API endpoint must exist");
+      isnot(navigator.credentials.get, undefined, "CredentialManagement get API endpoint must exist");
+
+      let credm = navigator.credentials;
 
-     let authn = navigator.authentication;
+      let chall = new Uint8Array(16);
+      window.crypto.getRandomValues(chall);
 
-     let chall = new Uint8Array(16);
-     window.crypto.getRandomValues(chall);
-
-     let acct = {rpDisplayName: "none", displayName: "none", id: "none"};
-     let param = {type: "ScopedCred", algorithm: "p-256"};
+      let user = {id: "none", name: "none", icon: "none", displayName: "none"};
+      let param = {type: "public-key", algorithm: "p-256"};
 
-     var testFuncs = [
-       function() {
-         // Test basic good call
-         return authn.makeCredential(acct, [param], chall, {rpId: document.origin})
-                     .then(keepThisScopedCredential)
-                     .then(arrivingHereIsGood)
-                     .catch(arrivingHereIsBad);
-       },
+      var testFuncs = [
+        function() {
+          // Test basic good call
+          let rp = {id: document.origin};
+          let makeCredentialOptions = {
+            rp: rp, user: user, challenge: chall, parameters: [param]
+          };
+          return credm.create({publicKey: makeCredentialOptions})
+                      .then(keepThisPublicKeyCredential)
+                      .then(arrivingHereIsGood)
+                      .catch(arrivingHereIsBad);
+        },
 
-       function() {
-         // Test rpId being unset
-         return authn.makeCredential(acct, [param], chall, {})
-                     .then(arrivingHereIsGood)
-                     .catch(arrivingHereIsBad);
-       },
-       function() {
-         // Test this origin with optional fields
-         return authn.makeCredential(acct, [param], chall,
-                                     {rpId: "user:pass@" + document.origin + ":8888"})
-                     .then(arrivingHereIsBad)
-                     .catch(expectSecurityError);
-       },
-       function() {
-         // Test blank rpId
-         return authn.makeCredential(acct, [param], chall, {rpId: ""})
-                     .then(arrivingHereIsBad)
-                     .catch(expectSecurityError);
-       },
-       function() {
-         // Test subdomain of this origin
-         return authn.makeCredential(acct, [param], chall,
-                                     {rpId: "subdomain." + document.origin})
-                     .then(arrivingHereIsBad)
-                     .catch(expectSecurityError);
-       },
-       function() {
-         // Test another origin
-         return authn.makeCredential(acct, [param], chall, {rpId: "example.com"})
-                     .then(arrivingHereIsBad)
-                     .catch(expectSecurityError);
-       },
-       function () {
-         // est a different domain within the same TLD
-         return authn.makeCredential(acct, [param], chall, {rpId: "alt.test"})
-                     .then(arrivingHereIsBad)
-                     .catch(expectSecurityError);
-       },
-       function () {
-         // Test basic good call
-         return authn.getAssertion(chall, {allowList: [ gTrackedCredential ],
-                                           rpId: document.origin})
-                     .then(arrivingHereIsGood)
-                     .catch(arrivingHereIsBad);
-       },
-       function () {
-         // Test rpId being unset
-         return authn.getAssertion(chall, {allowList: [ gTrackedCredential ]})
-                     .then(arrivingHereIsGood)
-                     .catch(arrivingHereIsBad);
-       },
-       function () {
-         // Test this origin with optional fields
-         return authn.getAssertion(chall, {allowList: [ gTrackedCredential ],
-                                           rpId: "user:pass@" + document.origin + ":8888"})
-                     .then(arrivingHereIsBad)
-                     .catch(expectSecurityError);
-       },
-       function () {
-         // Test blank rpId
-         return authn.getAssertion(chall, {allowList: [ gTrackedCredential ],
-                                           rpId: ""})
-                     .then(arrivingHereIsBad)
-                     .catch(expectSecurityError);
-       },
-       function () {
-         // Test subdomain of this origin
-         return authn.getAssertion(chall, {allowList: [ gTrackedCredential ],
-                                           rpId: "subdomain." + document.origin})
-                     .then(arrivingHereIsBad)
-                     .catch(expectSecurityError);
-       },
-       function () {
-         // Test another origin
-         return authn.getAssertion(chall, {allowList: [ gTrackedCredential ],
-                                           rpId: "example.com"})
-                     .then(arrivingHereIsBad)
-                     .catch(expectSecurityError);
-       },
-       function () {
-         // Test a different domain within the same TLD
-         return authn.getAssertion(chall, {allowList: [ gTrackedCredential ],
-                                           rpId: "alt.test"})
-                     .then(arrivingHereIsBad)
-                     .catch(expectSecurityError)
-       },
-       function () {
-         SimpleTest.finish();
-       }
-     ];
-     var i = 0;
-     var runNextTest = () => {
-       if (i == testFuncs.length) {
-         SimpleTest.finish();
-         return;
-       }
-       console.log(i);
-       testFuncs[i]().then(() => { runNextTest(); });
-       i = i + 1;
-     };
-     runNextTest();
-   };
-   SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn", true],
-                                      ["security.webauth.webauthn_enable_softtoken", true],
-                                      ["security.webauth.webauthn_enable_usbtoken", false]]},
-                             runTests);
+        function() {
+          // Test rp.id being unset
+          let makeCredentialOptions = {
+            rp: {}, user: user, challenge: chall, parameters: [param]
+          };
+          return credm.create({publicKey: makeCredentialOptions})
+                      .then(arrivingHereIsGood)
+                      .catch(arrivingHereIsBad);
+        },
+        function() {
+          // Test this origin with optional fields
+          let rp = {id: "user:pass@" + document.origin + ":8888"};
+          let makeCredentialOptions = {
+            rp: rp, user: user, challenge: chall, parameters: [param]
+          };
+          return credm.create({publicKey: makeCredentialOptions})
+                      .then(arrivingHereIsBad)
+                      .catch(expectSecurityError);
+        },
+        function() {
+          // Test blank rp.id
+          let rp = {id: ""};
+          let makeCredentialOptions = {
+            rp: rp, user: user, challenge: chall, parameters: [param]
+          };
+          return credm.create({publicKey: makeCredentialOptions})
+                      .then(arrivingHereIsBad)
+                      .catch(expectSecurityError);
+        },
+        function() {
+          // Test subdomain of this origin
+          let rp = {id: "subdomain." + document.origin};
+          let makeCredentialOptions = {
+            rp: rp, user: user, challenge: chall, parameters: [param]
+          };
+          return credm.create({publicKey: makeCredentialOptions})
+                      .then(arrivingHereIsBad)
+                      .catch(expectSecurityError);
+        },
+        function() {
+          // Test another origin
+          let rp = {id: "example.com"};
+          let makeCredentialOptions = {
+            rp: rp, user: user, challenge: chall, parameters: [param]
+          };
+          return credm.create({publicKey: makeCredentialOptions})
+                      .then(arrivingHereIsBad)
+                      .catch(expectSecurityError);
+        },
+        function () {
+          // Test a different domain within the same TLD
+          let rp = {id: "alt.test"};
+          let makeCredentialOptions = {
+            rp: rp, user: user, challenge: chall, parameters: [param]
+          };
+          return credm.create({publicKey: makeCredentialOptions})
+                      .then(arrivingHereIsBad)
+                      .catch(expectSecurityError);
+        },
+        function () {
+          // Test basic good call
+          let publicKeyCredentialRequestOptions = {
+            challenge: chall,
+            rpId: document.origin,
+            allowList: [gTrackedCredential]
+          };
+          return credm.get({publicKey: publicKeyCredentialRequestOptions})
+                      .then(arrivingHereIsGood)
+                      .catch(arrivingHereIsBad);
+        },
+        function () {
+          // Test rpId being unset
+          let publicKeyCredentialRequestOptions = {
+            challenge: chall,
+            allowList: [gTrackedCredential]
+          };
+          return credm.get({publicKey: publicKeyCredentialRequestOptions})
+                      .then(arrivingHereIsGood)
+                      .catch(arrivingHereIsBad);
+        },
+        function () {
+          // Test this origin with optional fields
+          let publicKeyCredentialRequestOptions = {
+            challenge: chall,
+            rpId: "user:pass@" + document.origin + ":8888",
+            allowList: [gTrackedCredential]
+          };
+          return credm.get({publicKey: publicKeyCredentialRequestOptions})
+                      .then(arrivingHereIsBad)
+                      .catch(expectSecurityError);
+        },
+        function () {
+          // Test blank rpId
+          let publicKeyCredentialRequestOptions = {
+            challenge: chall,
+            rpId: "",
+            allowList: [gTrackedCredential]
+          };
+          return credm.get({publicKey: publicKeyCredentialRequestOptions})
+                      .then(arrivingHereIsBad)
+                      .catch(expectSecurityError);
+        },
+        function () {
+          // Test subdomain of this origin
+          let publicKeyCredentialRequestOptions = {
+            challenge: chall,
+            rpId: "subdomain." + document.origin,
+            allowList: [gTrackedCredential]
+          };
+          return credm.get({publicKey: publicKeyCredentialRequestOptions})
+                      .then(arrivingHereIsBad)
+                      .catch(expectSecurityError);
+        },
+        function () {
+          // Test another origin
+          let publicKeyCredentialRequestOptions = {
+            challenge: chall,
+            rpId: "example.com",
+            allowList: [gTrackedCredential]
+          };
+          return credm.get({publicKey: publicKeyCredentialRequestOptions})
+                      .then(arrivingHereIsBad)
+                      .catch(expectSecurityError);
+        },
+        function () {
+          // Test a different domain within the same TLD
+          let publicKeyCredentialRequestOptions = {
+            challenge: chall,
+            rpId: "alt.test",
+            allowList: [gTrackedCredential]
+          };
+          return credm.get({publicKey: publicKeyCredentialRequestOptions})
+                      .then(arrivingHereIsBad)
+                      .catch(expectSecurityError)
+        },
+        function () {
+          SimpleTest.finish();
+        }
+      ];
+      var i = 0;
+      var runNextTest = () => {
+        if (i == testFuncs.length) {
+          SimpleTest.finish();
+          return;
+        }
+        console.log(i);
+        testFuncs[i]().then(() => { runNextTest(); });
+        i = i + 1;
+      };
+      runNextTest();
+    };
+    SpecialPowers.pushPrefEnv({"set": [["security.webauth.webauthn", true],
+                                       ["security.webauth.webauthn_enable_softtoken", true],
+                                       ["security.webauth.webauthn_enable_usbtoken", false]]},
+                              runTests);
 
   </script>
 
 </body>
 </html>
new file mode 100644
--- /dev/null
+++ b/dom/webidl/CredentialManagement.webidl
@@ -0,0 +1,28 @@
+/* -*- 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/.
+ *
+ * The origin of this IDL file is
+ * https://www.w3.org/TR/credential-management-1/
+ */
+
+[Exposed=Window, SecureContext, Pref="security.webauth.webauthn"]
+interface Credential {
+  readonly attribute USVString id;
+  readonly attribute DOMString type;
+};
+
+[Exposed=Window, SecureContext, Pref="security.webauth.webauthn"]
+interface CredentialsContainer {
+  Promise<Credential?> get(optional CredentialRequestOptions options);
+  Promise<Credential?> create(optional CredentialCreationOptions options);
+};
+
+dictionary CredentialRequestOptions {
+  PublicKeyCredentialRequestOptions publicKey;
+};
+
+dictionary CredentialCreationOptions {
+  MakeCredentialOptions publicKey;
+};
--- a/dom/webidl/Navigator.webidl
+++ b/dom/webidl/Navigator.webidl
@@ -366,10 +366,10 @@ partial interface Navigator {
 
 [NoInterfaceObject, Exposed=(Window,Worker)]
 interface NavigatorConcurrentHardware {
   readonly attribute unsigned long long hardwareConcurrency;
 };
 
 partial interface Navigator {
   [Pref="security.webauth.webauthn", SameObject]
-  readonly attribute WebAuthentication authentication;
+  readonly attribute CredentialsContainer credentials;
 };
--- a/dom/webidl/WebAuthentication.webidl
+++ b/dom/webidl/WebAuthentication.webidl
@@ -5,109 +5,107 @@
  *
  * The origin of this IDL file is
  * https://www.w3.org/TR/webauthn/
  */
 
 /***** Interfaces to Data *****/
 
 [SecureContext, Pref="security.webauth.webauthn"]
-interface ScopedCredentialInfo {
-    readonly attribute ScopedCredential    credential;
-    readonly attribute WebAuthnAttestation attestation;
+interface PublicKeyCredential : Credential {
+    readonly attribute ArrayBuffer           rawId;
+    readonly attribute AuthenticatorResponse response;
+    // Extensions are not supported yet.
+    // readonly attribute AuthenticationExtensions clientExtensionResults;
+};
+
+[SecureContext, Pref="security.webauth.webauthn"]
+interface AuthenticatorResponse {
+    readonly attribute ArrayBuffer clientDataJSON;
+};
+
+[SecureContext, Pref="security.webauth.webauthn"]
+interface AuthenticatorAttestationResponse : AuthenticatorResponse {
+    readonly attribute ArrayBuffer attestationObject;
+};
+
+dictionary PublicKeyCredentialParameters {
+    required PublicKeyCredentialType  type;
+    required WebAuthnAlgorithmID algorithm; // NOTE: changed from AllgorithmIdentifier because typedef (object or DOMString) not serializable
+};
+
+dictionary PublicKeyCredentialUserEntity : PublicKeyCredentialEntity {
+    DOMString displayName;
+};
+
+dictionary MakeCredentialOptions {
+    required PublicKeyCredentialEntity rp;
+    required PublicKeyCredentialUserEntity user;
+
+    required BufferSource                         challenge;
+    required sequence<PublicKeyCredentialParameters> parameters;
+
+    unsigned long                        timeout;
+    sequence<PublicKeyCredentialDescriptor> excludeList;
+    AuthenticatorSelectionCriteria       authenticatorSelection;
+    // Extensions are not supported yet.
+    // AuthenticationExtensions             extensions;
 };
 
-dictionary Account {
-    required DOMString rpDisplayName;
-    required DOMString displayName;
-    required DOMString id;
-    DOMString          name;
-    DOMString          imageURL;
+dictionary PublicKeyCredentialEntity {
+    DOMString id;
+    DOMString name;
+    USVString icon;
+};
+
+dictionary AuthenticatorSelectionCriteria {
+    Attachment    attachment;
+    boolean       requireResidentKey = false;
+};
+
+enum Attachment {
+    "platform",
+    "cross-platform"
+};
+
+dictionary PublicKeyCredentialRequestOptions {
+    required BufferSource                challenge;
+    unsigned long                        timeout;
+    USVString                            rpId;
+    sequence<PublicKeyCredentialDescriptor> allowList = [];
+    // Extensions are not supported yet.
+    // AuthenticationExtensions             extensions;
+};
+
+dictionary CollectedClientData {
+    required DOMString           challenge;
+    required DOMString           origin;
+    required DOMString           hashAlg;
+    DOMString                    tokenBinding;
+    // Extensions are not supported yet.
+    // AuthenticationExtensions     clientExtensions;
+    // AuthenticationExtensions     authenticatorExtensions;
+};
+
+enum PublicKeyCredentialType {
+    "public-key"
+};
+
+dictionary PublicKeyCredentialDescriptor {
+    required PublicKeyCredentialType type;
+    required BufferSource id;
+    sequence<WebAuthnTransport>   transports;
 };
 
 typedef (boolean or DOMString) WebAuthnAlgorithmID; // Fix when upstream there's a definition of how to serialize AlgorithmIdentifier
 
-dictionary ScopedCredentialParameters {
-    required ScopedCredentialType type;
-    required WebAuthnAlgorithmID  algorithm; // NOTE: changed from AllgorithmIdentifier because typedef (object or DOMString) not serializable
-};
-
-dictionary ScopedCredentialOptions {
-    unsigned long                        timeoutSeconds;
-    USVString                            rpId;
-    sequence<ScopedCredentialDescriptor> excludeList;
-    WebAuthnExtensions                   extensions;
-};
-
 [SecureContext, Pref="security.webauth.webauthn"]
-interface WebAuthnAssertion {
-    readonly attribute ScopedCredential credential;
-    readonly attribute ArrayBuffer      clientData;
+interface AuthenticatorAssertionResponse : AuthenticatorResponse {
     readonly attribute ArrayBuffer      authenticatorData;
     readonly attribute ArrayBuffer      signature;
 };
 
-dictionary AssertionOptions {
-    unsigned long                        timeoutSeconds;
-    USVString                            rpId;
-    sequence<ScopedCredentialDescriptor> allowList;
-    WebAuthnExtensions                   extensions;
-};
-
-dictionary WebAuthnExtensions {
-};
-
-[SecureContext, Pref="security.webauth.webauthn"]
-interface WebAuthnAttestation {
-    readonly    attribute USVString     format;
-    readonly    attribute ArrayBuffer   clientData;
-    readonly    attribute ArrayBuffer   authenticatorData;
-    readonly    attribute any           attestation;
-};
-
-// Renamed from "ClientData" to avoid a collision with U2F
-dictionary WebAuthnClientData {
-    required DOMString           challenge;
-    required DOMString           origin;
-    required WebAuthnAlgorithmID hashAlg; // NOTE: changed from AllgorithmIdentifier because typedef (object or DOMString) not serializable
-    DOMString                    tokenBinding;
-    WebAuthnExtensions           extensions;
-};
-
-enum ScopedCredentialType {
-    "ScopedCred"
-};
-
-[SecureContext, Pref="security.webauth.webauthn"]
-interface ScopedCredential {
-    readonly attribute ScopedCredentialType type;
-    readonly attribute ArrayBuffer          id;
-};
-
-dictionary ScopedCredentialDescriptor {
-    required ScopedCredentialType type;
-    required BufferSource         id;
-    sequence <WebAuthnTransport>  transports;
-};
-
 // Renamed from "Transport" to avoid a collision with U2F
 enum WebAuthnTransport {
     "usb",
     "nfc",
     "ble"
 };
-
-/***** The Main API *****/
-
-[SecureContext, Pref="security.webauth.webauthn"]
-interface WebAuthentication {
-    Promise<ScopedCredentialInfo> makeCredential (
-        Account                                 accountInformation,
-        sequence<ScopedCredentialParameters>    cryptoParameters,
-        BufferSource                            attestationChallenge,
-        optional ScopedCredentialOptions        options
-    );
-
-    Promise<WebAuthnAssertion> getAssertion (
-        BufferSource               assertionChallenge,
-        optional AssertionOptions  options
-    );
-};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -439,16 +439,17 @@ WEBIDL_FILES = [
     'Comment.webidl',
     'CompositionEvent.webidl',
     'Console.webidl',
     'ConstantSourceNode.webidl',
     'ContainerBoxObject.webidl',
     'ConvolverNode.webidl',
     'Coordinates.webidl',
     'CreateOfferRequest.webidl',
+    'CredentialManagement.webidl',
     'Crypto.webidl',
     'CSPDictionaries.webidl',
     'CSPReport.webidl',
     'CSS.webidl',
     'CSSAnimation.webidl',
     'CSSConditionRule.webidl',
     'CSSCounterStyleRule.webidl',
     'CSSFontFaceRule.webidl',
--- a/layout/reftests/forms/input/datetime/reftest.list
+++ b/layout/reftests/forms/input/datetime/reftest.list
@@ -6,8 +6,19 @@ skip-if(Android) fails-if(styloVsGecko) 
 skip-if(Android) != time-width-height.html time-basic.html
 skip-if(Android) fails-if(styloVsGecko) != time-border.html time-basic.html
 # only valid on Android where type=number looks the same as type=text
 skip-if(!Android) fails-if(styloVsGecko) == time-simple-unthemed.html time-simple-unthemed-ref.html
 
 # type change
 skip-if(Android) fails-if(styloVsGecko) == to-time-from-other-type-unthemed.html time-simple-unthemed.html
 skip-if(Android) == from-time-to-other-type-unthemed.html from-time-to-other-type-unthemed-ref.html
+
+# content should not overflow on small width/height
+skip-if(Android) == time-small-width.html time-small-width-ref.html
+skip-if(Android) == time-small-height.html time-small-height-ref.html
+skip-if(Android) == time-small-width-height.html time-small-width-height-ref.html
+
+# content (text) should be left aligned
+skip-if(Android) == time-content-left-aligned.html time-content-left-aligned-ref.html
+
+# reset button should be right aligned
+skip-if(Android) fails-if(styloVsGecko) == time-reset-button-right-aligned.html time-reset-button-right-aligned-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/time-content-left-aligned-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+  <body>
+     <input type="time" style="width: 200px;">
+     <!-- div to cover the right area -->
+     <div style="display:block; position:absolute; background-color:black;
+                 top:0px; left:40px; width:200px; height:100px;"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/time-content-left-aligned.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+  <body>
+     <input type="time" style="width: 50px;">
+     <!-- div to cover the right area -->
+     <div style="display:block; position:absolute; background-color:black;
+                 top:0px; left:40px; width:200px; height:100px;"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/time-reset-button-right-aligned-ref.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <input type="time" value="10:00" style="float: right; color: white;">
+    <!-- div to cover the left area -->
+    <div style="display:block; position:absolute; background-color:black;
+                top:0px; right:30px; width:500px; height:100px;"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/time-reset-button-right-aligned.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html>
+  <body>
+    <input type="time" value="10:00" style="width: 150px; float: right;
+                                            color: white;">
+    <!-- div to cover the left area -->
+    <div style="display:block; position:absolute; background-color:black;
+                top:0px; right:30px; width:500px; height:100px;"></div>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/time-small-height-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+input {
+  width: 200px;
+  height: 5px;
+  outline: 1px dotted black;
+  /* Disable baseline alignment, so that our y-position isn't influenced by the
+   * choice of font inside of input: */
+  vertical-align: top;
+}
+    </style>
+  </head>
+  <body>
+    <input>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/time-small-height.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+input {
+  width: 200px;
+  height: 5px;
+  outline: 1px dotted black;
+  color: white;
+  /* Disable baseline alignment, so that our y-position isn't influenced by the
+   * choice of font inside of input: */
+  vertical-align: top;
+}
+    </style>
+  </head>
+  <body>
+    <input type="time">
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/time-small-width-height-ref.html
@@ -0,0 +1,18 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+input {
+  width: 8px;
+  height: 8px;
+  outline: 1px dotted black;
+  /* Disable baseline alignment, so that our y-position isn't influenced by the
+   * choice of font inside of input: */
+  vertical-align: top;
+}
+    </style>
+  </head>
+  <body>
+    <input>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/time-small-width-height.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+input {
+  width: 8px;
+  height: 8px;
+  outline: 1px dotted black;
+  color: white;
+  /* Disable baseline alignment, so that our y-position isn't influenced by the
+   * choice of font inside of input: */
+  vertical-align: top;
+}
+    </style>
+  </head>
+  <body>
+    <input type="time">
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/time-small-width-ref.html
@@ -0,0 +1,19 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+input {
+  width: 10px;
+  height: 1.5em;
+  outline: 1px dotted black;
+  background: white;
+  /* Disable baseline alignment, so that our y-position isn't influenced by the
+   * choice of font inside of input: */
+  vertical-align: top;
+}
+    </style>
+  </head>
+  <body>
+    <input>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/forms/input/datetime/time-small-width.html
@@ -0,0 +1,20 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <style>
+input {
+  width: 10px;
+  height: 1.5em;
+  outline: 1px dotted black;
+  color: white;
+  background: white;
+  /* Disable baseline alignment, so that our y-position isn't influenced by the
+   * choice of font inside of input: */
+  vertical-align: top;
+}
+    </style>
+  </head>
+  <body>
+    <input type="time">
+  </body>
+</html>
--- a/layout/style/res/forms.css
+++ b/layout/style/res/forms.css
@@ -1182,14 +1182,19 @@ input[type=number]::-moz-number-spin-dow
   border-bottom-right-radius: 4px;
 }
 
 input[type="number"] > div > div > div:hover {
   /* give some indication of hover state for the up/down buttons */
   background-color: lightblue;
 }
 
+input[type="date"],
+input[type="time"] {
+  overflow: hidden !important;
+}
+
 :-moz-autofill, :-moz-autofill-preview {
   filter: grayscale(21%) brightness(88%) contrast(161%) invert(10%) sepia(40%) saturate(206%);
 }
 :-moz-autofill-preview {
   color: GrayText;
 }
--- a/mobile/android/locales/filter.py
+++ b/mobile/android/locales/filter.py
@@ -48,17 +48,17 @@ def test(mod, path, entity = None):
         "chrome/plugins.properties"):
       return "error"
     return "ignore"
 
   if mod not in ("mobile", "mobile/android"):
     # we only have exceptions for mobile*
     return "error"
   if mod == "mobile/android":
-    if not entity:
+    if entity is None:
       if (re.match(r"mobile-l10n.js", path) or
           re.match(r"defines.inc", path)):
         return "ignore"
     if path == "defines.inc":
       if entity == "MOZ_LANGPACK_CONTRIBUTORS":
         return "ignore"
     return "error"
 
--- a/mobile/android/locales/jar.mn
+++ b/mobile/android/locales/jar.mn
@@ -32,17 +32,17 @@
   locale/@AB_CD@/browser/aboutLogins.dtd          (%chrome/aboutLogins.dtd)
   locale/@AB_CD@/browser/aboutLogins.properties  (%chrome/aboutLogins.properties)
 #ifndef RELEASE_OR_BETA
   locale/@AB_CD@/browser/webcompatReporter.properties (%chrome/webcompatReporter.properties)
 #endif
 % resource search-plugins chrome://browser/locale/searchplugins/
 
 # overrides for toolkit l10n, also for en-US
-# keep this file list in sync with filter.py
+# keep this file list in sync with l10n.toml and filter.py
 relativesrcdir toolkit/locales:
   locale/@AB_CD@/browser/overrides/about.dtd                       (%chrome/global/about.dtd)
   locale/@AB_CD@/browser/overrides/aboutAbout.dtd                  (%chrome/global/aboutAbout.dtd)
   locale/@AB_CD@/browser/overrides/aboutReader.properties          (%chrome/global/aboutReader.properties)
   locale/@AB_CD@/browser/overrides/aboutRights.dtd                 (%chrome/global/aboutRights.dtd)
   locale/@AB_CD@/browser/overrides/charsetMenu.properties          (%chrome/global/charsetMenu.properties)
   locale/@AB_CD@/browser/overrides/commonDialogs.properties        (%chrome/global/commonDialogs.properties)
   locale/@AB_CD@/browser/overrides/intl.properties                 (%chrome/global/intl.properties)
new file mode 100644
--- /dev/null
+++ b/mobile/android/locales/l10n.toml
@@ -0,0 +1,246 @@
+# 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/.
+
+basepath = "../../.."
+
+locales = [
+    "an",
+    "ar",
+    "as",
+    "ast",
+    "az",
+    "be",
+    "bg",
+    "bn-BD",
+    "bn-IN",
+    "br",
+    "ca",
+    "cak",
+    "cs",
+    "cy",
+    "da",
+    "de",
+    "dsb",
+    "el",
+    "en-GB",
+    "en-ZA",
+    "eo",
+    "es-AR",
+    "es-CL",
+    "es-ES",
+    "es-MX",
+    "et",
+    "eu",
+    "fa",
+    "ff",
+    "fi",
+    "fr",
+    "fy-NL",
+    "ga-IE",
+    "gd",
+    "gl",
+    "gn",
+    "gu-IN",
+    "he",
+    "hi-IN",
+    "hr",
+    "hsb",
+    "hu",
+    "hy-AM",
+    "id",
+    "is",
+    "it",
+    "ja",
+    "ka",
+    "kab",
+    "kk",
+    "kn",
+    "ko",
+    "lo",
+    "lt",
+    "lv",
+    "mai",
+    "ml",
+    "mr",
+    "ms",
+    "my",
+    "nb-NO",
+    "ne-NP",
+    "nl",
+    "nn-NO",
+    "or",
+    "pa-IN",
+    "pl",
+    "pt-BR",
+    "pt-PT",
+    "rm",
+    "ro",
+    "ru",
+    "sk",
+    "sl",
+    "son",
+    "sq",
+    "sr",
+    "sv-SE",
+    "ta",
+    "te",
+    "th",
+    "tr",
+    "trs",
+    "uk",
+    "ur",
+    "uz",
+    "wo",
+    "xh",
+    "zam",
+    "zh-CN",
+    "zh-TW",
+    ]
+
+[build]
+exclude-multi-locale = [
+    "be",
+    "bn-BD",
+    "ne-NP",
+    "trs",
+    "wo",
+    "zam",
+]
+
+[env]
+    l = "{l10n_base}/{locale}/"
+
+
+[[paths]]
+    reference = "mobile/locales/en-US/**"
+    l10n = "{l}mobile/**"
+
+[[paths]]
+    reference = "mobile/android/locales/en-US/**"
+    l10n = "{l}mobile/android/**"
+
+[[paths]]
+    reference = "mobile/android/base/locales/en-US/**"
+    l10n = "{l}mobile/android/base/**"
+    test = [
+        "android-dtd",
+    ]
+
+# hand-picked paths from toolkit, keep in sync with jar.mn
+[[paths]]
+    reference = "dom/locales/en-US/chrome/global.dtd"
+    l10n = "{l}dom/chrome/global.dtd"
+
+[[paths]]
+    reference = "dom/locales/en-US/chrome/accessibility/AccessFu.properties"
+    l10n = "{l}dom/chrome/accessibility/AccessFu.properties"
+
+[[paths]]
+    reference = "dom/locales/en-US/chrome/dom/dom.properties"
+    l10n = "{l}dom/chrome/dom/dom.properties"
+
+[[paths]]
+    reference = "dom/locales/en-US/chrome/plugins.properties"
+    l10n = "{l}dom/chrome/plugins.properties"
+
+[[paths]]
+    reference = "toolkit/locales/en-US/chrome/global/about.dtd"
+    l10n = "{l}toolkit/chrome/global/about.dtd"
+
+[[paths]]
+    reference = "toolkit/locales/en-US/chrome/global/aboutAbout.dtd"
+    l10n = "{l}toolkit/chrome/global/aboutAbout.dtd"
+
+[[paths]]
+    reference = "toolkit/locales/en-US/chrome/global/aboutReader.properties"
+    l10n = "{l}toolkit/chrome/global/aboutReader.properties"
+
+[[paths]]
+    reference = "toolkit/locales/en-US/chrome/global/aboutRights.dtd"
+    l10n = "{l}toolkit/chrome/global/aboutRights.dtd"
+
+[[paths]]
+    reference = "toolkit/locales/en-US/chrome/global/charsetMenu.properties"
+    l10n = "{l}toolkit/chrome/global/charsetMenu.properties"
+
+[[paths]]
+    reference = "toolkit/locales/en-US/chrome/global/commonDialogs.properties"
+    l10n = "{l}toolkit/chrome/global/commonDialogs.properties"
+
+[[paths]]
+    reference = "toolkit/locales/en-US/chrome/global/intl.properties"
+    l10n = "{l}toolkit/chrome/global/intl.properties"
+
+[[paths]]
+    reference = "toolkit/locales/en-US/chrome/global/intl.css"
+    l10n = "{l}toolkit/chrome/global/intl.css"
+
+[[paths]]
+    reference = "toolkit/locales/en-US/chrome/passwordmgr/passwordmgr.properties"
+    l10n = "{l}toolkit/chrome/passwordmgr/passwordmgr.properties"
+
+[[paths]]
+    reference = "toolkit/locales/en-US/chrome/search/search.properties"
+    l10n = "{l}toolkit/chrome/search/search.properties"
+
+[[paths]]
+    reference = "toolkit/locales/en-US/chrome/pluginproblem/pluginproblem.dtd"
+    l10n = "{l}toolkit/chrome/pluginproblem/pluginproblem.dtd"
+
+[[paths]]
+    reference = "toolkit/locales/en-US/chrome/global/aboutSupport.dtd"
+    l10n = "{l}toolkit/chrome/global/aboutSupport.dtd"
+
+[[paths]]
+    reference = "toolkit/locales/en-US/chrome/global/aboutSupport.properties"
+    l10n = "{l}toolkit/chrome/global/aboutSupport.properties"
+
+[[paths]]
+    reference = "toolkit/locales/en-US/crashreporter/crashes.dtd"
+    l10n = "{l}toolkit/crashreporter/crashes.dtd"
+
+[[paths]]
+    reference = "toolkit/locales/en-US/crashreporter/crashes.properties"
+    l10n = "{l}toolkit/crashreporter/crashes.properties"
+
+[[paths]]
+    reference = "toolkit/locales/en-US/chrome/global/mozilla.dtd"
+    l10n = "{l}toolkit/chrome/global/mozilla.dtd"
+
+[[paths]]
+    reference = "toolkit/locales/en-US/chrome/global/aboutTelemetry.dtd"
+    l10n = "{l}toolkit/chrome/global/aboutTelemetry.dtd"
+
+[[paths]]
+    reference = "toolkit/locales/en-US/chrome/global/aboutTelemetry.properties"
+    l10n = "{l}toolkit/chrome/global/aboutTelemetry.properties"
+
+[[paths]]
+    reference = "toolkit/locales/en-US/chrome/global/aboutWebrtc.properties"
+    l10n = "{l}toolkit/chrome/global/aboutWebrtc.properties"
+
+
+[[filters]]
+    path = [
+        "{l}mobile/android/mobile-l10n.js",
+        "{l}mobile/android/defines.inc",
+    ]
+    action = "ignore"
+
+[[filters]]
+    path = "{l}mobile/android/defines.inc"
+    key = "MOZ_LANGPACK_CONTRIBUTORS"
+    action = "ignore"
+
+[[filters]]
+    path = "{l}mobile/chrome/region.properties"
+    key = [
+        "re:^browser\\.search\\.order\\.[1-9]$",
+        "re:^browser\\.search\\.[a-zA-Z]+\\.US",
+        "re:^browser\\.contentHandlers\\.types\\.[0-5]\\..*$",
+        "re:^gecko\\.handlerService\\.schemes\\..+$",
+        "re:^gecko\\.handlerService\\.defaultHandlersVersion$",
+        "re:^browser\\.suggestedsites\\..+$",
+    ]
+    action = "ignore"
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountGetStartedActivityWeb.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/activities/FxAccountGetStartedActivityWeb.java
@@ -1,11 +1,11 @@
 /* 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/. */
 
 package org.mozilla.gecko.fxa.activities;
 
 public class FxAccountGetStartedActivityWeb extends FxAccountWebFlowActivity {
   public FxAccountGetStartedActivityWeb() {
-    super(CANNOT_RESUME_WHEN_ACCOUNTS_EXIST, "signup");
+    super(CANNOT_RESUME_WHEN_ACCOUNTS_EXIST, "signin");
   }
 }
--- a/moz.build
+++ b/moz.build
@@ -35,16 +35,20 @@ with Files('*moz*'):
     BUG_COMPONENT = ('Core', 'Build Config')
 
 with Files('GNUmakefile'):
     BUG_COMPONENT = ('Core', 'Build Config')
 
 with Files('*gradle*'):
     BUG_COMPONENT = ('Firefox for Android', 'Build Config & IDE Support')
 
+with Files('**/l10n.toml'):
+    BUG_COMPONENT = ('Core', 'Localization')
+    FINAL = True
+
 with Files('README.txt'):
     BUG_COMPONENT = ('Core', 'General')
 
 with Files('**/Makefile.in'):
     BUG_COMPONENT = ('Core', 'Build Config')
     FINAL = True
 
 FILES_PER_UNIFIED_FILE = 1
@@ -64,16 +68,17 @@ DIRS += [
     'testing/mozbase',
     'third_party/python',
 ]
 
 if not CONFIG['JS_STANDALONE']:
     # These python manifests are included here so they get picked up without an objdir
     PYTHON_UNITTEST_MANIFESTS += [
         'testing/marionette/harness/marionette_harness/tests/harness_unit/python.ini',
+        'testing/mochitest/tests/python/python.ini',
     ]
 
     CONFIGURE_SUBST_FILES += [
         'tools/update-packaging/Makefile',
     ]
     CONFIGURE_DEFINE_FILES += [
         'mozilla-config.h',
     ]
--- a/python/mach_commands.py
+++ b/python/mach_commands.py
@@ -1,29 +1,30 @@
 # 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/.
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 import argparse
 import logging
-import mozpack.path as mozpath
 import os
+import tempfile
 
 from concurrent.futures import (
     ThreadPoolExecutor,
     as_completed,
     thread,
 )
 
 import mozinfo
 from manifestparser import TestManifest
 from manifestparser import filters as mpf
 
+import mozpack.path as mozpath
 from mozbuild.base import (
     MachCommandBase,
 )
 
 from mach.decorators import (
     CommandArgument,
     CommandProvider,
     Command,
@@ -64,23 +65,31 @@ class MachCommands(MachCommandBase):
     @CommandArgument('--subsuite',
         default=None,
         help=('Python subsuite to run. If not specified, all subsuites are run. '
              'Use the string `default` to only run tests without a subsuite.'))
     @CommandArgument('tests', nargs='*',
         metavar='TEST',
         help=('Tests to run. Each test can be a single file or a directory. '
               'Default test resolution relies on PYTHON_UNITTEST_MANIFESTS.'))
-    def python_test(self,
-                    tests=[],
-                    test_objects=None,
-                    subsuite=None,
-                    verbose=False,
-                    stop=False,
-                    jobs=1):
+    def python_test(self, *args, **kwargs):
+        try:
+            tempdir = os.environ[b'PYTHON_TEST_TMP'] = str(tempfile.mkdtemp(suffix='-python-test'))
+            return self.run_python_tests(*args, **kwargs)
+        finally:
+            import mozfile
+            mozfile.remove(tempdir)
+
+    def run_python_tests(self,
+                         tests=[],
+                         test_objects=None,
+                         subsuite=None,
+                         verbose=False,
+                         stop=False,
+                         jobs=1):
         self._activate_virtualenv()
 
         def find_tests_by_path():
             import glob
             files = []
             for t in tests:
                 if t.endswith('.py') and os.path.isfile(t):
                     files.append(t)
@@ -124,44 +133,60 @@ class MachCommands(MachCommandBase):
 
         filters = []
         if subsuite == 'default':
             filters.append(mpf.subsuite(None))
         elif subsuite:
             filters.append(mpf.subsuite(subsuite))
 
         tests = mp.active_tests(filters=filters, disabled=False, **mozinfo.info)
+        parallel = []
+        sequential = []
+        for test in tests:
+            if test.get('sequential'):
+                sequential.append(test)
+            else:
+                parallel.append(test)
 
         self.jobs = jobs
         self.terminate = False
         self.verbose = verbose
 
         return_code = 0
+
+        def on_test_finished(result):
+            output, ret, test_path = result
+
+            for line in output:
+                self.log(logging.INFO, 'python-test', {'line': line.rstrip()}, '{line}')
+
+            if ret and not return_code:
+                self.log(logging.ERROR, 'python-test', {'test_path': test_path, 'ret': ret},
+                         'Setting retcode to {ret} from {test_path}')
+            return return_code or ret
+
         with ThreadPoolExecutor(max_workers=self.jobs) as executor:
             futures = [executor.submit(self._run_python_test, test['path'])
-                       for test in tests]
+                       for test in parallel]
 
             try:
                 for future in as_completed(futures):
-                    output, ret, test_path = future.result()
-
-                    for line in output:
-                        self.log(logging.INFO, 'python-test', {'line': line.rstrip()}, '{line}')
-
-                    if ret and not return_code:
-                        self.log(logging.ERROR, 'python-test', {'test_path': test_path, 'ret': ret}, 'Setting retcode to {ret} from {test_path}')
-                    return_code = return_code or ret
+                    return_code = on_test_finished(future.result())
             except KeyboardInterrupt:
                 # Hack to force stop currently running threads.
                 # https://gist.github.com/clchiou/f2608cbe54403edb0b13
                 executor._threads.clear()
                 thread._threads_queues.clear()
                 raise
 
-        self.log(logging.INFO, 'python-test', {'return_code': return_code}, 'Return code from mach python-test: {return_code}')
+        for test in sequential:
+            return_code = on_test_finished(self._run_python_test(test['path']))
+
+        self.log(logging.INFO, 'python-test', {'return_code': return_code},
+                 'Return code from mach python-test: {return_code}')
         return return_code
 
     def _run_python_test(self, test_path):
         from mozprocess import ProcessHandler
 
         output = []
 
         def _log(line):
--- a/python/mozbuild/mozbuild/artifacts.py
+++ b/python/mozbuild/mozbuild/artifacts.py
@@ -341,17 +341,16 @@ class MacArtifactJob(ArtifactJob):
             bundle_dirs = glob.glob(mozpath.join(tempdir, '*.app'))
             if len(bundle_dirs) != 1:
                 raise ValueError('Expected one source bundle, found: {}'.format(bundle_dirs))
             [source] = bundle_dirs
 
             # These get copied into dist/bin without the path, so "root/a/b/c" -> "dist/bin/c".
             paths_no_keep_path = ('Contents/MacOS', [
                 'crashreporter.app/Contents/MacOS/crashreporter',
-                'crashreporter.app/Contents/MacOS/minidump-analyzer',
                 'firefox',
                 'firefox-bin',
                 'libfreebl3.dylib',
                 'liblgpllibs.dylib',
                 # 'liblogalloc.dylib',
                 'libmozglue.dylib',
                 'libnss3.dylib',
                 'libnssckbi.dylib',
@@ -365,46 +364,51 @@ class MacArtifactJob(ArtifactJob):
                 'pingsender',
                 'plugin-container.app/Contents/MacOS/plugin-container',
                 'updater.app/Contents/MacOS/org.mozilla.updater',
                 # 'xpcshell',
                 'XUL',
             ])
 
             # These get copied into dist/bin with the path, so "root/a/b/c" -> "dist/bin/a/b/c".
-            paths_keep_path = ('Contents/Resources', [
-                'browser/components/libbrowsercomps.dylib',
-                'dependentlibs.list',
-                # 'firefox',
-                'gmp-clearkey/0.1/libclearkey.dylib',
-                # 'gmp-fake/1.0/libfake.dylib',
-                # 'gmp-fakeopenh264/1.0/libfakeopenh264.dylib',
-                '**/interfaces.xpt',
-            ])
+            paths_keep_path = [
+                ('Contents/MacOS', [
+                    'crashreporter.app/Contents/MacOS/minidump-analyzer',
+                ]),
+                ('Contents/Resources', [
+                    'browser/components/libbrowsercomps.dylib',
+                    'dependentlibs.list',
+                    # 'firefox',
+                    'gmp-clearkey/0.1/libclearkey.dylib',
+                    # 'gmp-fake/1.0/libfake.dylib',
+                    # 'gmp-fakeopenh264/1.0/libfakeopenh264.dylib',
+                    '**/interfaces.xpt',
+                ]),
+            ]
 
             with JarWriter(file=processed_filename, optimize=False, compress_level=5) as writer:
                 root, paths = paths_no_keep_path
                 finder = UnpackFinder(mozpath.join(source, root))
                 for path in paths:
                     for p, f in finder.find(path):
                         self.log(logging.INFO, 'artifact',
                             {'path': p},
                             'Adding {path} to processed archive')
                         destpath = mozpath.join('bin', os.path.basename(p))
                         writer.add(destpath.encode('utf-8'), f, mode=f.mode)
 
-                root, paths = paths_keep_path
-                finder = UnpackFinder(mozpath.join(source, root))
-                for path in paths:
-                    for p, f in finder.find(path):
-                        self.log(logging.INFO, 'artifact',
-                            {'path': p},
-                            'Adding {path} to processed archive')
-                        destpath = mozpath.join('bin', p)
-                        writer.add(destpath.encode('utf-8'), f.open(), mode=f.mode)
+                for root, paths in paths_keep_path:
+                    finder = UnpackFinder(mozpath.join(source, root))
+                    for path in paths:
+                        for p, f in finder.find(path):
+                            self.log(logging.INFO, 'artifact',
+                                     {'path': p},
+                                     'Adding {path} to processed archive')
+                            destpath = mozpath.join('bin', p)
+                            writer.add(destpath.encode('utf-8'), f.open(), mode=f.mode)
 
         finally:
             os.chdir(oldcwd)
             try:
                 shutil.rmtree(tempdir)
             except (OSError, IOError):
                 self.log(logging.WARN, 'artifact',
                     {'tempdir': tempdir},
--- a/servo/components/style/properties/gecko.mako.rs
+++ b/servo/components/style/properties/gecko.mako.rs
@@ -675,16 +675,35 @@ impl Debug for ${style_struct.gecko_stru
 %else:
 impl Debug for ${style_struct.gecko_struct_name} {
     // FIXME(bholley): Generate this.
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { self.gecko.fmt(f) }
 }
 %endif
 </%def>
 
+<%def name="impl_simple_type_with_conversion(ident, gecko_ffi_name=None)">
+    <%
+    if gecko_ffi_name is None:
+        gecko_ffi_name = "m" + to_camel_case(ident)
+    %>
+
+    #[allow(non_snake_case)]
+    pub fn set_${ident}(&mut self, v: longhands::${ident}::computed_value::T) {
+        self.gecko.${gecko_ffi_name} = From::from(v)
+    }
+
+    <% impl_simple_copy(ident, gecko_ffi_name) %>
+
+    #[allow(non_snake_case)]
+    pub fn clone_${ident}(&self) -> longhands::${ident}::computed_value::T {
+        From::from(self.gecko.${gecko_ffi_name})
+    }
+</%def>
+
 <%def name="raw_impl_trait(style_struct, skip_longhands='', skip_additionals='')">
 <%
     longhands = [x for x in style_struct.longhands
                 if not (skip_longhands == "*" or x.name in skip_longhands.split())]
 
     #
     # Make a list of types we can't auto-generate.
     #
@@ -993,38 +1012,55 @@ fn static_assert() {
 
     pub fn copy_border_image_outset_from(&mut self, other: &Self) {
         % for side in SIDES:
             self.gecko.mBorderImageOutset.data_at_mut(${side.index})
                 .copy_from(&other.gecko.mBorderImageOutset.data_at(${side.index}));
         % endfor
     }
 
+    <%
+    border_image_repeat_keywords = ["Stretch", "Repeat", "Round", "Space"]
+    %>
+
     pub fn set_border_image_repeat(&mut self, v: longhands::border_image_repeat::computed_value::T) {
         use properties::longhands::border_image_repeat::computed_value::RepeatKeyword;
-        use gecko_bindings::structs::{NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH, NS_STYLE_BORDER_IMAGE_REPEAT_REPEAT};
-        use gecko_bindings::structs::{NS_STYLE_BORDER_IMAGE_REPEAT_ROUND, NS_STYLE_BORDER_IMAGE_REPEAT_SPACE};
+        use gecko_bindings::structs;
 
         % for i, side in enumerate(["H", "V"]):
             let k = match v.${i} {
-                RepeatKeyword::Stretch => NS_STYLE_BORDER_IMAGE_REPEAT_STRETCH,
-                RepeatKeyword::Repeat => NS_STYLE_BORDER_IMAGE_REPEAT_REPEAT,
-                RepeatKeyword::Round => NS_STYLE_BORDER_IMAGE_REPEAT_ROUND,
-                RepeatKeyword::Space => NS_STYLE_BORDER_IMAGE_REPEAT_SPACE,
+                % for keyword in border_image_repeat_keywords:
+                RepeatKeyword::${keyword} => structs::NS_STYLE_BORDER_IMAGE_REPEAT_${keyword.upper()},
+                % endfor
             };
 
             self.gecko.mBorderImageRepeat${side} = k as u8;
         % endfor
     }
 
     pub fn copy_border_image_repeat_from(&mut self, other: &Self) {
         self.gecko.mBorderImageRepeatH = other.gecko.mBorderImageRepeatH;
         self.gecko.mBorderImageRepeatV = other.gecko.mBorderImageRepeatV;
     }
 
+    pub fn clone_border_image_repeat(&self) -> longhands::border_image_repeat::computed_value::T {
+        use properties::longhands::border_image_repeat::computed_value::RepeatKeyword;
+        use gecko_bindings::structs;
+
+        % for side in ["H", "V"]:
+        let servo_${side.lower()} = match self.gecko.mBorderImageRepeat${side} as u32 {
+            % for keyword in border_image_repeat_keywords:
+            structs::NS_STYLE_BORDER_IMAGE_REPEAT_${keyword.upper()} => RepeatKeyword::${keyword},
+            % endfor
+            x => panic!("Found unexpected value in mBorderImageRepeat${side}: {:?}", x),
+        };
+        % endfor
+        longhands::border_image_repeat::computed_value::T(servo_h, servo_v)
+    }
+
     pub fn set_border_image_width(&mut self, v: longhands::border_image_width::computed_value::T) {
         use values::generics::border::BorderImageSideWidth;
 
         % for side in SIDES:
         match v.${side.index} {
             BorderImageSideWidth::Auto => {
                 self.gecko.mBorderImageWidth.data_at_mut(${side.index}).set_value(CoordDataValue::Auto)
             },
@@ -1129,63 +1165,21 @@ fn static_assert() {
             CoordDataValue::Auto => Either::Second(Auto),
             _ => {
                 debug_assert!(false);
                 Either::First(0)
             }
         }
     }
 
-    pub fn set_align_content(&mut self, v: longhands::align_content::computed_value::T) {
-        self.gecko.mAlignContent = v.bits()
-    }
-
-    ${impl_simple_copy('align_content', 'mAlignContent')}
-
-    pub fn set_justify_content(&mut self, v: longhands::justify_content::computed_value::T) {
-        self.gecko.mJustifyContent = v.bits()
-    }
-
-    ${impl_simple_copy('justify_content', 'mJustifyContent')}
-
-    pub fn set_align_self(&mut self, v: longhands::align_self::computed_value::T) {
-        self.gecko.mAlignSelf = v.0.bits()
-    }
-
-    ${impl_simple_copy('align_self', 'mAlignSelf')}
-
-    pub fn set_justify_self(&mut self, v: longhands::justify_self::computed_value::T) {
-        self.gecko.mJustifySelf = v.0.bits()
-    }
-
-    ${impl_simple_copy('justify_self', 'mJustifySelf')}
-
-    pub fn set_align_items(&mut self, v: longhands::align_items::computed_value::T) {
-        self.gecko.mAlignItems = v.0.bits()
-    }
-
-    ${impl_simple_copy('align_items', 'mAlignItems')}
-
-    pub fn clone_align_items(&self) -> longhands::align_items::computed_value::T {
-        use values::specified::align::{AlignFlags, AlignItems};
-        AlignItems(AlignFlags::from_bits(self.gecko.mAlignItems)
-                                        .expect("mAlignItems contains valid flags"))
-    }
-
-    pub fn set_justify_items(&mut self, v: longhands::justify_items::computed_value::T) {
-        self.gecko.mJustifyItems = v.0.bits()
-    }
-
-    ${impl_simple_copy('justify_items', 'mJustifyItems')}
-
-    pub fn clone_justify_items(&self) -> longhands::justify_items::computed_value::T {
-        use values::specified::align::{AlignFlags, JustifyItems};
-        JustifyItems(AlignFlags::from_bits(self.gecko.mJustifyItems)
-                                          .expect("mJustifyItems contains valid flags"))
-    }
+    % for kind in ["align", "justify"]:
+    ${impl_simple_type_with_conversion(kind + "_content")}
+    ${impl_simple_type_with_conversion(kind + "_self")}
+    ${impl_simple_type_with_conversion(kind + "_items")}
+    % endfor
 
     pub fn set_order(&mut self, v: longhands::order::computed_value::T) {
         self.gecko.mOrder = v;
     }
 
     pub fn clone_order(&self) -> longhands::order::computed_value::T {
         self.gecko.mOrder
     }
@@ -1367,37 +1361,17 @@ fn static_assert() {
     pub fn copy_grid_template_${kind}_from(&mut self, other: &Self) {
         unsafe {
             bindings::Gecko_CopyStyleGridTemplateValues(&mut ${self_grid},
                                                         &other.gecko.mGridTemplate${kind.title()});
         }
     }
     % endfor
 
-    pub fn set_grid_auto_flow(&mut self, v: longhands::grid_auto_flow::computed_value::T) {
-        use gecko_bindings::structs::NS_STYLE_GRID_AUTO_FLOW_ROW;
-        use gecko_bindings::structs::NS_STYLE_GRID_AUTO_FLOW_COLUMN;
-        use gecko_bindings::structs::NS_STYLE_GRID_AUTO_FLOW_DENSE;
-        use properties::longhands::grid_auto_flow::computed_value::AutoFlow::{Row, Column};
-
-        self.gecko.mGridAutoFlow = 0;
-
-        let value = match v.autoflow {
-            Row => NS_STYLE_GRID_AUTO_FLOW_ROW,
-            Column => NS_STYLE_GRID_AUTO_FLOW_COLUMN,
-        };
-
-        self.gecko.mGridAutoFlow |= value as u8;
-
-        if v.dense {
-            self.gecko.mGridAutoFlow |= NS_STYLE_GRID_AUTO_FLOW_DENSE as u8;
-        }
-    }
-
-    ${impl_simple_copy('grid_auto_flow', 'mGridAutoFlow')}
+    ${impl_simple_type_with_conversion("grid_auto_flow")}
 
     pub fn set_grid_template_areas(&mut self, v: longhands::grid_template_areas::computed_value::T) {
         use gecko_bindings::bindings::Gecko_NewGridTemplateAreasValue;
         use gecko_bindings::sugar::refptr::UniqueRefPtr;
 
         let v = match v {
             Either::First(areas) => areas,
             Either::Second(_) => {
@@ -1809,31 +1783,17 @@ fn static_assert() {
 
     pub fn clone_font_weight(&self) -> longhands::font_weight::computed_value::T {
         debug_assert!(self.gecko.mFont.weight >= 100);
         debug_assert!(self.gecko.mFont.weight <= 900);
         debug_assert!(self.gecko.mFont.weight % 10 == 0);
         unsafe { transmute(self.gecko.mFont.weight) }
     }
 
-    pub fn set_font_synthesis(&mut self, v: longhands::font_synthesis::computed_value::T) {
-        use gecko_bindings::structs::{NS_FONT_SYNTHESIS_WEIGHT, NS_FONT_SYNTHESIS_STYLE};
-
-        self.gecko.mFont.synthesis = 0;
-        if v.weight {
-            self.gecko.mFont.synthesis |= NS_FONT_SYNTHESIS_WEIGHT as u8;
-        }
-        if v.style {
-            self.gecko.mFont.synthesis |= NS_FONT_SYNTHESIS_STYLE as u8;
-        }
-    }
-
-    pub fn copy_font_synthesis_from(&mut self, other: &Self) {
-        self.gecko.mFont.synthesis = other.gecko.mFont.synthesis;
-    }
+    ${impl_simple_type_with_conversion("font_synthesis", "mFont.synthesis")}
 
     pub fn set_font_size_adjust(&mut self, v: longhands::font_size_adjust::computed_value::T) {
         use properties::longhands::font_size_adjust::computed_value::T;
         match v {
             T::None => self.gecko.mFont.sizeAdjust = -1.0 as f32,
             T::Number(n) => self.gecko.mFont.sizeAdjust = n,
         }
     }
@@ -1858,49 +1818,32 @@ fn static_assert() {
 
     #[allow(non_snake_case)]
     pub fn copy__x_lang_from(&mut self, other: &Self) {
         unsafe {
             Gecko_nsStyleFont_CopyLangFrom(&mut self.gecko, &other.gecko);
         }
     }
 
-    pub fn set_font_language_override(&mut self, v: longhands::font_language_override::computed_value::T) {
-        self.gecko.mFont.languageOverride = v.0;
-    }
-    ${impl_simple_copy('font_language_override', 'mFont.languageOverride')}
+    <% impl_simple_type_with_conversion("font_language_override", "mFont.languageOverride") %>
 
     pub fn set_font_variant_alternates(&mut self, v: longhands::font_variant_alternates::computed_value::T) {
         self.gecko.mFont.variantAlternates = v.to_gecko_keyword()
     }
 
     #[allow(non_snake_case)]
     pub fn copy_font_variant_alternates_from(&mut self, other: &Self) {
         self.gecko.mFont.variantAlternates = other.gecko.mFont.variantAlternates;
         // FIXME: Copy alternateValues as well.
         // self.gecko.mFont.alternateValues = other.gecko.mFont.alternateValues;
     }
 
-    pub fn set_font_variant_ligatures(&mut self, v: longhands::font_variant_ligatures::computed_value::T) {
-        self.gecko.mFont.variantLigatures = v.to_gecko_keyword()
-    }
-
-    ${impl_simple_copy('font_variant_ligatures', 'mFont.variantLigatures')}
-
-    pub fn set_font_variant_east_asian(&mut self, v: longhands::font_variant_east_asian::computed_value::T) {
-        self.gecko.mFont.variantEastAsian = v.to_gecko_keyword()
-    }
-
-    ${impl_simple_copy('font_variant_east_asian', 'mFont.variantEastAsian')}
-
-    pub fn set_font_variant_numeric(&mut self, v: longhands::font_variant_numeric::computed_value::T) {
-        self.gecko.mFont.variantNumeric = v.to_gecko_keyword()
-    }
-
-    ${impl_simple_copy('font_variant_numeric', 'mFont.variantNumeric')}
+    ${impl_simple_type_with_conversion("font_variant_ligatures", "mFont.variantLigatures")}
+    ${impl_simple_type_with_conversion("font_variant_east_asian", "mFont.variantEastAsian")}
+    ${impl_simple_type_with_conversion("font_variant_numeric", "mFont.variantNumeric")}
 
     #[allow(non_snake_case)]
     pub fn set__moz_min_font_size_ratio(&mut self, v: longhands::_moz_min_font_size_ratio::computed_value::T) {
         let percentage = if v.0 > 255. {
             255.
         } else if v.0 < 0. {
             0.
         } else {
@@ -2649,17 +2592,17 @@ fn static_assert() {
     }
     ${impl_animation_count('iteration_count', 'IterationCount')}
     ${impl_copy_animation_value('iteration_count', 'IterationCount')}
 
     ${impl_animation_timing_function()}
 
     <% scroll_snap_type_keyword = Keyword("scroll-snap-type", "none mandatory proximity") %>
 
-    ${impl_keyword('scroll_snap_type_y', 'mScrollSnapTypeY', scroll_snap_type_keyword, need_clone=False)}
+    ${impl_keyword('scroll_snap_type_y', 'mScrollSnapTypeY', scroll_snap_type_keyword, need_clone=True)}
 
     pub fn set_perspective_origin(&mut self, v: longhands::perspective_origin::computed_value::T) {
         self.gecko.mPerspectiveOrigin[0].set(v.horizontal);
         self.gecko.mPerspectiveOrigin[1].set(v.vertical);
     }
 
     pub fn copy_perspective_origin_from(&mut self, other: &Self) {
         self.gecko.mPerspectiveOrigin[0].copy_from(&other.gecko.mPerspectiveOrigin[0]);
@@ -2850,21 +2793,17 @@ fn static_assert() {
             servo_flags.insert(contain::PAINT);
         }
 
         return servo_flags;
     }
 
     ${impl_simple_copy("contain", "mContain")}
 
-    pub fn set_touch_action(&mut self, v: longhands::touch_action::computed_value::T) {
-        self.gecko.mTouchAction = v.bits();
-    }
-
-    ${impl_simple_copy("touch_action", "mTouchAction")}
+    ${impl_simple_type_with_conversion("touch_action")}
 </%self:impl_trait>
 
 <%def name="simple_image_array_property(name, shorthand, field_name)">
     <%
         image_layers_field = "mImage" if shorthand == "background" else "mMask"
         copy_simple_image_array_property(name, shorthand, image_layers_field, field_name)
     %>
 
@@ -3632,17 +3571,17 @@ fn static_assert() {
             vertical: Au(self.gecko.mBorderSpacingRow)
         }
     }
 </%self:impl_trait>
 
 
 <%self:impl_trait style_struct_name="InheritedText"
                   skip_longhands="text-align text-emphasis-style text-shadow line-height letter-spacing word-spacing
-                                  -webkit-text-stroke-width text-emphasis-position -moz-tab-size -moz-text-size-adjust">
+                                  -webkit-text-stroke-width text-emphasis-position -moz-tab-size">
 
     <% text_align_keyword = Keyword("text-align",
                                     "start end left right center justify -moz-center -moz-left -moz-right char",
                                     gecko_strip_moz_prefix=False) %>
     ${impl_keyword('text_align', 'mTextAlign', text_align_keyword, need_clone=False)}
     ${impl_keyword_clone('text_align', 'mTextAlign', text_align_keyword)}
 
     pub fn set_text_shadow<I>(&mut self, v: I)
@@ -3739,35 +3678,17 @@ fn static_assert() {
     fn clear_text_emphasis_style_if_string(&mut self) {
         use nsstring::nsString;
         if self.gecko.mTextEmphasisStyle == structs::NS_STYLE_TEXT_EMPHASIS_STYLE_STRING as u8 {
             self.gecko.mTextEmphasisStyleString.assign(&nsString::new());
             self.gecko.mTextEmphasisStyle = structs::NS_STYLE_TEXT_EMPHASIS_STYLE_NONE as u8;
         }
     }
 
-    pub fn set_text_emphasis_position(&mut self, v: longhands::text_emphasis_position::computed_value::T) {
-        use properties::longhands::text_emphasis_position::*;
-
-        let mut result = match v.0 {
-            HorizontalWritingModeValue::Over => structs::NS_STYLE_TEXT_EMPHASIS_POSITION_OVER as u8,
-            HorizontalWritingModeValue::Under => structs::NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER as u8,
-        };
-        match v.1 {
-            VerticalWritingModeValue::Right => {
-                result |= structs::NS_STYLE_TEXT_EMPHASIS_POSITION_RIGHT as u8;
-            }
-            VerticalWritingModeValue::Left => {
-                result |= structs::NS_STYLE_TEXT_EMPHASIS_POSITION_LEFT as u8;
-            }
-        }
-        self.gecko.mTextEmphasisPosition = result;
-    }
-
-    <%call expr="impl_simple_copy('text_emphasis_position', 'mTextEmphasisPosition')"></%call>
+    ${impl_simple_type_with_conversion("text_emphasis_position")}
 
     pub fn set_text_emphasis_style(&mut self, v: longhands::text_emphasis_style::computed_value::T) {
         use properties::longhands::text_emphasis_style::computed_value::T;
         use properties::longhands::text_emphasis_style::ShapeKeyword;
 
         self.clear_text_emphasis_style_if_string();
         let (te, s) = match v {
             T::None => (structs::NS_STYLE_TEXT_EMPHASIS_STYLE_NONE, ""),
@@ -3799,17 +3720,17 @@ fn static_assert() {
         self.clear_text_emphasis_style_if_string();
         if other.gecko.mTextEmphasisStyle == structs::NS_STYLE_TEXT_EMPHASIS_STYLE_STRING as u8 {
             self.gecko.mTextEmphasisStyleString
                       .assign(&*other.gecko.mTextEmphasisStyleString)
         }
         self.gecko.mTextEmphasisStyle = other.gecko.mTextEmphasisStyle;
     }
 
-    <%call expr="impl_app_units('_webkit_text_stroke_width', 'mWebkitTextStrokeWidth', need_clone=False)"></%call>
+    <%call expr="impl_app_units('_webkit_text_stroke_width', 'mWebkitTextStrokeWidth', need_clone=True)"></%call>
 
     #[allow(non_snake_case)]
     pub fn set__moz_tab_size(&mut self, v: longhands::_moz_tab_size::computed_value::T) {
         use values::Either;
 
         match v {
             Either::Second(number) => {
                 self.gecko.mTabSize.set_value(CoordDataValue::Factor(number));
@@ -3827,49 +3748,23 @@ fn static_assert() {
         match self.gecko.mTabSize.as_value() {
             CoordDataValue::Coord(coord) => Either::First(Au(coord)),
             CoordDataValue::Factor(number) => Either::Second(number),
             _ => unreachable!(),
         }
     }
 
     <%call expr="impl_coord_copy('_moz_tab_size', 'mTabSize')"></%call>
-
-    <% text_size_adjust_keyword = Keyword("text-size-adjust", "auto none") %>
-
-    ${impl_keyword('_moz_text_size_adjust', 'mTextSizeAdjust', text_size_adjust_keyword, need_clone=False)}
-
 </%self:impl_trait>
 
 <%self:impl_trait style_struct_name="Text"
                   skip_longhands="text-decoration-line text-overflow initial-letter"
                   skip_additionals="*">
 
-    pub fn set_text_decoration_line(&mut self, v: longhands::text_decoration_line::computed_value::T) {
-        let mut bits: u8 = 0;
-        if v.contains(longhands::text_decoration_line::UNDERLINE) {
-            bits |= structs::NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE as u8;
-        }
-        if v.contains(longhands::text_decoration_line::OVERLINE) {
-            bits |= structs::NS_STYLE_TEXT_DECORATION_LINE_OVERLINE as u8;
-        }
-        if v.contains(longhands::text_decoration_line::LINE_THROUGH) {
-            bits |= structs::NS_STYLE_TEXT_DECORATION_LINE_LINE_THROUGH as u8;
-        }
-        if v.contains(longhands::text_decoration_line::BLINK) {
-            bits |= structs::NS_STYLE_TEXT_DECORATION_LINE_BLINK as u8;
-        }
-        if v.contains(longhands::text_decoration_line::COLOR_OVERRIDE) {
-            bits |= structs::NS_STYLE_TEXT_DECORATION_LINE_OVERRIDE_ALL as u8;
-        }
-        self.gecko.mTextDecorationLine = bits;
-    }
-
-    ${impl_simple_copy('text_decoration_line', 'mTextDecorationLine')}
-
+    ${impl_simple_type_with_conversion("text_decoration_line")}
 
     fn clear_overflow_sides_if_string(&mut self) {
         use gecko_bindings::structs::nsStyleTextOverflowSide;
         use nsstring::nsString;
         fn clear_if_string(side: &mut nsStyleTextOverflowSide) {
             if side.mType == structs::NS_STYLE_TEXT_OVERFLOW_STRING as u8 {
                 side.mString.assign(&nsString::new());
                 side.mType = structs::NS_STYLE_TEXT_OVERFLOW_CLIP as u8;
@@ -3933,16 +3828,28 @@ fn static_assert() {
         }
     }
 
     pub fn copy_initial_letter_from(&mut self, other: &Self) {
         self.gecko.mInitialLetterSize = other.gecko.mInitialLetterSize;
         self.gecko.mInitialLetterSink = other.gecko.mInitialLetterSink;
     }
 
+    pub fn clone_initial_letter(&self) -> longhands::initial_letter::computed_value::T {
+        use values::generics::text::InitialLetter;
+
+        if self.gecko.mInitialLetterSize == 0. && self.gecko.mInitialLetterSink == 0 {
+            InitialLetter::Normal
+        } else if self.gecko.mInitialLetterSize.floor() as i32 == self.gecko.mInitialLetterSink {
+            InitialLetter::Specified(self.gecko.mInitialLetterSize, None)
+        } else {
+            InitialLetter::Specified(self.gecko.mInitialLetterSize, Some(self.gecko.mInitialLetterSink))
+        }
+    }
+
     #[inline]
     pub fn has_underline(&self) -> bool {
         (self.gecko.mTextDecorationLine & (structs::NS_STYLE_TEXT_DECORATION_LINE_UNDERLINE as u8)) != 0
     }
 
     #[inline]
     pub fn has_overline(&self) -> bool {
         (self.gecko.mTextDecorationLine & (structs::NS_STYLE_TEXT_DECORATION_LINE_OVERLINE as u8)) != 0
@@ -4486,32 +4393,32 @@ clip-path
             unsafe {
                 bindings::Gecko_CopyCounter${counter_property}sFrom(&mut self.gecko, &other.gecko)
             }
         }
     % endfor
 </%self:impl_trait>
 
 <%self:impl_trait style_struct_name="UI" skip_longhands="-moz-force-broken-image-icon">
-    #[allow(non_snake_case)]
-    pub fn set__moz_force_broken_image_icon(&mut self, v: longhands::_moz_force_broken_image_icon::computed_value::T) {
-        self.gecko.mForceBrokenImageIcon = v.0 as u8;
-    }
-
-    ${impl_simple_copy("_moz_force_broken_image_icon", "mForceBrokenImageIcon")}
+    ${impl_simple_type_with_conversion("_moz_force_broken_image_icon", "mForceBrokenImageIcon")}
 </%self:impl_trait>
 
 <%self:impl_trait style_struct_name="XUL"
                   skip_longhands="-moz-box-ordinal-group">
     #[allow(non_snake_case)]
     pub fn set__moz_box_ordinal_group(&mut self, v: i32) {
         self.gecko.mBoxOrdinal = v as u32;
     }
 
     ${impl_simple_copy("_moz_box_ordinal_group", "mBoxOrdinal")}
+
+    #[allow(non_snake_case)]
+    pub fn clone__moz_box_ordinal_group(&self) -> i32{
+        self.gecko.mBoxOrdinal as i32
+    }
 </%self:impl_trait>
 
 <%def name="define_ffi_struct_accessor(style_struct)">
 #[no_mangle]
 #[allow(non_snake_case, unused_variables)]
 pub unsafe extern "C" fn Servo_GetStyle${style_struct.gecko_name}(computed_values:
         ServoComputedValuesBorrowedOrNull) -> *const ${style_struct.gecko_ffi_name} {
     ComputedValues::arc_from_borrowed(&computed_values).unwrap().get_${style_struct.name_lower}().get_gecko()
--- a/servo/components/style/properties/longhand/border.mako.rs
+++ b/servo/components/style/properties/longhand/border.mako.rs
@@ -207,17 +207,17 @@
 ${helpers.predefined_type("border-image-outset", "LengthOrNumberRect",
     parse_method="parse_non_negative",
     initial_value="computed::LengthOrNumber::zero().into()",
     initial_specified_value="specified::LengthOrNumber::zero().into()",
     spec="https://drafts.csswg.org/css-backgrounds/#border-image-outset",
     animation_value_type="none",
     boxed=True)}
 
-<%helpers:longhand name="border-image-repeat" animation_value_type="none"
+<%helpers:longhand name="border-image-repeat" animation_value_type="discrete"
                    spec="https://drafts.csswg.org/css-backgrounds/#border-image-repeat">
     use std::fmt;
     use style_traits::ToCss;
 
     no_viewport_percentage!(SpecifiedValue);
 
     pub mod computed_value {
         pub use super::RepeatKeyword;
--- a/servo/components/style/properties/longhand/box.mako.rs
+++ b/servo/components/style/properties/longhand/box.mako.rs
@@ -1607,17 +1607,17 @@
 
 ${helpers.single_keyword("scroll-snap-type-x",
                          "none mandatory proximity",
                          products="gecko",
                          gecko_constant_prefix="NS_STYLE_SCROLL_SNAP_TYPE",
                          spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-type-x)",
                          animation_value_type="discrete")}
 
-<%helpers:longhand products="gecko" name="scroll-snap-type-y" animation_value_type="none"
+<%helpers:longhand products="gecko" name="scroll-snap-type-y" animation_value_type="discrete"
                    spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/scroll-snap-type-x)">
     pub use super::scroll_snap_type_x::SpecifiedValue;
     pub use super::scroll_snap_type_x::computed_value;
     pub use super::scroll_snap_type_x::get_initial_value;
     pub use super::scroll_snap_type_x::parse;
 </%helpers:longhand>
 
 // Compositing and Blending Level 1
@@ -1705,17 +1705,17 @@
                           animation_value_type="ComputedValue",
                           extra_prefixes="moz webkit",
                           boxed=True,
                           spec="https://drafts.csswg.org/css-transforms/#transform-origin-property")}
 
 // FIXME: `size` and `content` values are not implemented and `strict` is implemented
 // like `content`(layout style paint) in gecko. We should implement `size` and `content`,
 // also update the glue once they are implemented in gecko.
-<%helpers:longhand name="contain" animation_value_type="none" products="gecko" need_clone="True"
+<%helpers:longhand name="contain" animation_value_type="discrete" products="gecko" need_clone="True"
                    flags="FIXPOS_CB"
                    spec="https://drafts.csswg.org/css-contain/#contain-property">
     use std::fmt;
     use style_traits::ToCss;
     use values::computed::ComputedValueAsSpecified;
 
     impl ComputedValueAsSpecified for SpecifiedValue {}
     no_viewport_percentage!(SpecifiedValue);
@@ -1920,17 +1920,17 @@
 ${helpers.predefined_type("shape-outside", "basic_shape::FloatAreaShape",
                           "generics::basic_shape::ShapeSource::None",
                           products="gecko", boxed="True",
                           animation_value_type="none",
                           spec="https://drafts.csswg.org/css-shapes/#shape-outside-property")}
 
 <%helpers:longhand name="touch-action"
                    products="gecko"
-                   animation_value_type="none"
+                   animation_value_type="discrete"
                    disable_when_testing="True"
                    spec="https://compat.spec.whatwg.org/#touch-action">
     use gecko_bindings::structs;
     use std::fmt;
     use style_traits::ToCss;
     use values::computed::ComputedValueAsSpecified;
 
     impl ComputedValueAsSpecified for SpecifiedValue {}
@@ -1994,9 +1994,12 @@
                     Ok(TOUCH_ACTION_PAN_X | TOUCH_ACTION_PAN_Y)
                 } else {
                     Ok(TOUCH_ACTION_PAN_Y)
                 }
             },
             _ => Err(()),
         }
     }
+
+    #[cfg(feature = "gecko")]
+    impl_bitflags_conversions!(SpecifiedValue);
 </%helpers:longhand>
--- a/servo/components/style/properties/longhand/font.mako.rs
+++ b/servo/components/style/properties/longhand/font.mako.rs
@@ -11,16 +11,33 @@
 <%def name="nongecko_unreachable()">
     %if product == "gecko":
         ${caller.body()}
     %else:
         unreachable!()
     %endif
 </%def>
 
+#[cfg(feature = "gecko")]
+macro_rules! impl_gecko_keyword_from_trait {
+    ($name: ident, $utype: ty) => {
+        impl From<$utype> for $name {
+            fn from(bits: $utype) -> $name {
+                $name::from_gecko_keyword(bits)
+            }
+        }
+
+        impl From<$name> for $utype {
+            fn from(v: $name) -> $utype {
+                v.to_gecko_keyword()
+            }
+        }
+    };
+}
+
 // Define ToComputedValue, ToCss, and other boilerplate for a specified value
 // which is of the form `enum SpecifiedValue {Value(..), System(SystemFont)}`
 <%def name="simple_system_boilerplate(name)">
     impl ToCss for SpecifiedValue {
         fn to_css<W>(&self, dest: &mut W) -> fmt::Result where W: fmt::Write {
             match *self {
                 SpecifiedValue::Value(ref v) => v.to_css(dest),
                 SpecifiedValue::System(_) => Ok(())
@@ -1139,17 +1156,17 @@
         if input.try(|input| input.expect_ident_matching("none")).is_ok() {
             return Ok(SpecifiedValue::None);
         }
 
         Ok(SpecifiedValue::Number(try!(Number::parse_non_negative(context, input))))
     }
 </%helpers:longhand>
 
-<%helpers:longhand products="gecko" name="font-synthesis" animation_value_type="none"
+<%helpers:longhand products="gecko" name="font-synthesis" animation_value_type="discrete"
                    spec="https://drafts.csswg.org/css-fonts/#propdef-font-synthesis">
     use std::fmt;
     use style_traits::ToCss;
     use values::computed::ComputedValueAsSpecified;
 
     impl ComputedValueAsSpecified for SpecifiedValue {}
     no_viewport_percentage!(SpecifiedValue);
 
@@ -1199,16 +1216,44 @@
                 if input.try(|input| input.expect_ident_matching("weight")).is_ok() {
                     result.weight = true;
                 }
                 Ok(result)
             },
             _ => Err(())
         }
     }
+
+    #[cfg(feature = "gecko")]
+    impl From<u8> for SpecifiedValue {
+        fn from(bits: u8) -> SpecifiedValue {
+            use gecko_bindings::structs;
+
+            SpecifiedValue {
+                weight: bits & structs::NS_FONT_SYNTHESIS_WEIGHT as u8 != 0,
+                style: bits & structs::NS_FONT_SYNTHESIS_STYLE as u8 != 0
+            }
+        }
+    }
+
+    #[cfg(feature = "gecko")]
+    impl From<SpecifiedValue> for u8 {
+        fn from(v: SpecifiedValue) -> u8 {
+            use gecko_bindings::structs;
+
+            let mut bits: u8 = 0;
+            if v.weight {
+                bits |= structs::NS_FONT_SYNTHESIS_WEIGHT as u8;
+            }
+            if v.style {
+                bits |= structs::NS_FONT_SYNTHESIS_STYLE as u8;
+            }
+            bits
+        }
+    }
 </%helpers:longhand>
 
 ${helpers.single_keyword_system("font-stretch",
                                 "normal ultra-condensed extra-condensed condensed \
                                  semi-condensed semi-expanded expanded extra-expanded \
                                  ultra-expanded",
                                 gecko_ffi_name="mFont.stretch",
                                 gecko_constant_prefix="NS_FONT_STRETCH",
@@ -1357,17 +1402,17 @@ macro_rules! exclusive_value {
         if $value.intersects($set) {
             return Err(())
         } else {
             $ident
         }
     }
 }
 
-<%helpers:longhand name="font-variant-east-asian" products="gecko" animation_value_type="none"
+<%helpers:longhand name="font-variant-east-asian" products="gecko" animation_value_type="discrete"
                    spec="https://drafts.csswg.org/css-fonts/#propdef-font-variant-east-asian">
     use properties::longhands::system_font::SystemFont;
     use std::fmt;
     use style_traits::ToCss;
 
     no_viewport_percentage!(SpecifiedValue);
 
     bitflags! {
@@ -1495,19 +1540,22 @@ macro_rules! exclusive_value {
         }
 
         if !result.is_empty() {
             Ok(SpecifiedValue::Value(result))
         } else {
             Err(())
         }
     }
+
+    #[cfg(feature = "gecko")]
+    impl_gecko_keyword_from_trait!(VariantEastAsian, u16);
 </%helpers:longhand>
 
-<%helpers:longhand name="font-variant-ligatures" products="gecko" animation_value_type="none"
+<%helpers:longhand name="font-variant-ligatures" products="gecko" animation_value_type="discrete"
                    spec="https://drafts.csswg.org/css-fonts/#propdef-font-variant-ligatures">
     use properties::longhands::system_font::SystemFont;
     use std::fmt;
     use style_traits::ToCss;
 
     no_viewport_percentage!(SpecifiedValue);
 
     bitflags! {
@@ -1645,19 +1693,22 @@ macro_rules! exclusive_value {
         }
 
         if !result.is_empty() {
             Ok(SpecifiedValue::Value(result))
         } else {
             Err(())
         }
     }
+
+    #[cfg(feature = "gecko")]
+    impl_gecko_keyword_from_trait!(VariantLigatures, u16);
 </%helpers:longhand>
 
-<%helpers:longhand name="font-variant-numeric" products="gecko" animation_value_type="none"
+<%helpers:longhand name="font-variant-numeric" products="gecko" animation_value_type="discrete"
                    spec="https://drafts.csswg.org/css-fonts/#propdef-font-variant-numeric">
     use properties::longhands::system_font::SystemFont;
     use std::fmt;
     use style_traits::ToCss;
 
     no_viewport_percentage!(SpecifiedValue);
 
     bitflags! {
@@ -1788,16 +1839,19 @@ macro_rules! exclusive_value {
         }
 
         if !result.is_empty() {
             Ok(SpecifiedValue::Value(result))
         } else {
             Err(())
         }
     }
+
+    #[cfg(feature = "gecko")]
+    impl_gecko_keyword_from_trait!(VariantNumeric, u8);
 </%helpers:longhand>
 
 ${helpers.single_keyword_system("font-variant-position",
                                 "normal sub super",
                                 products="gecko",
                                 gecko_ffi_name="mFont.variantPosition",
                                 gecko_constant_prefix="NS_FONT_VARIANT_POSITION",
                                 spec="https://drafts.csswg.org/css-fonts/#propdef-font-variant-position",
@@ -1870,17 +1924,17 @@ https://drafts.csswg.org/css-fonts-4/#lo
     }
 
     /// normal | <feature-tag-value>#
     pub fn parse(context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
         computed_value::T::parse(context, input)
     }
 </%helpers:longhand>
 
-<%helpers:longhand name="font-language-override" products="gecko" animation_value_type="none"
+<%helpers:longhand name="font-language-override" products="gecko" animation_value_type="discrete"
                    extra_prefixes="moz" boxed="True"
                    spec="https://drafts.csswg.org/css-fonts-3/#propdef-font-language-override">
     use properties::longhands::system_font::SystemFont;
     use std::fmt;
     use style_traits::ToCss;
     use byteorder::{BigEndian, ByteOrder};
     no_viewport_percentage!(SpecifiedValue);
 
@@ -2004,16 +2058,30 @@ https://drafts.csswg.org/css-fonts-4/#lo
         if input.try(|input| input.expect_ident_matching("normal")).is_ok() {
             Ok(SpecifiedValue::Normal)
         } else {
             input.expect_string().map(|cow| {
                 SpecifiedValue::Override(cow.into_owned())
             })
         }
     }
+
+    #[cfg(feature = "gecko")]
+    impl From<u32> for computed_value::T {
+        fn from(bits: u32) -> computed_value::T {
+            computed_value::T(bits)
+        }
+    }
+
+    #[cfg(feature = "gecko")]
+    impl From<computed_value::T> for u32 {
+        fn from(v: computed_value::T) -> u32 {
+            v.0
+        }
+    }
 </%helpers:longhand>
 
 <%helpers:longhand name="-x-lang" products="gecko" animation_value_type="none" internal="True"
                    spec="Internal (not web-exposed)">
     use values::computed::ComputedValueAsSpecified;
     pub use self::computed_value::T as SpecifiedValue;
 
     impl ComputedValueAsSpecified for SpecifiedValue {}
--- a/servo/components/style/properties/longhand/inherited_text.mako.rs
+++ b/servo/components/style/properties/longhand/inherited_text.mako.rs
@@ -13,61 +13,64 @@
                           spec="https://drafts.csswg.org/css2/visudet.html#propdef-line-height")}
 
 // CSS Text Module Level 3
 
 // TODO(pcwalton): `full-width`
 ${helpers.single_keyword("text-transform",
                          "none capitalize uppercase lowercase",
                          extra_gecko_values="full-width",
-                         animation_value_type="none",
+                         animation_value_type="discrete",
                          spec="https://drafts.csswg.org/css-text/#propdef-text-transform")}
 
 ${helpers.single_keyword("hyphens", "manual none auto",
                          gecko_enum_prefix="StyleHyphens",
-                         products="gecko", animation_value_type="none", extra_prefixes="moz",
+                         gecko_inexhaustive=True,
+                         products="gecko", animation_value_type="discrete", extra_prefixes="moz",
                          spec="https://drafts.csswg.org/css-text/#propdef-hyphens")}
 
 // TODO: Support <percentage>
 ${helpers.single_keyword("-moz-text-size-adjust", "auto none",
                          gecko_constant_prefix="NS_STYLE_TEXT_SIZE_ADJUST",
-                         products="gecko", animation_value_type="none",
+                         gecko_ffi_name="mTextSizeAdjust",
+                         products="gecko", animation_value_type="discrete",
                          spec="https://drafts.csswg.org/css-size-adjust/#adjustment-control",
                          alias="-webkit-text-size-adjust")}
 
 ${helpers.predefined_type("text-indent",
                           "LengthOrPercentage",
                           "computed::LengthOrPercentage::Length(Au(0))",
                           animation_value_type="ComputedValue",
                           spec="https://drafts.csswg.org/css-text/#propdef-text-indent",
                           allow_quirks=True)}
 
 // Also known as "word-wrap" (which is more popular because of IE), but this is the preferred
 // name per CSS-TEXT 6.2.
 ${helpers.single_keyword("overflow-wrap",
                          "normal break-word",
                          gecko_constant_prefix="NS_STYLE_OVERFLOWWRAP",
-                         animation_value_type="none",
+                         animation_value_type="discrete",
                          spec="https://drafts.csswg.org/css-text/#propdef-overflow-wrap",
                          alias="word-wrap")}
 
 // TODO(pcwalton): Support `word-break: keep-all` once we have better CJK support.
 ${helpers.single_keyword("word-break",
                          "normal break-all keep-all",
                          gecko_constant_prefix="NS_STYLE_WORDBREAK",
-                         animation_value_type="none",
+                         animation_value_type="discrete",
                          spec="https://drafts.csswg.org/css-text/#propdef-word-break")}
 
 // TODO(pcwalton): Support `text-justify: distribute`.
 <%helpers:single_keyword_computed name="text-justify"
                                   values="auto none inter-word"
                                   extra_gecko_values="inter-character"
                                   extra_specified="${'distribute' if product == 'gecko' else ''}"
                                   gecko_enum_prefix="StyleTextJustify"
-                                  animation_value_type="none"
+                                  gecko_inexhaustive="True"
+                                  animation_value_type="discrete"
                                   spec="https://drafts.csswg.org/css-text/#propdef-text-justify">
     no_viewport_percentage!(SpecifiedValue);
 
     impl ToComputedValue for SpecifiedValue {
         type ComputedValue = computed_value::T;
 
         #[inline]
         fn to_computed_value(&self, _: &Context) -> computed_value::T {
@@ -96,21 +99,21 @@
         }
     }
 </%helpers:single_keyword_computed>
 
 ${helpers.single_keyword("text-align-last",
                          "auto start end left right center justify",
                          products="gecko",
                          gecko_constant_prefix="NS_STYLE_TEXT_ALIGN",
-                         animation_value_type="none",
+                         animation_value_type="discrete",
                          spec="https://drafts.csswg.org/css-text/#propdef-text-align-last")}
 
 // TODO make this a shorthand and implement text-align-last/text-align-all
-<%helpers:longhand name="text-align" animation_value_type="none" need_clone="True"
+<%helpers:longhand name="text-align" animation_value_type="discrete"
                    spec="https://drafts.csswg.org/css-text/#propdef-text-align">
     no_viewport_percentage!(SpecifiedValue);
     pub mod computed_value {
         use style_traits::ToCss;
         macro_rules! define_text_align {
             ( $( $name: ident ( $string: expr ) => $discriminant: expr, )+ ) => {
                 define_css_keyword_enum! { T:
                     $(
@@ -355,17 +358,18 @@
     }
 </%helpers:longhand>
 
 <%helpers:single_keyword_computed name="white-space"
                                   values="normal pre nowrap pre-wrap pre-line"
                                   extra_gecko_values="-moz-pre-space"
                                   gecko_enum_prefix="StyleWhiteSpace"
                                   needs_conversion="True"
-                                  animation_value_type="none"
+                                  gecko_inexhaustive="True"
+                                  animation_value_type="discrete"
                                   spec="https://drafts.csswg.org/css-text/#propdef-white-space">
     use values::computed::ComputedValueAsSpecified;
     impl ComputedValueAsSpecified for SpecifiedValue {}
     no_viewport_percentage!(SpecifiedValue);
 
     % if product != "gecko":
     impl SpecifiedValue {
         pub fn allow_wrap(&self) -> bool {
@@ -600,17 +604,17 @@
             (Some(fill), Err(_)) => KeywordValue::Fill(fill),
             (None, Ok(shape)) => KeywordValue::Shape(shape),
             _ => return Err(()),
         };
         Ok(SpecifiedValue::Keyword(keyword_value))
     }
 </%helpers:longhand>
 
-<%helpers:longhand name="text-emphasis-position" animation_value_type="none" products="gecko"
+<%helpers:longhand name="text-emphasis-position" animation_value_type="discrete" products="gecko"
                    spec="https://drafts.csswg.org/css-text-decor/#propdef-text-emphasis-position">
     use values::computed::ComputedValueAsSpecified;
     use style_traits::ToCss;
 
     define_css_keyword_enum!(HorizontalWritingModeValue:
                              "over" => Over,
                              "under" => Under);
     define_css_keyword_enum!(VerticalWritingModeValue:
@@ -658,16 +662,42 @@
                     HorizontalWritingModeValue::Over
                 } else {
                     debug_assert!(kw & structs::NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER != 0);
                     HorizontalWritingModeValue::Under
                 };
                 SpecifiedValue(horiz, vert)
             }
         }
+
+        impl From<u8> for SpecifiedValue {
+            fn from(bits: u8) -> SpecifiedValue {
+                SpecifiedValue::from_gecko_keyword(bits as u32)
+            }
+        }
+
+        impl From<SpecifiedValue> for u8 {
+            fn from(v: SpecifiedValue) -> u8 {
+                use gecko_bindings::structs;
+
+                let mut result = match v.0 {
+                    HorizontalWritingModeValue::Over => structs::NS_STYLE_TEXT_EMPHASIS_POSITION_OVER,
+                    HorizontalWritingModeValue::Under => structs::NS_STYLE_TEXT_EMPHASIS_POSITION_UNDER,
+                };
+                match v.1 {
+                    VerticalWritingModeValue::Right => {
+                        result |= structs::NS_STYLE_TEXT_EMPHASIS_POSITION_RIGHT;
+                    }
+                    VerticalWritingModeValue::Left => {
+                        result |= structs::NS_STYLE_TEXT_EMPHASIS_POSITION_LEFT;
+                    }
+                };
+                result as u8
+            }
+        }
     % endif
 </%helpers:longhand>
 
 ${helpers.predefined_type("text-emphasis-color", "Color",
                           "computed_value::T::currentcolor()",
                           initial_specified_value="specified::Color::currentcolor()",
                           products="gecko", animation_value_type="IntermediateColor",
                           need_clone=True, ignored_when_colors_disabled=True,
--- a/servo/components/style/properties/longhand/outline.mako.rs
+++ b/servo/components/style/properties/longhand/outline.mako.rs
@@ -11,17 +11,17 @@
 
 // TODO(pcwalton): `invert`
 ${helpers.predefined_type("outline-color", "Color", "computed_value::T::currentcolor()",
                           initial_specified_value="specified::Color::currentcolor()",
                           animation_value_type="IntermediateColor", need_clone=True,
                           ignored_when_colors_disabled=True,
                           spec="https://drafts.csswg.org/css-ui/#propdef-outline-color")}
 
-<%helpers:longhand name="outline-style" need_clone="True" animation_value_type="none"
+<%helpers:longhand name="outline-style" animation_value_type="discrete"
                    spec="https://drafts.csswg.org/css-ui/#propdef-outline-style">
     use values::specified::BorderStyle;
 
     pub type SpecifiedValue = Either<Auto, BorderStyle>;
 
     impl SpecifiedValue {
         #[inline]
         pub fn none_or_hidden(&self) -> bool {
--- a/servo/components/style/properties/longhand/position.mako.rs
+++ b/servo/components/style/properties/longhand/position.mako.rs
@@ -19,16 +19,34 @@
 // offset-* logical properties, map to "top" / "left" / "bottom" / "right"
 % for side in LOGICAL_SIDES:
     ${helpers.predefined_type("offset-%s" % side, "LengthOrPercentageOrAuto",
                               "computed::LengthOrPercentageOrAuto::Auto",
                               spec="https://drafts.csswg.org/css-logical-props/#propdef-offset-%s" % side,
                               animation_value_type="ComputedValue", logical=True)}
 % endfor
 
+#[cfg(feature = "gecko")]
+macro_rules! impl_align_conversions {
+    ($name: path) => {
+        impl From<u8> for $name {
+            fn from(bits: u8) -> $name {
+                $name(::values::specified::align::AlignFlags::from_bits(bits)
+                      .expect("bits contain valid flag"))
+            }
+        }
+
+        impl From<$name> for u8 {
+            fn from(v: $name) -> u8 {
+                v.0.bits()
+            }
+        }
+    };
+}
+
 ${helpers.predefined_type("z-index", "IntegerOrAuto",
                           "Either::Second(Auto)",
                           spec="https://www.w3.org/TR/CSS2/visuren.html#z-index",
                           flags="CREATES_STACKING_CONTEXT",
                           animation_value_type="ComputedValue")}
 
 
 // CSS Flexible Box Layout Module Level 1
@@ -43,58 +61,64 @@
                          spec="https://drafts.csswg.org/css-flexbox/#flex-wrap-property",
                          extra_prefixes="webkit", animation_value_type="discrete")}
 
 % if product == "servo":
     // FIXME: Update Servo to support the same Syntax as Gecko.
     ${helpers.single_keyword("justify-content", "flex-start stretch flex-end center space-between space-around",
                              extra_prefixes="webkit",
                              spec="https://drafts.csswg.org/css-align/#propdef-justify-content",
-                             animation_value_type="none")}
+                             animation_value_type="discrete")}
 % else:
     ${helpers.predefined_type(name="justify-content",
                               type="AlignJustifyContent",
                               initial_value="specified::AlignJustifyContent::normal()",
                               spec="https://drafts.csswg.org/css-align/#propdef-justify-content",
                               extra_prefixes="webkit",
-                              animation_value_type="none")}
+                              animation_value_type="discrete")}
 % endif
 
 % if product == "servo":
     // FIXME: Update Servo to support the same Syntax as Gecko.
     ${helpers.single_keyword("align-content", "stretch flex-start flex-end center space-between space-around",
                              extra_prefixes="webkit",
                              spec="https://drafts.csswg.org/css-align/#propdef-align-content",
-                             animation_value_type="none")}
+                             animation_value_type="discrete")}
 
     ${helpers.single_keyword("align-items",
                              "stretch flex-start flex-end center baseline",
                              extra_prefixes="webkit",
                              spec="https://drafts.csswg.org/css-flexbox/#align-items-property",
                              animation_value_type="discrete")}
 % else:
     ${helpers.predefined_type(name="align-content",
                               type="AlignJustifyContent",
                               initial_value="specified::AlignJustifyContent::normal()",
                               spec="https://drafts.csswg.org/css-align/#propdef-align-content",
                               extra_prefixes="webkit",
-                              animation_value_type="none")}
+                              animation_value_type="discrete")}
 
     ${helpers.predefined_type(name="align-items",
                               type="AlignItems",
                               initial_value="specified::AlignItems::normal()",
                               spec="https://drafts.csswg.org/css-align/#propdef-align-items",
                               extra_prefixes="webkit",
                               animation_value_type="discrete")}
 
+    #[cfg(feature = "gecko")]
+    impl_align_conversions!(::values::specified::align::AlignItems);
+
     ${helpers.predefined_type(name="justify-items",
                               type="JustifyItems",
                               initial_value="specified::JustifyItems::auto()",
                               spec="https://drafts.csswg.org/css-align/#propdef-justify-items",
-                              animation_value_type="none")}
+                              animation_value_type="discrete")}
+
+    #[cfg(feature = "gecko")]
+    impl_align_conversions!(::values::specified::align::JustifyItems);
 % endif
 
 // Flex item properties
 ${helpers.predefined_type("flex-grow", "Number",
                           "0.0", "parse_non_negative",
                           spec="https://drafts.csswg.org/css-flexbox/#flex-grow-property",
                           extra_prefixes="webkit",
                           animation_value_type="ComputedValue")}
@@ -104,33 +128,35 @@
                           spec="https://drafts.csswg.org/css-flexbox/#flex-shrink-property",
                           extra_prefixes="webkit",
                           animation_value_type="ComputedValue")}
 
 // https://drafts.csswg.org/css-align/#align-self-property
 % if product == "servo":
     // FIXME: Update Servo to support the same syntax as Gecko.
     ${helpers.single_keyword("align-self", "auto stretch flex-start flex-end center baseline",
-                             need_clone=True,
                              extra_prefixes="webkit",
                              spec="https://drafts.csswg.org/css-flexbox/#propdef-align-self",
-                             animation_value_type="none")}
+                             animation_value_type="discrete")}
 % else:
     ${helpers.predefined_type(name="align-self",
                               type="AlignJustifySelf",
                               initial_value="specified::AlignJustifySelf::auto()",
                               spec="https://drafts.csswg.org/css-align/#align-self-property",
                               extra_prefixes="webkit",
-                              animation_value_type="none")}
+                              animation_value_type="discrete")}
 
     ${helpers.predefined_type(name="justify-self",
                               type="AlignJustifySelf",
                               initial_value="specified::AlignJustifySelf::auto()",
                               spec="https://drafts.csswg.org/css-align/#justify-self-property",
-                              animation_value_type="none")}
+                              animation_value_type="discrete")}
+
+    #[cfg(feature = "gecko")]
+    impl_align_conversions!(::values::specified::align::AlignJustifySelf);
 % endif
 
 // https://drafts.csswg.org/css-flexbox/#propdef-order
 ${helpers.predefined_type("order", "Integer", "0",
                           extra_prefixes="webkit",
                           animation_value_type="ComputedValue",
                           spec="https://drafts.csswg.org/css-flexbox/#order-property")}
 
@@ -261,17 +287,17 @@
                               boxed=True,
                               animation_value_type="none")}
 
 % endfor
 
 <%helpers:longhand name="grid-auto-flow"
         spec="https://drafts.csswg.org/css-grid/#propdef-grid-auto-flow"
         products="gecko"
-        animation_value_type="none">
+        animation_value_type="discrete">
     use std::fmt;
     use style_traits::ToCss;
     use values::computed::ComputedValueAsSpecified;
 
     pub type SpecifiedValue = computed_value::T;
 
     pub mod computed_value {
         #[derive(PartialEq, Clone, Eq, Copy, Debug)]
@@ -341,16 +367,53 @@
             Ok(computed_value::T {
                 autoflow: value.unwrap_or(AutoFlow::Row),
                 dense: dense,
             })
         } else {
             Err(())
         }
     }
+
+    #[cfg(feature = "gecko")]
+    impl From<u8> for SpecifiedValue {
+        fn from(bits: u8) -> SpecifiedValue {
+            use gecko_bindings::structs;
+            use self::computed_value::AutoFlow;
+
+            SpecifiedValue {
+                autoflow:
+                    if bits & structs::NS_STYLE_GRID_AUTO_FLOW_ROW as u8 != 0 {
+                        AutoFlow::Row
+                    } else {
+                        AutoFlow::Column
+                    },
+                dense:
+                    bits & structs::NS_STYLE_GRID_AUTO_FLOW_DENSE as u8 != 0,
+            }
+        }
+    }
+
+    #[cfg(feature = "gecko")]
+    impl From<SpecifiedValue> for u8 {
+        fn from(v: SpecifiedValue) -> u8 {
+            use gecko_bindings::structs;
+            use self::computed_value::AutoFlow;
+
+            let mut result: u8 = match v.autoflow {
+                AutoFlow::Row => structs::NS_STYLE_GRID_AUTO_FLOW_ROW as u8,
+                AutoFlow::Column => structs::NS_STYLE_GRID_AUTO_FLOW_COLUMN as u8,
+            };
+
+            if v.dense {
+                result |= structs::NS_STYLE_GRID_AUTO_FLOW_DENSE as u8;
+            }
+            result
+        }
+    }
 </%helpers:longhand>
 
 <%helpers:longhand name="grid-template-areas"
         spec="https://drafts.csswg.org/css-grid/#propdef-grid-template-areas"
         products="gecko"
         animation_value_type="none"
         disable_when_testing="True"
         boxed="True">
--- a/servo/components/style/properties/longhand/text.mako.rs
+++ b/servo/components/style/properties/longhand/text.mako.rs
@@ -138,34 +138,33 @@
     }
 </%helpers:longhand>
 
 ${helpers.single_keyword("unicode-bidi",
                          "normal embed isolate bidi-override isolate-override plaintext",
                          animation_value_type="discrete",
                          spec="https://drafts.csswg.org/css-writing-modes/#propdef-unicode-bidi")}
 
-// FIXME: This prop should be animatable.
 <%helpers:longhand name="text-decoration-line"
                    custom_cascade="${product == 'servo'}"
-                   animation_value_type="none"
+                   animation_value_type="discrete"
                    spec="https://drafts.csswg.org/css-text-decor/#propdef-text-decoration-line">
     use std::fmt;
     use style_traits::ToCss;
     use values::computed::ComputedValueAsSpecified;
 
     impl ComputedValueAsSpecified for SpecifiedValue {}
     no_viewport_percentage!(SpecifiedValue);
 
     bitflags! {
         #[cfg_attr(feature = "servo", derive(HeapSizeOf))]
         pub flags SpecifiedValue: u8 {
             const NONE = 0,
-            const OVERLINE = 0x01,
-            const UNDERLINE = 0x02,
+            const UNDERLINE = 0x01,
+            const OVERLINE = 0x02,
             const LINE_THROUGH = 0x04,
             const BLINK = 0x08,
         % if product == "gecko":
             /// Only set by presentation attributes
             ///
             /// Setting this will mean that text-decorations use the color
             /// specified by `color` in quirks mode.
             ///
@@ -251,16 +250,19 @@
         fn cascade_property_custom(_declaration: &PropertyDeclaration,
                                    _inherited_style: &ComputedValues,
                                    context: &mut computed::Context,
                                    _cacheable: &mut bool,
                                    _error_reporter: &ParseErrorReporter) {
                 longhands::_servo_text_decorations_in_effect::derive_from_text_decoration(context);
         }
     % endif
+
+    #[cfg(feature = "gecko")]
+    impl_bitflags_conversions!(SpecifiedValue);
 </%helpers:longhand>
 
 ${helpers.single_keyword("text-decoration-style",
                          "solid double dotted dashed wavy -moz-none",
                          products="gecko",
                          animation_value_type="discrete",
                          spec="https://drafts.csswg.org/css-text-decor/#propdef-text-decoration-style")}
 
@@ -273,11 +275,11 @@
     ignored_when_colors_disabled=True,
     spec="https://drafts.csswg.org/css-text-decor/#propdef-text-decoration-color")}
 
 ${helpers.predefined_type(
     "initial-letter",
     "InitialLetter",
     "computed::InitialLetter::normal()",
     initial_specified_value="specified::InitialLetter::normal()",
-    animation_value_type="none",
+    animation_value_type="discrete",
     products="gecko",
     spec="https://drafts.csswg.org/css-inline/#sizing-drop-initials")}
--- a/servo/components/style/properties/longhand/ui.mako.rs
+++ b/servo/components/style/properties/longhand/ui.mako.rs
@@ -37,17 +37,17 @@
                          gecko_constant_prefix="NS_STYLE_WINDOW_SHADOW",
                          gecko_inexhaustive=True,
                          animation_value_type="discrete",
                          internal=True,
                          spec="None (Nonstandard internal property)")}
 
 <%helpers:longhand name="-moz-force-broken-image-icon"
                    products="gecko"
-                   animation_value_type="none"
+                   animation_value_type="discrete"
                    spec="None (Nonstandard Firefox-only property)">
     use std::fmt;
     use style_traits::ToCss;
     use values::computed::ComputedValueAsSpecified;
 
     no_viewport_percentage!(SpecifiedValue);
 
     pub mod computed_value {
@@ -78,9 +78,24 @@
 
     pub fn parse(_context: &ParserContext, input: &mut Parser) -> Result<SpecifiedValue, ()> {
         match try!(input.expect_integer()) {
             0 => Ok(computed_value::T(false)),
             1 => Ok(computed_value::T(true)),
             _ => Err(()),
         }
     }
+
+    impl From<u8> for SpecifiedValue {
+        fn from(bits: u8) -> SpecifiedValue {
+            SpecifiedValue(bits == 1)
+        }
+    }
+
+    impl From<SpecifiedValue> for u8 {
+        fn from(v: SpecifiedValue) -> u8 {
+            match v.0 {
+                true => 1u8,
+                false => 0u8,
+            }
+        }
+    }
 </%helpers:longhand>
--- a/servo/components/style/properties/longhand/xul.mako.rs
+++ b/servo/components/style/properties/longhand/xul.mako.rs
@@ -54,10 +54,10 @@
                          animation_value_type="discrete",
                          spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-stack-sizing)")}
 
 ${helpers.predefined_type("-moz-box-ordinal-group", "Integer", "0",
                           parse_method="parse_non_negative",
                           products="gecko",
                           alias="-webkit-box-ordinal-group",
                           gecko_ffi_name="mBoxOrdinal",
-                          animation_value_type="none",
+                          animation_value_type="discrete",
                           spec="Nonstandard (https://developer.mozilla.org/en-US/docs/Web/CSS/-moz-box-ordinal-group)")}
--- a/servo/components/style/properties/properties.mako.rs
+++ b/servo/components/style/properties/properties.mako.rs
@@ -48,16 +48,33 @@ use style_adjuster::StyleAdjuster;
 pub use self::declaration_block::*;
 
 #[cfg(feature = "gecko")]
 #[macro_export]
 macro_rules! property_name {
     ($s: tt) => { atom!($s) }
 }
 
+#[cfg(feature = "gecko")]
+macro_rules! impl_bitflags_conversions {
+    ($name: ident) => {
+        impl From<u8> for $name {
+            fn from(bits: u8) -> $name {
+                $name::from_bits(bits).expect("bits contain valid flag")
+            }
+        }
+
+        impl From<$name> for u8 {
+            fn from(v: $name) -> u8 {
+                v.bits()
+            }
+        }
+    };
+}
+
 <%!
     from data import Method, Keyword, to_rust_ident, to_camel_case, SYSTEM_FONT_LONGHANDS
     import os.path
 %>
 
 #[path="${repr(os.path.join(os.path.dirname(__file__), 'declaration_block.rs'))[1:-1]}"]
 pub mod declaration_block;
 
--- a/servo/components/style/values/specified/align.rs
+++ b/servo/components/style/values/specified/align.rs
@@ -132,20 +132,16 @@ impl AlignJustifyContent {
     /// Construct a value including a fallback alignment.
     ///
     /// https://drafts.csswg.org/css-align/#fallback-alignment
     #[inline]
     pub fn with_fallback(flags: AlignFlags, fallback: AlignFlags) -> Self {
         AlignJustifyContent(flags.bits() as u16 | ((fallback.bits() as u16) << ALIGN_ALL_SHIFT))
     }
 
-    /// The combined 16-bit flags, for copying into a Gecko style struct.
-    #[inline]
-    pub fn bits(self) -> u16 { self.0 }
-
     /// The primary alignment
     #[inline]
     pub fn primary(self) -> AlignFlags {
         AlignFlags::from_bits((self.0 & ALIGN_ALL_BITS) as u8)
             .expect("AlignJustifyContent must contain valid flags")
     }
 
     /// The fallback alignment
@@ -320,16 +316,30 @@ impl Parse for JustifyItems {
         // [ <overflow-position>? && <self-position> ]
         if let Ok(value) = parse_overflow_self_position(input) {
             return Ok(JustifyItems(value))
         }
         Err(())
     }
 }
 
+#[cfg(feature = "gecko")]
+impl From<u16> for AlignJustifyContent {
+    fn from(bits: u16) -> AlignJustifyContent {
+        AlignJustifyContent(bits)
+    }
+}
+
+#[cfg(feature = "gecko")]
+impl From<AlignJustifyContent> for u16 {
+    fn from(v: AlignJustifyContent) -> u16 {
+        v.0
+    }
+}
+
 // auto | normal | stretch | <baseline-position>
 fn parse_auto_normal_stretch_baseline(input: &mut Parser) -> Result<AlignFlags, ()> {
     if let Ok(baseline) = input.try(parse_baseline) {
         return Ok(baseline);
     }
 
     let ident = input.expect_ident()?;
     match_ignore_ascii_case! { &ident,
--- a/taskcluster/ci/source-test/python-tests.yml
+++ b/taskcluster/ci/source-test/python-tests.yml
@@ -45,16 +45,52 @@ marionette-harness:
     when:
         files-changed:
           - 'testing/marionette/harness/**'
           - 'testing/mozbase/mozlog/mozlog/**'
           - 'testing/mozbase/mozlog/setup.py'
           - 'testing/mozbase/packages.txt'
           - 'python/mach_commands.py'
 
+mochitest-harness:
+    description: testing/mochitest unittests
+    platform: linux64/opt
+    require-build: true
+    treeherder:
+        symbol: py(mch)
+        kind: test
+        tier: 2
+    worker-type:
+        by-platform:
+            linux64.*: aws-provisioner-v1/gecko-t-linux-xlarge
+    worker:
+        by-platform:
+            linux64.*:
+                docker-image: {in-tree: "desktop1604-test"}
+                max-run-time: 3600
+    run:
+        using: run-task
+        command: >
+            source /home/worker/scripts/xvfb.sh &&
+            start_xvfb '1600x1200x24' 0 &&
+            cd /home/worker/checkouts/gecko &&
+            ./mach python-test --subsuite mochitest
+    run-on-projects:
+        - integration
+        - release
+    when:
+        files-changed:
+            - 'config/mozunit.py'
+            - 'python/mach_commands.py'
+            - 'testing/mochitest/**'
+            - 'testing/mozharness/mozharness/base/log.py'
+            - 'testing/mozharness/mozharness/mozilla/structuredlog.py'
+            - 'testing/mozharness/mozharness/mozilla/testing/errors.py'
+            - 'testing/profiles/prefs_general.js'
+
 mozbase:
     description: testing/mozbase unit tests
     platform:
         - linux64/opt
     treeherder:
         symbol: py(mb)
         kind: test
         tier: 2
--- a/taskcluster/taskgraph/transforms/job/common.py
+++ b/taskcluster/taskgraph/transforms/job/common.py
@@ -4,20 +4,16 @@
 """
 Common support for various job types.  These functions are all named after the
 worker implementation they operate on, and take the same three parameters, for
 consistency.
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
-from taskgraph.util.attributes import keymatch
-
-
-ARTIFACT_URL = 'https://queue.taskcluster.net/v1/task/{}/artifacts/{}'
 SECRET_SCOPE = 'secrets:get:project/releng/gecko/{}/level-{}/{}'
 
 
 def docker_worker_add_workspace_cache(config, job, taskdesc):
     """Add the workspace cache based on the build platform/type and level,
     except on try where workspace caches are not used."""
     if config.params['project'] == 'try':
         return
@@ -56,40 +52,16 @@ def docker_worker_add_gecko_vcs_env_vars
     env.update({
         'GECKO_BASE_REPOSITORY': config.params['base_repository'],
         'GECKO_HEAD_REF': config.params['head_rev'],
         'GECKO_HEAD_REPOSITORY': config.params['head_repository'],
         'GECKO_HEAD_REV': config.params['head_rev'],
     })
 
 
-def add_build_dependency(config, job, taskdesc):
-    """Add build dependency to the task description and installer_url to env."""
-    key = job['platform']
-    build_labels = config.config.get('dependent-build-platforms', {})
-    matches = keymatch(build_labels, key)
-    if not matches:
-        raise Exception("No build platform found for '{}'. "
-                        "Define 'dependent-build-platforms' in the kind config.".format(key))
-
-    if len(matches) > 1:
-        raise Exception("More than one build platform found for '{}'.".format(key))
-
-    label = matches[0]['label']
-    target = matches[0]['target-name']
-    deps = taskdesc.setdefault('dependencies', {})
-    deps.update({'build': label})
-
-    build_artifact = 'public/build/{}'.format(target)
-    installer_url = ARTIFACT_URL.format('<build>', build_artifact)
-
-    env = taskdesc['worker'].setdefault('env', {})
-    env.update({'GECKO_INSTALLER_URL': {'task-reference': installer_url}})
-
-
 def support_vcs_checkout(config, job, taskdesc):
     """Update a job/task with parameters to enable a VCS checkout.
 
     The configuration is intended for tasks using "run-task" and its
     VCS checkout behavior.
     """
     level = config.params['level']
 
--- a/taskcluster/taskgraph/transforms/job/mach.py
+++ b/taskcluster/taskgraph/transforms/job/mach.py
@@ -15,22 +15,16 @@ from taskgraph.transforms.job.run_task i
 from taskgraph.util.schema import Schema
 from voluptuous import Required
 
 mach_schema = Schema({
     Required('using'): 'mach',
 
     # The mach command (omitting `./mach`) to run
     Required('mach'): basestring,
-
-    # Whether the job requires a build artifact or not. If True, the task
-    # will depend on a build task and run-task will download and set up the
-    # installer. Build labels are determined by the `dependent-build-platforms`
-    # config in kind.yml.
-    Required('requires-build', default=False): bool,
 })
 
 
 @run_job_using("docker-worker", "mach", schema=mach_schema)
 @run_job_using("native-engine", "mach", schema=mach_schema)
 def docker_worker_mach(config, job, taskdesc):
     run = job['run']
 
--- a/taskcluster/taskgraph/transforms/job/mozharness_test.py
+++ b/taskcluster/taskgraph/transforms/job/mozharness_test.py
@@ -434,38 +434,38 @@ def mozharness_test_buildbot_bridge(conf
 
     if mozharness.get('chunked', False):
         this_chunk = test.get('this-chunk')
         test_name = '{}-{}'.format(test_name, this_chunk)
 
     if test.get('suite', '') == 'talos':
         # on linux64-<variant>/<build>, we add the variant to the buildername
         m = re.match(r'\w+-([^/]+)/.*', test['test-platform'])
-        variant = ''
-        if m and m.group(1):
-            variant = m.group(1) + ' '
+        variant = m.group(1) if m and m.group(1) else ''
+
         # On beta and release, we run nightly builds on-push; the talos
         # builders need to run against non-nightly buildernames
-        if variant == 'nightly ':
+        if variant == 'nightly':
             variant = ''
+
         # this variant name has branch after the variant type in BBB bug 1338871
         if variant in ('stylo', 'stylo-sequential'):
-            buildername = '{} {}{} talos {}'.format(
-                BUILDER_NAME_PREFIX[platform],
-                variant,
-                branch,
-                test_name
-            )
+            name = '{prefix} {variant} {branch} talos {test_name}'
+        elif variant:
+            name = '{prefix} {branch} {variant} talos {test_name}'
         else:
-            buildername = '{} {} {}talos {}'.format(
-                BUILDER_NAME_PREFIX[platform],
-                branch,
-                variant,
-                test_name
-            )
+            name = '{prefix} {branch} talos {test_name}'
+
+        buildername = name.format(
+            prefix=BUILDER_NAME_PREFIX[platform],
+            variant=variant,
+            branch=branch,
+            test_name=test_name
+        )
+
         if buildername.startswith('Ubuntu'):
             buildername = buildername.replace('VM', 'HW')
     else:
         buildername = '{} {} {} test {}'.format(
             BUILDER_NAME_PREFIX[platform],
             branch,
             build_type,
             test_name
--- a/taskcluster/taskgraph/transforms/job/run_task.py
+++ b/taskcluster/taskgraph/transforms/job/run_task.py
@@ -4,53 +4,41 @@
 """
 Support for running jobs that are invoked via the `run-task` script.
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 from taskgraph.transforms.job import run_job_using
 from taskgraph.util.schema import Schema
-from taskgraph.transforms.job.common import (
-    add_build_dependency,
-    support_vcs_checkout,
-)
+from taskgraph.transforms.job.common import support_vcs_checkout
 from voluptuous import Required, Any
 
 run_task_schema = Schema({
     Required('using'): 'run-task',
 
     # if true, add a cache at ~worker/.cache, which is where things like pip
     # tend to hide their caches.  This cache is never added for level-1 jobs.
     Required('cache-dotcache', default=False): bool,
 
     # if true (the default), perform a checkout in /home/worker/checkouts/gecko
     Required('checkout', default=True): bool,
 
     # The command arguments to pass to the `run-task` script, after the
     # checkout arguments.  If a list, it will be passed directly; otherwise
     # it will be included in a single argument to `bash -cx`.
     Required('command'): Any([basestring], basestring),
-
-    # Whether the job requires a build artifact or not. If True, the task
-    # will depend on a build task and run-task will download and set up the
-    # installer. Build labels are determined by the `dependent-build-platforms`
-    # config in kind.yml.
-    Required('requires-build', default=False): bool,
 })
 
 
 def common_setup(config, job, taskdesc):
     run = job['run']
     if run['checkout']:
         support_vcs_checkout(config, job, taskdesc)
 
-    if run['requires-build']:
-        add_build_dependency(config, job, taskdesc)
-
 
 @run_job_using("docker-worker", "run-task", schema=run_task_schema)
 def docker_worker_run_task(config, job, taskdesc):
     run = job['run']
     worker = taskdesc['worker'] = job['worker']
     common_setup(config, job, taskdesc)
 
     if run.get('cache-dotcache') and int(config.params['level']) > 1:
--- a/taskcluster/taskgraph/transforms/source_test.py
+++ b/taskcluster/taskgraph/transforms/source_test.py
@@ -8,39 +8,48 @@ treeherder configuration and attributes 
 """
 
 from __future__ import absolute_import, print_function, unicode_literals
 
 import copy
 
 from taskgraph.transforms.base import TransformSequence
 from taskgraph.transforms.job import job_description_schema
+from taskgraph.util.attributes import keymatch
 from taskgraph.util.schema import (
     validate_schema,
     resolve_keyed_by,
 )
 from voluptuous import (
     Any,
     Extra,
     Required,
     Schema,
 )
 
+ARTIFACT_URL = 'https://queue.taskcluster.net/v1/task/{}/artifacts/{}'
+
 job_description_schema = {str(k): v for k, v in job_description_schema.schema.iteritems()}
 
 source_test_description_schema = Schema({
     # most fields are passed directly through as job fields, and are not
     # repeated here
     Extra: object,
 
     # The platform on which this task runs.  This will be used to set up attributes
     # (for try selection) and treeherder metadata (for display).  If given as a list,
     # the job will be "split" into multiple tasks, one with each platform.
     Required('platform'): Any(basestring, [basestring]),
 
+    # Whether the job requires a build artifact or not. If True, the task will
+    # depend on a build task and the installer url will be saved to the
+    # GECKO_INSTALLER_URL environment variable. Build labels are determined by the
+    # `dependent-build-platforms` config in kind.yml.
+    Required('require-build', default=False): bool,
+
     # These fields can be keyed by "platform", and are otherwise identical to
     # job descriptions.
     Required('worker-type'): Any(
         job_description_schema['worker-type'],
         {'by-platform': {basestring: job_description_schema['worker-type']}},
     ),
     Required('worker'): Any(
         job_description_schema['worker'],
@@ -78,16 +87,42 @@ def expand_platforms(config, jobs):
 
             if 'name' in pjob:
                 pjob['name'] = '{}-{}'.format(pjob['name'], platform)
             else:
                 pjob['label'] = '{}-{}'.format(pjob['label'], platform)
             yield pjob
 
 
+def add_build_dependency(config, job):
+    """
+    Add build dependency to the job and installer_url to env.
+    """
+    key = job['platform']
+    build_labels = config.config.get('dependent-build-platforms', {})
+    matches = keymatch(build_labels, key)
+    if not matches:
+        raise Exception("No build platform found for '{}'. "
+                        "Define 'dependent-build-platforms' in the kind config.".format(key))
+
+    if len(matches) > 1:
+        raise Exception("More than one build platform found for '{}'.".format(key))
+
+    label = matches[0]['label']
+    target = matches[0]['target-name']
+    deps = job.setdefault('dependencies', {})
+    deps.update({'build': label})
+
+    build_artifact = 'public/build/{}'.format(target)
+    installer_url = ARTIFACT_URL.format('<build>', build_artifact)
+
+    env = job['worker'].setdefault('env', {})
+    env.update({'GECKO_INSTALLER_URL': {'task-reference': installer_url}})
+
+
 @transforms.add
 def handle_platform(config, jobs):
     """
     Handle the 'platform' property, setting up treeherder context as well as
     try-related attributes.
     """
     fields = [
         'worker-type',
@@ -98,10 +133,13 @@ def handle_platform(config, jobs):
         platform = job['platform']
 
         for field in fields:
             resolve_keyed_by(job, field, item_name=job['name'])
 
         if 'treeherder' in job:
             job['treeherder']['platform'] = platform
 
+        if job.pop('require-build'):
+            add_build_dependency(config, job)
+
         del job['platform']
         yield job
--- a/testing/geckodriver/README.md
+++ b/testing/geckodriver/README.md
@@ -1,9 +1,9 @@
-# geckodriver [![Build Status](https://travis-ci.org/mozilla/geckodriver.svg?branch=master)](https://travis-ci.org/mozilla/geckodriver)
+# geckodriver [![Build Status](https://travis-ci.org/mozilla/geckodriver.svg?branch=release)](https://travis-ci.org/mozilla/geckodriver)
 
 Proxy for using W3C WebDriver-compatible clients
 to interact with Gecko-based browsers.
 
 This program provides the HTTP API described by
 the [WebDriver protocol](http://w3c.github.io/webdriver/webdriver-spec.html#protocol)
 to communicate with Gecko browsers, such as Firefox.
 It translates calls into
--- a/testing/marionette/harness/marionette_harness/marionette_test/testcases.py
+++ b/testing/marionette/harness/marionette_harness/marionette_test/testcases.py
@@ -264,33 +264,16 @@ class CommonTestCase(unittest.TestCase):
                     self.loglines = [['Error getting log: {}'.format(inst)]]
                 try:
                     self.marionette.delete_session()
                 except IOError:
                     # Gecko has crashed?
                     pass
         self.marionette = None
 
-    def setup_SpecialPowers_observer(self):
-        self.marionette.set_context("chrome")
-        self.marionette.execute_script("""
-let SECURITY_PREF = "security.turn_off_all_security_so_that_viruses_can_take_over_this_computer";
-Components.utils.import("resource://gre/modules/Preferences.jsm");
-Preferences.set(SECURITY_PREF, true);
-
-if (!testUtils.hasOwnProperty("specialPowersObserver")) {
-  let loader = Components.classes["@mozilla.org/moz/jssubscript-loader;1"]
-    .getService(Components.interfaces.mozIJSSubScriptLoader);
-  loader.loadSubScript("chrome://specialpowers/content/SpecialPowersObserver.jsm",
-    testUtils);
-  testUtils.specialPowersObserver = new testUtils.SpecialPowersObserver();
-  testUtils.specialPowersObserver.init();
-}
-""")
-
 
 class MarionetteTestCase(CommonTestCase):
 
     match_re = re.compile(r"test_(.*)\.py$")
 
     def __init__(self, marionette_weakref, fixtures, methodName='runTest',
                  filepath='', **kwargs):
         self.filepath = filepath
--- a/testing/mochitest/mochitest_options.py
+++ b/testing/mochitest/mochitest_options.py
@@ -760,34 +760,29 @@ class MochitestArguments(ArgumentContain
         if options.jscov_dir_prefix:
             options.jscov_dir_prefix = os.path.abspath(options.jscov_dir_prefix)
             if not os.path.isdir(options.jscov_dir_prefix):
                 parser.error(
                     "directory %s does not exist as a destination for coverage "
                     "data." % options.jscov_dir_prefix)
 
         if options.testingModulesDir is None:
+            # Try to guess the testing modules directory.
+            possible = [os.path.join(here, os.path.pardir, 'modules')]
             if build_obj:
-                options.testingModulesDir = os.path.join(
-                    build_obj.topobjdir, '_tests', 'modules')
-            else:
-                # Try to guess the testing modules directory.
-                # This somewhat grotesque hack allows the buildbot machines to find the
-                # modules directory without having to configure the buildbot hosts. This
-                # code should never be executed in local runs because the build system
-                # should always set the flag that populates this variable. If buildbot ever
-                # passes this argument, this code can be deleted.
-                possible = os.path.join(here, os.path.pardir, 'modules')
+                possible.insert(0, os.path.join(build_obj.topobjdir, '_tests', 'modules'))
 
-                if os.path.isdir(possible):
-                    options.testingModulesDir = possible
+            for p in possible:
+                if os.path.isdir(p):
+                    options.testingModulesDir = p
+                    break
 
         if build_obj:
             plugins_dir = os.path.join(build_obj.distdir, 'plugins')
-            if plugins_dir not in options.extraProfileFiles:
+            if os.path.isdir(plugins_dir) and plugins_dir not in options.extraProfileFiles:
                 options.extraProfileFiles.append(plugins_dir)
 
         # Even if buildbot is updated, we still want this, as the path we pass in
         # to the app must be absolute and have proper slashes.
         if options.testingModulesDir is not None:
             options.testingModulesDir = os.path.normpath(
                 options.testingModulesDir)
 
--- a/testing/mochitest/runtests.py
+++ b/testing/mochitest/runtests.py
@@ -794,17 +794,16 @@ class MochitestDesktop(object):
     """
     oldcwd = os.getcwd()
     mochijar = os.path.join(SCRIPT_DIR, 'mochijar')
 
     # Path to the test script on the server
     TEST_PATH = "tests"
     NESTED_OOP_TEST_PATH = "nested_oop"
     CHROME_PATH = "redirect.html"
-    log = None
 
     certdbNew = False
     sslTunnel = None
     DEFAULT_TIMEOUT = 60.0
     mediaDevices = None
 
     patternFiles = {}
 
@@ -824,26 +823,20 @@ class MochitestDesktop(object):
         self._locations = None
 
         self.marionette = None
         self.start_script = None
         self.mozLogs = None
         self.start_script_kwargs = {}
         self.urlOpts = []
 
-        if self.log is None:
-            commandline.log_formatters["tbpl"] = (
-                MochitestFormatter,
-                "Mochitest specific tbpl formatter")
-            self.log = commandline.setup_logging("mochitest",
-                                                 logger_options,
-                                                 {
-                                                     "tbpl": sys.stdout
-                                                 })
-            MochitestDesktop.log = self.log
+        commandline.log_formatters["tbpl"] = (
+            MochitestFormatter,
+            "Mochitest specific tbpl formatter")
+        self.log = commandline.setup_logging("mochitest", logger_options, {"tbpl": sys.stdout})
 
         # Jetpack flavors still don't use the structured logger. We need to process their output
         # slightly differently.
         structured = not self.flavor.startswith('jetpack')
         self.message_logger = MessageLogger(
                 logger=self.log, buffering=quiet, structured=structured)
 
         # Max time in seconds to wait for server startup before tests will fail -- if
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/tests/python/conftest.py
@@ -0,0 +1,178 @@
+# 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/.
+
+from __future__ import print_function, unicode_literals
+
+import json
+import os
+import shutil
+import sys
+from argparse import Namespace
+from cStringIO import StringIO
+
+import pytest
+import requests
+
+import mozfile
+import mozinstall
+from manifestparser import TestManifest
+from mozbuild.base import MozbuildObject
+
+here = os.path.abspath(os.path.dirname(__file__))
+build = MozbuildObject.from_environment(cwd=here)
+
+HARNESS_ROOT_NOT_FOUND = """
+Could not find test harness root. Either a build or the 'GECKO_INSTALLER_URL'
+environment variable is required.
+""".lstrip()
+
+
+def filter_action(action, lines):
+    return filter(lambda x: x['action'] == action, lines)
+
+
+def _get_harness_root():
+    # Check if there is a local build
+    harness_root = os.path.join(build.topobjdir, '_tests', 'testing', 'mochitest')
+    if os.path.isdir(harness_root):
+        return harness_root
+
+    # Check if it was previously set up by another test
+    harness_root = os.path.join(os.environ['PYTHON_TEST_TMP'], 'tests', 'mochitest')
+    if os.path.isdir(harness_root):
+        return harness_root
+
+    # Check if there is a test package to download
+    if 'GECKO_INSTALLER_URL' in os.environ:
+        base_url = os.environ['GECKO_INSTALLER_URL'].rsplit('/', 1)[0]
+        test_packages = requests.get(base_url + '/target.test_packages.json').json()
+
+        dest = os.path.join(os.environ['PYTHON_TEST_TMP'], 'tests')
+        for name in test_packages['mochitest']:
+            url = base_url + '/' + name
+            bundle = os.path.join(os.environ['PYTHON_TEST_TMP'], name)
+
+            r = requests.get(url, stream=True)
+            with open(bundle, 'w+b') as fh:
+                for chunk in r.iter_content(chunk_size=1024):
+                    fh.write(chunk)
+
+            mozfile.extract(bundle, dest)
+
+        return os.path.join(dest, 'mochitest')
+
+    # Couldn't find a harness root, let caller do error handling.
+    return None
+
+
+@pytest.fixture(scope='session')
+def setup_harness_root():
+    harness_root = _get_harness_root()
+    if harness_root:
+        sys.path.insert(0, harness_root)
+
+        # Link the test files to the test package so updates are automatically
+        # picked up. Fallback to copy on Windows.
+        test_root = os.path.join(harness_root, 'tests', 'selftests')
+        if not os.path.exists(test_root):
+            files = os.path.join(here, 'files')
+            if hasattr(os, 'symlink'):
+                os.symlink(files, test_root)
+            else:
+                shutil.copytree(files, test_root)
+
+    elif 'GECKO_INSTALLER_URL' in os.environ:
+        # The mochitest tests will run regardless of whether a build exists or not.
+        # In a local environment, they should simply be skipped if setup fails. But
+        # in automation, we'll need to make sure an error is propagated up.
+        pytest.fail(HARNESS_ROOT_NOT_FOUND)
+    else:
+        # Tests will be marked skipped by the calls to pytest.importorskip() below.
+        # We are purposefully not failing here because running |mach python-test|
+        # without a build is a perfectly valid use case.
+        pass
+
+
+@pytest.fixture(scope='session')
+def binary():
+    try:
+        return build.get_binary_path()
+    except:
+        pass
+
+    app = 'firefox'
+    bindir = os.path.join(os.environ['PYTHON_TEST_TMP'], app)
+    if os.path.isdir(bindir):
+        try:
+            return mozinstall.get_binary(bindir, app_name=app)
+        except:
+            pass
+
+    if 'GECKO_INSTALLER_URL' in os.environ:
+        bindir = mozinstall.install(
+            os.environ['GECKO_INSTALLER_URL'], os.environ['PYTHON_TEST_TMP'])
+        return mozinstall.get_binary(bindir, app_name='firefox')
+
+
+@pytest.fixture(scope='function')
+def parser(request):
+    parser = pytest.importorskip('mochitest_options')
+
+    app = getattr(request.module, 'APP', 'generic')
+    return parser.MochitestArgumentParser(app=app)
+
+
+@pytest.fixture(scope='function')
+def runtests(setup_harness_root, binary, parser, request):
+    """Creates an easy to use entry point into the mochitest harness.
+
+    :returns: A function with the signature `*tests, **opts`. Each test is a file name
+              (relative to the `files` dir). At least one is required. The opts are
+              used to override the default mochitest options, they are optional.
+    """
+    runtests = pytest.importorskip('runtests')
+
+    mochitest_root = runtests.SCRIPT_DIR
+    test_root = os.path.join(mochitest_root, 'tests', 'selftests')
+
+    buf = StringIO()
+    options = vars(parser.parse_args([]))
+    options.update({
+        'app': binary,
+        'keep_open': False,
+        'log_raw': [buf],
+    })
+
+    if not os.path.isdir(runtests.build_obj.bindir):
+        package_root = os.path.dirname(mochitest_root)
+        options.update({
+            'certPath': os.path.join(package_root, 'certs'),
+            'utilityPath': os.path.join(package_root, 'bin'),
+        })
+        options['extraProfileFiles'].append(os.path.join(package_root, 'bin', 'plugins'))
+
+    options.update(getattr(request.module, 'OPTIONS', {}))
+
+    def normalize(test):
+        return {
+            'name': test,
+            'relpath': test,
+            'path': os.path.join(test_root, test),
+            # add a dummy manifest file because mochitest expects it
+            'manifest': os.path.join(test_root, 'mochitest.ini'),
+        }
+
+    def inner(*tests, **opts):
+        assert len(tests) > 0
+
+        manifest = TestManifest()
+        manifest.tests.extend(map(normalize, tests))
+        options['manifestFile'] = manifest
+        options.update(opts)
+
+        result = runtests.run_test_harness(parser, Namespace(**options))
+        out = json.loads('[' + ','.join(buf.getvalue().splitlines()) + ']')
+        buf.close()
+        return result, out
+    return inner
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/tests/python/files/test_fail.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1343659
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test Fail</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+    ok(false, "Test is ok");
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1343659">Mozilla Bug 1343659</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/tests/python/files/test_pass.html
@@ -0,0 +1,24 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1343659
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test Pass</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+  <script type="application/javascript">
+    ok(true, "Test is ok");
+  </script>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1343659">Mozilla Bug 1343659</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+<pre id="test">
+</pre>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/tests/python/python.ini
@@ -0,0 +1,5 @@
+[DEFAULT]
+subsuite = mochitest
+sequential = true
+
+[test_basic_mochitest_plain.py]
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/tests/python/test_basic_mochitest_plain.py
@@ -0,0 +1,73 @@
+# 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/.
+
+import json
+import os
+import sys
+
+import pytest
+
+from conftest import build, filter_action
+
+sys.path.insert(0, os.path.join(build.topsrcdir, 'testing', 'mozharness'))
+from mozharness.base.log import INFO, WARNING
+from mozharness.base.errors import BaseErrorList
+from mozharness.mozilla.buildbot import TBPL_SUCCESS, TBPL_WARNING
+from mozharness.mozilla.structuredlog import StructuredOutputParser
+from mozharness.mozilla.testing.errors import HarnessErrorList
+
+
+def get_mozharness_status(lines, status):
+    parser = StructuredOutputParser(
+        config={'log_level': INFO},
+        error_list=BaseErrorList+HarnessErrorList,
+        strict=False,
+        suite_category='mochitest',
+    )
+
+    for line in lines:
+        parser.parse_single_line(json.dumps(line))
+    return parser.evaluate_parser(status)
+
+
+def test_output_pass(runtests):
+    status, lines = runtests('test_pass.html')
+    assert status == 0
+
+    tbpl_status, log_level = get_mozharness_status(lines, status)
+    assert tbpl_status == TBPL_SUCCESS
+    assert log_level == WARNING
+
+    lines = filter_action('test_status', lines)
+    assert len(lines) == 1
+    assert lines[0]['status'] == 'PASS'
+
+
+def test_output_fail(runtests):
+    from runtests import build_obj
+
+    status, lines = runtests('test_fail.html')
+    assert status == 1
+
+    tbpl_status, log_level = get_mozharness_status(lines, status)
+    assert tbpl_status == TBPL_WARNING
+    assert log_level == WARNING
+
+    lines = filter_action('test_status', lines)
+
+    # If we are running with a build_obj, the failed status will be
+    # logged a second time at the end of the run.
+    if build_obj:
+        assert len(lines) == 2
+    else:
+        assert len(lines) == 1
+    assert lines[0]['status'] == 'FAIL'
+
+    if build_obj:
+        assert set(lines[0].keys()) == set(lines[1].keys())
+        assert set(lines[0].values()) == set(lines[1].values())
+
+
+if __name__ == '__main__':
+    sys.exit(pytest.main(['--verbose', __file__]))
--- a/testing/mozbase/mozinstall/mozinstall/mozinstall.py
+++ b/testing/mozbase/mozinstall/mozinstall/mozinstall.py
@@ -3,19 +3,22 @@
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from optparse import OptionParser
 import os
 import shutil
 import subprocess
 import sys
 import tarfile
+import tempfile
 import time
 import zipfile
 
+import requests
+
 import mozfile
 import mozinfo
 
 try:
     import pefile
     has_pefile = True
 except ImportError:
     has_pefile = False
@@ -91,22 +94,31 @@ def get_binary(path, app_name):
 def install(src, dest):
     """Install a zip, exe, tar.gz, tar.bz2 or dmg file, and return the path of
     the installation folder.
 
     :param src: Path to the install file
     :param dest: Path to install to (to ensure we do not overwrite any existent
                  files the folder should not exist yet)
     """
+
+    if not is_installer(src):
+        msg = "{} is not a valid installer file".format(src)
+        if '://' in src:
+            try:
+                return _install_url(src, dest)
+            except:
+                exc, val, tb = sys.exc_info()
+                msg = "{} ({})".format(msg, val)
+                raise InvalidSource, msg, tb
+        raise InvalidSource(msg)
+
     src = os.path.realpath(src)
     dest = os.path.realpath(dest)
 
-    if not is_installer(src):
-        raise InvalidSource(src + ' is not valid installer file.')
-
     did_we_create = False
     if not os.path.exists(dest):
         did_we_create = True
         os.makedirs(dest)
 
     trbk = None
     try:
         install_dir = None
@@ -232,16 +244,36 @@ def uninstall(install_folder):
                 # http://docs.python.org/library/sys.html#sys.exc_info
                 del trbk
 
     # Ensure that we remove any trace of the installation. Even the uninstaller
     # on Windows leaves files behind we have to explicitely remove.
     mozfile.remove(install_folder)
 
 
+def _install_url(url, dest):
+    """Saves a url to a temporary file, and passes that through to the
+    install function.
+
+    :param url: Url to the install file
+    :param dest: Path to install to (to ensure we do not overwrite any existent
+                 files the folder should not exist yet)
+    """
+    r = requests.get(url, stream=True)
+    name = tempfile.mkstemp()[1]
+    try:
+        with open(name, 'w+b') as fh:
+            for chunk in r.iter_content(chunk_size=16*1024):
+                fh.write(chunk)
+        result = install(name, dest)
+    finally:
+        mozfile.remove(name)
+    return result
+
+
 def _install_dmg(src, dest):
     """Extract a dmg file into the destination folder and return the
     application folder.
 
     src -- DMG image which has to be extracted
     dest -- the path to extract to
 
     """
--- a/testing/mozbase/mozinstall/setup.py
+++ b/testing/mozbase/mozinstall/setup.py
@@ -6,20 +6,21 @@ import os
 from setuptools import setup
 
 try:
     here = os.path.dirname(os.path.abspath(__file__))
     description = file(os.path.join(here, 'README.md')).read()
 except IOError:
     description = None
 
-PACKAGE_VERSION = '1.12'
+PACKAGE_VERSION = '1.13'
 
 deps = ['mozinfo >= 0.7',
         'mozfile >= 1.0',
+        'requests',
         ]
 
 setup(name='mozInstall',
       version=PACKAGE_VERSION,
       description="package for installing and uninstalling Mozilla applications",
       long_description="see http://mozbase.readthedocs.org/",
       # Get strings from http://pypi.python.org/pypi?%3Aaction=list_classifiers
       classifiers=['Environment :: Console',
--- a/testing/mozbase/mozinstall/tests/test.py
+++ b/testing/mozbase/mozinstall/tests/test.py
@@ -108,16 +108,20 @@ class TestMozInstall(unittest.TestCase):
         elif mozinfo.isWin:
             self.assertRaises(mozinstall.InvalidSource, mozinstall.install,
                               self.bz2, 'firefox')
 
         elif mozinfo.isMac:
             self.assertRaises(mozinstall.InvalidSource, mozinstall.install,
                               self.bz2, 'firefox')
 
+        # Test an invalid url handler
+        self.assertRaises(mozinstall.InvalidSource, mozinstall.install,
+                          'file://foo.bar', 'firefox')
+
     @unittest.skipIf(mozinfo.isWin, "Bug 1157352 - We need a new firefox.exe "
                      "for mozinstall 1.12 and higher.")
     def test_install(self):
         """ Test mozinstall's install capability """
 
         if mozinfo.isLinux:
             installdir = mozinstall.install(self.bz2, self.tempdir)
             self.assertEqual(os.path.join(self.tempdir, 'firefox'), installdir)
@@ -162,10 +166,11 @@ class TestMozInstall(unittest.TestCase):
             mozinstall.uninstall(installdir_zip)
             self.assertFalse(os.path.exists(installdir_zip))
 
         elif mozinfo.isMac:
             installdir = mozinstall.install(self.dmg, self.tempdir)
             mozinstall.uninstall(installdir)
             self.assertFalse(os.path.exists(installdir))
 
+
 if __name__ == '__main__':
     mozunit.main()
--- a/testing/mozbase/mozlog/mozlog/commandline.py
+++ b/testing/mozbase/mozlog/mozlog/commandline.py
@@ -209,16 +209,19 @@ def setup_logging(logger, args, defaults
                      logs otherwise).
     :param formatter_defaults: A dictionary of {option_name: default_value} to provide
                                to the formatters in the absence of command line overrides.
     :rtype: StructuredLogger
     """
 
     if not isinstance(logger, StructuredLogger):
         logger = StructuredLogger(logger)
+        # The likely intent when using this function is to get a brand new
+        # logger, so reset state in case it was previously initialized.
+        logger.reset_state()
 
     # Keep track of any options passed for formatters.
     formatter_options = {}
     # Keep track of formatters and list of streams specified.
     formatters = defaultdict(list)
     found = False
     found_stdout_logger = False
     if args is None:
--- a/testing/mozbase/mozlog/mozlog/structuredlog.py
+++ b/testing/mozbase/mozlog/mozlog/structuredlog.py
@@ -106,16 +106,19 @@ lint_levels = ["ERROR", "WARNING"]
 def log_actions():
     """Returns the set of actions implemented by mozlog."""
     return set(convertor_registry.keys())
 
 
 class LoggerState(object):
 
     def __init__(self):
+        self.reset()
+
+    def reset(self):
         self.handlers = []
         self.running_tests = set()
         self.suite_started = False
         self.component_states = {}
 
 
 class ComponentState(object):
 
@@ -149,16 +152,23 @@ class StructuredLogger(object):
     def add_handler(self, handler):
         """Add a handler to the current logger"""
         self._state.handlers.append(handler)
 
     def remove_handler(self, handler):
         """Remove a handler from the current logger"""
         self._state.handlers.remove(handler)
 
+    def reset_state(self):
+        """Resets the logger to a brand new state. This means all handlers
+        are removed, running tests are discarded and components are reset.
+        """
+        self._state.reset()
+        self._component_state = self._state.component_states[self.component] = ComponentState()
+
     def send_message(self, topic, command, *args):
         """Send a message to each handler configured for this logger. This
         part of the api is useful to those users requiring dynamic control
         of a handler's behavior.
 
         :param topic: The name used by handlers to subscribe to a message.
         :param command: The name of the command to issue.
         :param args: Any arguments known to the target for specialized
--- a/testing/mozharness/mozharness/mozilla/l10n/locales.py
+++ b/testing/mozharness/mozharness/mozilla/l10n/locales.py
@@ -175,18 +175,18 @@ class LocalesMixin(ChunkingMixin):
             dirs['abs_l10n_dir'] = os.path.join(dirs['abs_work_dir'],
                                                 c['l10n_dir'])
         if 'mozilla_dir' in c:
             dirs['abs_mozilla_dir'] = os.path.join(dirs['abs_work_dir'],
                                                    c['mozilla_dir'])
             dirs['abs_locales_src_dir'] = os.path.join(dirs['abs_mozilla_dir'],
                                                        c['locales_dir'])
             dirs['abs_compare_locales_dir'] = os.path.join(dirs['abs_mozilla_dir'],
-                                                           'python', 'compare-locales',
-                                                           'compare_locales')
+                                                           'third_party', 'python',
+                                                           'compare-locales', 'compare_locales')
         else:
             # Use old-compare-locales if no mozilla_dir set, needed
             # for clobberer, and existing mozharness tests.
             dirs['abs_compare_locales_dir'] = os.path.join(dirs['abs_work_dir'],
                                                            'compare-locales')
 
         if 'objdir' in c:
             if os.path.isabs(c['objdir']):
--- a/testing/mozharness/mozprocess/processhandler.py
+++ b/testing/mozharness/mozprocess/processhandler.py
@@ -899,16 +899,19 @@ class ProcessHandler(ProcessHandlerMixin
 
     If logfile is not None, the output produced by the process will be
     appended to the given file.
     """
 
     def __init__(self, cmd, logfile=None, storeOutput=True, **kwargs):
         kwargs.setdefault('processOutputLine', [])
 
+        if not isinstance(kwargs['processOutputLine'], (list, tuple)):
+            kwargs['processOutputLine'] = [kwargs['processOutputLine']]
+
         # Print to standard output only if no outputline provided
         if not kwargs['processOutputLine']:
             kwargs['processOutputLine'].append(print_output)
 
         if logfile:
             logoutput = LogOutput(logfile)
             kwargs['processOutputLine'].append(logoutput)
 
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -868,20 +868,16 @@ this.Extension = class extends Extension
   }
 
   loadManifest() {
     return super.loadManifest().then(manifest => {
       if (this.errors.length) {
         return Promise.reject({errors: this.errors});
       }
 
-      if (AppConstants.RELEASE_OR_BETA) {
-        return manifest;
-      }
-
       // Load Experiments APIs that this extension depends on.
       return Promise.all(
         Array.from(this.apiNames, api => ExtensionAPIs.load(api))
       ).then(apis => {
         for (let API of apis) {
           this.apis.push(new API(this));
         }
 
--- a/toolkit/components/extensions/ext-c-runtime.js
+++ b/toolkit/components/extensions/ext-c-runtime.js
@@ -19,16 +19,17 @@ this.runtime = class extends ExtensionAP
           extensionId = extensionId || extension.id;
           let recipient = {extensionId};
 
           return context.messenger.connect(context.messageManager, name, recipient);
         },
 
         sendMessage(...args) {
           let extensionId, message, options, responseCallback;
+
           if (typeof args[args.length - 1] === "function") {
             responseCallback = args.pop();
           }
 
           function checkOptions(options) {
             let toProxyScript = false;
             if (typeof options !== "object") {
               return [false, "runtime.sendMessage's options argument is invalid"];
@@ -50,28 +51,28 @@ this.runtime = class extends ExtensionAP
           }
 
           if (!args.length) {
             return Promise.reject({message: "runtime.sendMessage's message argument is missing"});
           } else if (args.length === 1) {
             message = args[0];
           } else if (args.length === 2) {
             // With two optional arguments, this is the ambiguous case,
-            // particularly sendMessage("string", {});
+            // particularly sendMessage("string", {} or null)
             // Given that sending a message within the extension is generally
             // more common than sending the empty object to another extension,
             // we prefer that conclusion, as long as the second argument looks
-            // like valid options.
+            // like valid options object, or is null/undefined.
             let [validOpts] = checkOptions(args[1]);
-            if (validOpts) {
+            if (validOpts || args[1] == null) {
               [message, options] = args;
             } else {
               [extensionId, message] = args;
             }
-          } else if (args.length === 3) {
+          } else if (args.length === 3 || (args.length === 4 && args[3] == null)) {
             [extensionId, message, options] = args;
           } else if (args.length === 4 && !responseCallback) {
             return Promise.reject({message: "runtime.sendMessage's last argument is not a function"});
           } else {
             return Promise.reject({message: "runtime.sendMessage received too many arguments"});
           }
 
           if (extensionId != null && typeof extensionId !== "string") {
--- a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage_args.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage_args.js
@@ -54,29 +54,45 @@ add_task(async function() {
 
   // sendMessage() takes 3 arguments:
   //  optional extensionID
   //  mandatory message
   //  optional options
   // Due to this insane design we parse its arguments manually.  This
   // test is meant to cover all the combinations.
 
+  // A single null or undefined argument is allowed, and represents the message
+  extension1.sendMessage(null);
+  await checkLocalMessage(null);
+
   // With one argument, it must be just the message
   extension1.sendMessage("message");
   await checkLocalMessage("message");
 
   // With two arguments, these cases should be treated as (extensionID, message)
   extension1.sendMessage(ID2, "message");
   await checkRemoteMessage("message");
 
   extension1.sendMessage(ID2, {msg: "message"});
   await checkRemoteMessage({msg: "message"});
 
-  // And this case should be (message, options)
+  // And these should be (message, options)
   extension1.sendMessage("message", {});
   await checkLocalMessage("message");
 
+  // or (message, non-callback), pick your poison
+  extension1.sendMessage("message", undefined);
+  await checkLocalMessage("message");
+
   // With three arguments, we send a cross-extension message
   extension1.sendMessage(ID2, "message", {});
   await checkRemoteMessage("message");
 
+  // Even when the last one is null or undefined
+  extension1.sendMessage(ID2, "message", undefined);
+  await checkRemoteMessage("message");
+
+  // The four params case is unambigous, so we allow null as a (non-) callback
+  extension1.sendMessage(ID2, "message", {}, null);
+  await checkRemoteMessage("message");
+
   await Promise.all([extension1.unload(), extension2.unload()]);
 });
--- a/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage_errors.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_runtime_sendMessage_errors.js
@@ -4,17 +4,17 @@
 
 add_task(async function test_sendMessage_error() {
   async function background() {
     let circ = {};
     circ.circ = circ;
     let testCases = [
       // [arguments, expected error string],
       [[], "runtime.sendMessage's message argument is missing"],
-      [[null, null, null, null], "runtime.sendMessage's last argument is not a function"],
+      [[null, null, null, 42], "runtime.sendMessage's last argument is not a function"],
       [[null, null, 1], "runtime.sendMessage's options argument is invalid"],
       [[1, null, null], "runtime.sendMessage's extensionId argument is invalid"],
       [[null, null, null, null, null], "runtime.sendMessage received too many arguments"],
 
       // Even when the parameters are accepted, we still expect an error
       // because there is no onMessage listener.
       [[null, null, null], "Could not establish connection. Receiving end does not exist."],
 
--- a/toolkit/content/widgets/datetimebox.css
+++ b/toolkit/content/widgets/datetimebox.css
@@ -3,20 +3,28 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 @namespace url("http://www.w3.org/1999/xhtml");
 @namespace xul url("http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul");
 
 .datetime-input-box-wrapper {
   -moz-appearance: none;
   display: inline-flex;
+  flex: 1;
   cursor: default;
   background-color: inherit;
   color: inherit;
   font-family: monospace;
+  min-width: 0;
+  justify-content: space-between;
+}
+
+.datetime-input-edit-wrapper {
+  overflow: hidden;
+  white-space: nowrap;
 }
 
 .datetime-edit-field {
   display: inline;
   cursor: default;
   -moz-user-select: none;
   text-align: center;
   padding: 1px 3px;
@@ -43,10 +51,10 @@
   background-image: url(chrome://global/skin/icons/input-clear.svg);
   background-color: transparent;
   background-repeat: no-repeat;
   background-size: 12px, 12px;
   border: none;
   height: 12px;
   width: 12px;
   align-self: center;
-  justify-content: flex-end;
+  flex: none;
 }
--- a/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.dtd
+++ b/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.dtd
@@ -255,10 +255,11 @@
 <!ENTITY disabledUnsigned.devInfo.linkToManual "manual">
 <!ENTITY disabledUnsigned.devInfo.end ".">
 
 <!ENTITY pluginDeprecation.description "Missing something? Some plugins are no longer supported by &brandShortName;.">
 <!ENTITY pluginDeprecation.learnMore "Learn More.">
 
 <!ENTITY legacyWarning.description "Missing something? Some extensions are no longer supported by &brandShortName;.">
 <!ENTITY legacyWarning.showLegacy "Show legacy extensions">
+<!ENTITY legacyExtensions.title "Legacy Extensions">
 <!ENTITY legacyExtensions.description "These extensions do not meet current &brandShortName; standards so they have been deactivated.">
 <!ENTITY legacyExtensions.learnMore "Learn about the changes to add-ons">
new file mode 100644
--- /dev/null
+++ b/toolkit/locales/l10n.toml
@@ -0,0 +1,29 @@
+# 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/.
+
+basepath = "../.."
+
+
+[env]
+    l = "{l10n_base}/{locale}/"
+
+
+[[paths]]
+    reference = "toolkit/locales/en-US/**"
+    l10n = "{l}toolkit/**"
+
+[[paths]]
+    reference = "dom/locales/en-US/**"
+    l10n = "{l}dom/**"
+
+[[paths]]
+    reference = "netwerk/locales/en-US/**"
+    l10n = "{l}netwerk/**"
+
+[[paths]]
+    reference = "security/manager/locales/en-US/**"
+    l10n = "{l}security/manager/**"
+
+[[includes]]
+    path = "devtools/shared/locales/l10n.toml"
--- a/toolkit/mozapps/extensions/content/extensions.xul
+++ b/toolkit/mozapps/extensions/content/extensions.xul
@@ -438,17 +438,17 @@
                 <spacer class="alert-spacer-after"/>
               </vbox>
               <richlistbox id="addon-list" class="list" flex="1"/>
             </vbox>
 
             <!-- legacy extensions view -->
             <vbox id="legacy-view" flex="1" class="view-pane" align="stretch" tabindex="0">
               <vbox id="legacy-extensions-info">
-                <label id="legacy-extensions-heading" value="Legacy Extensions"/>
+                <label id="legacy-extensions-heading" value="&legacyExtensions.title;"/>
                 <description>
                   &legacyExtensions.description;
                   <label class="text-link plain" id="legacy-learnmore">&legacyExtensions.learnMore;</label>
                 </description>
               </vbox>
               <richlistbox id="legacy-list" class="list" flex="1"/>
             </vbox>
 
--- a/tools/compare-locales/docs/index.rst
+++ b/tools/compare-locales/docs/index.rst
@@ -15,17 +15,34 @@ idea to check the :doc:`./glossary` for 
 we use for what.
 
 Exposing strings
 ----------------
 
 Localizers only handle a few file formats in well-known locations in the
 source tree.
 
-The locations are in directories like
+The locations are specified by TOML files. They're part of the bigger
+localization ecosystem at Mozilla, and `the documentation about the
+file format <http://moz-l10n-config.readthedocs.io/en/latest/fileformat.html>`_
+explains how to set them up, and what the entries mean. In short, you find
+
+.. code-block:: toml
+
+    [[paths]]
+        reference = browser/locales/en-US/**
+        l10n = {l}browser/**
+
+to add a directory for all localizations. Changes to these files are best
+submitted for review by :Pike or :flod.
+
+These configuration files are the future, and right now, we still have
+support for the previous way to configuring l10n, which is described below.
+
+The locations are commonly in directories like
 
     :file:`browser/`\ ``locales/en-US/``\ :file:`subdir/file.ext`
 
 The first thing to note is that only files beneath :file:`locales/en-US` are
 exposed to localizers. The second thing to note is that only a few directories
 are exposed. Which directories are exposed is defined in files called
 ``l10n.ini``, which are at a
 `few places <https://dxr.mozilla.org/mozilla-central/search?q=path%3Al10n.ini&redirect=true>`_
@@ -83,17 +100,17 @@ The following file formats are known to 
 DTD
     Used in XUL and XHTML. Also for Android native strings.
 Properties
     Used from JavaScript and C++. When used from js, also comes with
     `plural support <https://developer.mozilla.org/docs/Mozilla/Localization/Localization_and_Plurals>`_.
 ini
     Used by the crashreporter and updater, avoid if possible.
 foo.defines
-    Used during builds, for example to create file:`install.rdf` for
+    Used during builds, for example to create :file:`install.rdf` for
     language packs.
 
 Adding new formats involves changing various different tools, and is strongly
 discouraged.
 
 Exceptions
 ----------
 Generally, anything that exists in ``en-US`` needs a one-to-one mapping in