merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Tue, 28 Jun 2016 16:09:05 +0200
changeset 381966 e45890951ce77c3df05575bd54072b9f300d77b0
parent 381869 cf243ad179e9929358583f4b8e6f8ce709f85eb6 (current diff)
parent 381965 581d221488b88275575d6b11dc1a999918deb6bd (diff)
child 381967 b0f08a4893d1437c0f85624d6fb311bb469d09b1
child 381997 e5bc108c46ea9b00bb639e73c857ecc6ddf05379
child 382019 910bb13e5929010e0e34b6a08fd3bcf6f5657b6e
child 382028 5ef0cae167fde28438c131d40e72718bbb1cf745
child 382052 72aef44bce6f07f25c3cc81352b92bfdd6661bd5
child 382068 cb3904d36c02fea4578bf6eb4e33e43d97f22feb
child 382069 22af6d11d01305377efe40dad20e2e8fceaf7381
child 382070 6215cb51c39e13ea245469ef29b0378a5a313df1
child 382081 e774866bf8a12537e451ccc4b38ffbf0610b5d49
child 382100 cd6bdd6e1f527a44e727c233cf6ecf0e118358f3
child 382105 925f54e6ecf04de9ee69f2818e411dfcaa5d7840
child 382110 029c91a259d0031ba19c062fcb17bc98bdbc646b
child 382126 f21eff63bbd8e9dd1885d1eb80623ebea3357037
child 382132 466965ae24b6126898f0fc166c91dd4e63572de3
child 382140 53f3037272d5151f361f796139bc6dc1e4f1f53b
child 382141 1ab1b188f893773dda3e95b36a8a45d6cdb69520
child 382159 8f8e9cd682234b44ba5fa8925ec4004bab2c27dc
child 382160 260b04d1226f35bcb3c61bc3d17115507b573486
child 382164 4b1011b86eb29083928c022295da7fe96ab9d01d
child 382204 8cbe1df0f202cc10dfb8adb7f649413a45f4fd79
child 382206 80b6580c93407481d7b712d5b3ae23828f9096cc
child 382212 df2004aa2ebc79268c948d905845a27e7d4b7b83
child 382227 4e274a13527e630ef90832cfd86da91f6bff6c17
child 382229 90a1a87a659b24a8c364bac7a268b21db306c1b1
child 382230 2b1aa81476d0a95a469be5f7fc6df904338a0faf
child 382231 841bcc9a2acc015524234373104e1bc1c470fc75
child 382233 11a756a3cba92d9c9cd18350b14b4d0cd663b8d3
child 382237 7ea609eb5e68036d45b6c8381b23842b12111004
child 382239 2a073cea4ae621eb6a5b9dcc1c14df9b9d6fb707
child 382241 36205b94cc096d30f8b601c68e69929a126f22d0
child 382242 78d86b1f6c99a2fd808a3e6af8221b279ef1208b
child 382266 f2d6517bc607b7d4e11c1647748cc3791a11c1a6
child 382270 05e8b8481cc10b282ac14b93bb6e711929996970
child 382271 808ea690ee2b1279216929d2190594a9f1e7ff28
child 382275 33f84b95e3931feaf35ed0b4efc53b51a468ff16
child 382279 15b835270aa39e84454e3945cff058aec16b537c
child 382280 4a4950cc62d1fb151cd4a4beb8f63dacf459afdd
child 382281 9db55281f64200bb2069a189113322e0f9a10898
child 382402 cefb60c7aa6370bbbbe0b6353ba3d40319c28e18
child 382406 c9146d887d15e44aefb9391af2f12d19f9200f79
child 382575 f622f899e715f0bbe70543103839006893c3d98d
child 382605 1efab206e5cca8de30c8665d8768f8a8806df18f
child 382607 5f969968fbd9ecdb86768bdb16a211acfa77f661
child 382632 60099a5ca6dfa574bf4f5188f9fbbd6b59769a30
child 382641 babf2601f53fd666bc11bc8ab3609c8897e3f976
child 382696 6ccf9d93a5a2799e40c8e39d0a85d5b386ed5f60
child 382743 0b7434898e3492af368cdb0d57ab69cf7df1b494
child 382744 761d55a70127df480372efe4ef5511e7cf477a41
child 382817 a39ee9aac2b4386332f8059cb0906efa2a87299c
child 382934 14d28d9c1f1b2ba6b61abfd55e7f91768402b229
child 382940 1e5709588a79b95a6563dc477a636fedf7e2fe08
child 383063 739ed29a7098d6692a3375c8af2c60874bef9093
child 385326 a7fc949740e747d64857d30c2c35029271e6c21a
push id21580
push userbmo:andrew@comminos.com
push dateTue, 28 Jun 2016 14:23:06 +0000
reviewersmerge
milestone50.0a1
merge mozilla-inbound to mozilla-central a=merge
dom/base/nsIXMLHttpRequest.idl
dom/base/nsXMLHttpRequest.cpp
dom/base/nsXMLHttpRequest.h
dom/base/test/echo.sjs
dom/base/test/file_XHRDocURI.html
dom/base/test/file_XHRDocURI.html^headers^
dom/base/test/file_XHRDocURI.sjs
dom/base/test/file_XHRDocURI.text
dom/base/test/file_XHRDocURI.text^headers^
dom/base/test/file_XHRDocURI.xml
dom/base/test/file_XHRDocURI.xml^headers^
dom/base/test/file_XHRResponseURL.js
dom/base/test/file_XHRResponseURL.sjs
dom/base/test/file_XHRResponseURL.text
dom/base/test/file_XHRResponseURL.text^headers^
dom/base/test/file_XHRResponseURL_nocors.text
dom/base/test/file_XHRSendData.sjs
dom/base/test/file_XHRSendData_doc.xml
dom/base/test/file_XHRSendData_doc.xml^headers^
dom/base/test/file_XHR_anon.sjs
dom/base/test/file_XHR_binary1.bin
dom/base/test/file_XHR_binary1.bin^headers^
dom/base/test/file_XHR_binary2.bin
dom/base/test/file_XHR_fail1.txt
dom/base/test/file_XHR_fail1.txt^headers^
dom/base/test/file_XHR_fail1b.txt
dom/base/test/file_XHR_header.sjs
dom/base/test/file_XHR_pass1.xml
dom/base/test/file_XHR_pass2.txt
dom/base/test/file_XHR_pass3.txt
dom/base/test/file_XHR_pass3.txt^headers^
dom/base/test/file_XHR_system_redirect.html
dom/base/test/file_XHR_system_redirect.html^headers^
dom/base/test/file_XHR_timeout.sjs
dom/base/test/file_html_in_xhr.html
dom/base/test/file_html_in_xhr.sjs
dom/base/test/file_html_in_xhr2.html
dom/base/test/file_html_in_xhr3.html
dom/base/test/progressserver.sjs
dom/base/test/responseIdentical.sjs
dom/base/test/test_XHR.html
dom/base/test/test_XHRDocURI.html
dom/base/test/test_XHRResponseURL.html
dom/base/test/test_XHRSendData.html
dom/base/test/test_XHR_anon.html
dom/base/test/test_XHR_header.html
dom/base/test/test_XHR_onuploadprogress.html
dom/base/test/test_XHR_parameters.html
dom/base/test/test_XHR_system.html
dom/base/test/test_XHR_timeout.html
dom/base/test/test_XHR_timeout.js
dom/base/test/test_html_in_xhr.html
dom/base/test/test_sync_xhr_timer.xhtml
dom/base/test/test_xhr_abort_after_load.html
dom/base/test/test_xhr_forbidden_headers.html
dom/base/test/test_xhr_overridemimetype_throws_on_invalid_state.html
dom/base/test/test_xhr_progressevents.html
dom/base/test/test_xhr_send.html
dom/base/test/test_xhr_send_readystate.html
dom/base/test/test_xhr_withCredentials.html
dom/gamepad/GamepadFunctions.cpp
dom/gamepad/GamepadFunctions.h
dom/interfaces/gamepad/moz.build
dom/interfaces/gamepad/nsIGamepadServiceTest.idl
dom/tests/mochitest/gamepad/gamepad_service_test_chrome_script.js
dom/workers/XMLHttpRequest.cpp
dom/workers/XMLHttpRequest.h
dom/workers/XMLHttpRequestUpload.cpp
dom/workers/XMLHttpRequestUpload.h
dom/workers/test/file_getcookie.sjs
dom/workers/test/relativeLoad_import.js
dom/workers/test/relativeLoad_worker.js
dom/workers/test/relativeLoad_worker2.js
dom/workers/test/subdir/relativeLoad_sub_import.js
dom/workers/test/subdir/relativeLoad_sub_worker.js
dom/workers/test/subdir/relativeLoad_sub_worker2.js
dom/workers/test/terminateSyncXHR_frame.html
dom/workers/test/terminateSyncXHR_worker.js
dom/workers/test/testXHR.txt
dom/workers/test/test_relativeLoad.html
dom/workers/test/test_terminateSyncXHR.html
dom/workers/test/test_xhr.html
dom/workers/test/test_xhr2.html
dom/workers/test/test_xhrAbort.html
dom/workers/test/test_xhr_3rdparty.html
dom/workers/test/test_xhr_cors_redirect.html
dom/workers/test/test_xhr_headers.html
dom/workers/test/test_xhr_implicit_cancel.html
dom/workers/test/test_xhr_parameters.html
dom/workers/test/test_xhr_parameters.js
dom/workers/test/test_xhr_responseURL.html
dom/workers/test/test_xhr_system.html
dom/workers/test/test_xhr_system.js
dom/workers/test/test_xhr_timeout.html
dom/workers/test/xhr2_worker.js
dom/workers/test/xhrAbort_worker.js
dom/workers/test/xhr_cors_redirect.js
dom/workers/test/xhr_cors_redirect.sjs
dom/workers/test/xhr_headers_server.sjs
dom/workers/test/xhr_headers_worker.js
dom/workers/test/xhr_implicit_cancel_worker.js
dom/workers/test/xhr_worker.js
gfx/angle/include/KHR/khrplatform.h
gfx/angle/src/compiler/preprocessor/generate_parser.sh
gfx/angle/src/libANGLE/Data.cpp
gfx/angle/src/libANGLE/Data.h
gfx/angle/src/libANGLE/renderer/ImplFactory.h
gfx/angle/src/libANGLE/renderer/Renderer.cpp
gfx/angle/src/libANGLE/renderer/Renderer.h
gfx/angle/src/libANGLE/renderer/d3d/copyimage.cpp
gfx/angle/src/libANGLE/renderer/d3d/copyimage.h
gfx/angle/src/libANGLE/renderer/d3d/copyimage.inl
gfx/angle/src/libANGLE/renderer/d3d/d3d11/NativeWindow.h
gfx/angle/src/libANGLE/renderer/d3d/d3d11/gen_swizzle_format_table.py
gfx/angle/src/libANGLE/renderer/d3d/d3d11/internal_format_initializer_table.cpp
gfx/angle/src/libANGLE/renderer/d3d/d3d11/internal_format_initializer_table.h
gfx/angle/src/libANGLE/renderer/d3d/d3d11/swizzle_format_data.json
gfx/angle/src/libANGLE/renderer/d3d/d3d11/swizzle_format_info.h
gfx/angle/src/libANGLE/renderer/d3d/d3d11/swizzle_format_info_autogen.cpp
gfx/angle/src/libANGLE/renderer/d3d/d3d11/win32/NativeWindow.cpp
gfx/angle/src/libANGLE/renderer/d3d/formatutilsD3D.cpp
gfx/angle/src/libANGLE/renderer/d3d/imageformats.h
gfx/angle/src/libANGLE/renderer/generate_new_renderer.py
gfx/angle/src/tests/gl_tests/FramebufferFormatsTest.cpp
js/src/tests/ecma/jsref.js
js/src/tests/ecma_2/jsref.js
js/src/tests/js1_1/jsref.js
js/src/tests/js1_2/jsref.js
js/src/tests/js1_3/jsref.js
js/src/tests/js1_4/jsref.js
js/src/tests/js1_5/Regress/regress-416628.js
modules/libpref/init/all.js
modules/woff2/missing-assert-header.patch
taskcluster/taskgraph/types.py
testing/docker/desktop-test/bin/run-wizard
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -208,16 +208,17 @@
 @RESPATH@/components/dom_tv.xpt
 @RESPATH@/components/dom_inputport.xpt
 @RESPATH@/components/dom_views.xpt
 @RESPATH@/components/dom_voicemail.xpt
 #ifdef MOZ_WEBSPEECH
 @RESPATH@/components/dom_webspeechrecognition.xpt
 #endif
 @RESPATH@/components/dom_xbl.xpt
+@RESPATH@/components/dom_xhr.xpt
 @RESPATH@/components/dom_xpath.xpt
 @RESPATH@/components/dom_xul.xpt
 @RESPATH@/components/dom_time.xpt
 @RESPATH@/components/dom_presentation.xpt
 @RESPATH@/components/downloads.xpt
 @RESPATH@/components/editor.xpt
 @RESPATH@/components/embed_base.xpt
 @RESPATH@/components/extensions.xpt
--- a/browser/base/content/contentSearchUI.js
+++ b/browser/base/content/contentSearchUI.js
@@ -77,16 +77,19 @@ ContentSearchUIController.prototype = {
   // if it's set when the suggestions table is actually opened.
   _pendingOneOffRefresh: undefined,
 
   get defaultEngine() {
     return this._defaultEngine;
   },
 
   set defaultEngine(engine) {
+    if (this._defaultEngine && this._defaultEngine.icon) {
+      URL.revokeObjectURL(this._defaultEngine.icon);
+    }
     let icon;
     if (engine.iconBuffer) {
       icon = this._getFaviconURIFromBuffer(engine.iconBuffer);
     }
     else {
       icon = this._getImageURIForCurrentResolution(
         "chrome://mozapps/skin/places/defaultFavicon.png");
     }
@@ -863,16 +866,20 @@ ContentSearchUIController.prototype = {
       if (engine.iconBuffer) {
         uri = this._getFaviconURIFromBuffer(engine.iconBuffer);
       }
       else {
         uri = this._getImageURIForCurrentResolution(
           "chrome://browser/skin/search-engine-placeholder.png");
       }
       img.setAttribute("src", uri);
+      img.addEventListener("load", function imgLoad() {
+        img.removeEventListener("load", imgLoad);
+        URL.revokeObjectURL(uri);
+      });
       button.appendChild(img);
       button.style.width = buttonWidth + "px";
       button.setAttribute("title", engine.name);
 
       button.engineName = engine.name;
       button.addEventListener("click", this);
       button.addEventListener("mousemove", this);
 
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -211,21 +211,19 @@
 @RESPATH@/components/dom_traversal.xpt
 @RESPATH@/components/dom_tv.xpt
 @RESPATH@/components/dom_voicemail.xpt
 #ifdef MOZ_WEBSPEECH
 @RESPATH@/components/dom_webspeechrecognition.xpt
 #endif
 @RESPATH@/components/dom_workers.xpt
 @RESPATH@/components/dom_xbl.xpt
+@RESPATH@/components/dom_xhr.xpt
 @RESPATH@/components/dom_xpath.xpt
 @RESPATH@/components/dom_xul.xpt
-#ifdef MOZ_GAMEPAD
-@RESPATH@/components/dom_gamepad.xpt
-#endif
 @RESPATH@/components/dom_payment.xpt
 @RESPATH@/components/dom_presentation.xpt
 @RESPATH@/components/downloads.xpt
 @RESPATH@/components/editor.xpt
 @RESPATH@/components/embed_base.xpt
 @RESPATH@/components/extensions.xpt
 @RESPATH@/components/exthandler.xpt
 @RESPATH@/components/exthelper.xpt
--- a/build/annotationProcessors/CodeGenerator.java
+++ b/build/annotationProcessors/CodeGenerator.java
@@ -37,23 +37,24 @@ public class CodeGenerator {
         this.clsName = annotatedClass.generatedName;
 
         final String unqualifiedName = Utils.getUnqualifiedName(clsName);
         header.append(
                 "class " + clsName + " : public mozilla::jni::ObjectBase<" +
                         unqualifiedName + ", jobject>\n" +
                 "{\n" +
                 "public:\n" +
+                "    static const char name[];\n" +
+                "\n" +
                 "    explicit " + unqualifiedName + "(const Context& ctx) : ObjectBase<" +
                         unqualifiedName + ", jobject>(ctx) {}\n" +
                 "\n");
 
         cpp.append(
-                "template<> const char mozilla::jni::Context<" +
-                        clsName + ", jobject>::name[] =\n" +
+                "const char " + clsName + "::name[] =\n" +
                 "        \"" + cls.getName().replace('.', '/') + "\";\n" +
                 "\n");
 
         natives.append(
                 "template<class Impl>\n" +
                 "class " + clsName + "::Natives : " +
                         "public mozilla::jni::NativeImpl<" + unqualifiedName + ", Impl>\n" +
                 "{\n" +
--- a/devtools/client/locales/en-US/markers.properties
+++ b/devtools/client/locales/en-US/markers.properties
@@ -98,17 +98,16 @@ marker.value.DOMEventTargetPhase=Target
 marker.value.DOMEventCapturingPhase=Capture
 marker.value.DOMEventBubblingPhase=Bubbling
 
 # LOCALIZATION NOTE (marker.gcreason.label.*):
 # These strings are used to give a concise but readable description of a GC reason.
 marker.gcreason.label.API=API Call
 marker.gcreason.label.EAGER_ALLOC_TRIGGER=Eager Allocation Trigger
 marker.gcreason.label.DESTROY_RUNTIME=Shutdown
-marker.gcreason.label.DESTROY_CONTEXT=Shutdown
 marker.gcreason.label.LAST_DITCH=Out of Memory
 marker.gcreason.label.TOO_MUCH_MALLOC=Too Many Bytes Allocated
 marker.gcreason.label.ALLOC_TRIGGER=Too Many Allocations
 marker.gcreason.label.DEBUG_GC=Debug GC
 marker.gcreason.label.COMPARTMENT_REVIVED=Dead Global Revived
 marker.gcreason.label.RESET=Finish Incremental Cycle
 marker.gcreason.label.OUT_OF_NURSERY=Nursery is Full
 marker.gcreason.label.EVICT_NURSERY=Nursery Eviction
@@ -138,17 +137,16 @@ marker.gcreason.label.USER_INACTIVE=User
 # The name of a nursery collection.
 marker.nurseryCollection=Nursery Collection
 
 # LOCALIZATION NOTE (marker.gcreason.description.*):
 # These strings are used to give an expanded description of why a GC occurred.
 marker.gcreason.description.API=There was an API call to force garbage collection.
 marker.gcreason.description.EAGER_ALLOC_TRIGGER=JavaScript returned to the event loop and there were enough bytes allocated since the last GC that a new GC cycle was triggered.
 marker.gcreason.description.DESTROY_RUNTIME=Firefox destroyed a JavaScript runtime or context, and this was the final garbage collection before shutting down.
-marker.gcreason.description.DESTROY_CONTEXT=Firefox destroyed a JavaScript runtime or context, and this was the final garbage collection before shutting down.
 marker.gcreason.description.LAST_DITCH=JavaScript attempted to allocate, but there was no memory available. Doing a full compacting garbage collection as an attempt to free up memory for the allocation.
 marker.gcreason.description.TOO_MUCH_MALLOC=JavaScript allocated too many bytes, and forced a garbage collection.
 marker.gcreason.description.ALLOC_TRIGGER=JavaScript allocated too many times, and forced a garbage collection.
 marker.gcreason.description.DEBUG_GC=GC due to Zeal debug settings.
 marker.gcreason.description.COMPARTMENT_REVIVED=A global object that was thought to be dead at the start of the GC cycle was revived by the end of the GC cycle.
 marker.gcreason.description.RESET=The active incremental GC cycle was forced to finish immediately.
 marker.gcreason.description.OUT_OF_NURSERY=JavaScript allocated enough new objects in the nursery that it became full and triggered a minor GC.
 marker.gcreason.description.EVICT_NURSERY=Work needed to be done on the tenured heap, requiring the nursery to be empty.
--- a/dom/animation/KeyframeEffect.cpp
+++ b/dom/animation/KeyframeEffect.cpp
@@ -10,21 +10,23 @@
 #include "mozilla/dom/KeyframeEffectBinding.h"
 #include "mozilla/AnimationUtils.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/LookAndFeel.h" // For LookAndFeel::GetInt
 #include "mozilla/KeyframeUtils.h"
 #include "mozilla/StyleAnimationValue.h"
 #include "Layers.h" // For Layer
 #include "nsComputedDOMStyle.h" // nsComputedDOMStyle::GetStyleContextForElement
+#include "nsContentUtils.h"  // nsContentUtils::ReportToConsole
 #include "nsCSSPropertySet.h"
 #include "nsCSSProps.h" // For nsCSSProps::PropHasFlags
 #include "nsCSSPseudoElements.h" // For CSSPseudoElementType
 #include "nsDOMMutationObserver.h" // For nsAutoAnimationMutationBatch
 #include "nsIPresShell.h" // For nsIPresShell
+#include "nsIScriptError.h"
 
 namespace mozilla {
 
 // Helper functions for generating a ComputedTimingProperties dictionary
 static void
 GetComputedTimingDictionary(const ComputedTiming& aComputedTiming,
                             const Nullable<TimeDuration>& aLocalTime,
                             const TimingParams& aTiming,
@@ -72,30 +74,34 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_
 NS_INTERFACE_MAP_END_INHERITING(AnimationEffectReadOnly)
 
 NS_IMPL_ADDREF_INHERITED(KeyframeEffectReadOnly, AnimationEffectReadOnly)
 NS_IMPL_RELEASE_INHERITED(KeyframeEffectReadOnly, AnimationEffectReadOnly)
 
 KeyframeEffectReadOnly::KeyframeEffectReadOnly(
   nsIDocument* aDocument,
   const Maybe<OwningAnimationTarget>& aTarget,
-  const TimingParams& aTiming)
+  const TimingParams& aTiming,
+  const KeyframeEffectParams& aOptions)
   : KeyframeEffectReadOnly(aDocument, aTarget,
                            new AnimationEffectTimingReadOnly(aDocument,
-                                                             aTiming))
+                                                             aTiming),
+                           aOptions)
 {
 }
 
 KeyframeEffectReadOnly::KeyframeEffectReadOnly(
   nsIDocument* aDocument,
   const Maybe<OwningAnimationTarget>& aTarget,
-  AnimationEffectTimingReadOnly* aTiming)
+  AnimationEffectTimingReadOnly* aTiming,
+  const KeyframeEffectParams& aOptions)
   : AnimationEffectReadOnly(aDocument)
   , mTarget(aTarget)
   , mTiming(aTiming)
+  , mEffectOptions(aOptions)
   , mInEffectOnLastAnimationTimingUpdate(false)
   , mCumulativeChangeHint(nsChangeHint(0))
 {
   MOZ_ASSERT(aTiming);
 }
 
 JSObject*
 KeyframeEffectReadOnly::WrapObject(JSContext* aCx,
@@ -437,39 +443,33 @@ KeyframeEffectReadOnly::SetKeyframes(JSC
   }
 
   nsTArray<Keyframe> keyframes =
     KeyframeUtils::GetKeyframesFromObject(aContext, aKeyframes, aRv);
   if (aRv.Failed()) {
     return;
   }
 
-  RefPtr<nsStyleContext> styleContext;
-  nsIPresShell* shell = doc->GetShell();
-  if (shell && mTarget) {
-    nsIAtom* pseudo =
-      mTarget->mPseudoType < CSSPseudoElementType::Count ?
-      nsCSSPseudoElements::GetPseudoAtom(mTarget->mPseudoType) : nullptr;
-    styleContext =
-      nsComputedDOMStyle::GetStyleContextForElement(mTarget->mElement,
-                                                    pseudo, shell);
-  }
-
+  RefPtr<nsStyleContext> styleContext = GetTargetStyleContext(doc);
   SetKeyframes(Move(keyframes), styleContext);
 }
 
 void
 KeyframeEffectReadOnly::SetKeyframes(nsTArray<Keyframe>&& aKeyframes,
                                   nsStyleContext* aStyleContext)
 {
   if (KeyframesEqualIgnoringComputedOffsets(aKeyframes, mKeyframes)) {
     return;
   }
 
   mKeyframes = Move(aKeyframes);
+  // Apply distribute spacing irrespective of the spacing mode. We will apply
+  // the specified spacing mode when we generate computed animation property
+  // values from the keyframes since both operations require a style context
+  // and need to be performed whenever the style context changes.
   KeyframeUtils::ApplyDistributeSpacing(mKeyframes);
 
   if (mAnimation && mAnimation->IsRelevant()) {
     nsNodeUtils::AnimationChanged(mAnimation);
   }
 
   if (aStyleContext) {
     UpdateProperties(aStyleContext);
@@ -507,21 +507,30 @@ KeyframeEffectReadOnly::HasAnimationOfPr
 
 void
 KeyframeEffectReadOnly::UpdateProperties(nsStyleContext* aStyleContext)
 {
   MOZ_ASSERT(aStyleContext);
 
   nsTArray<AnimationProperty> properties;
   if (mTarget) {
+    nsTArray<ComputedKeyframeValues> computedValues =
+      KeyframeUtils::GetComputedKeyframeValues(mKeyframes, mTarget->mElement,
+                                               aStyleContext);
+
+    if (mEffectOptions.mSpacingMode == SpacingMode::paced) {
+      KeyframeUtils::ApplySpacing(mKeyframes, SpacingMode::paced,
+                                  mEffectOptions.mPacedProperty,
+                                  computedValues);
+    }
+
     properties =
-      KeyframeUtils::GetAnimationPropertiesFromKeyframes(aStyleContext,
-                                                         mTarget->mElement,
-                                                         mTarget->mPseudoType,
-                                                         mKeyframes);
+      KeyframeUtils::GetAnimationPropertiesFromKeyframes(mKeyframes,
+                                                         computedValues,
+                                                         aStyleContext);
   }
 
   if (mProperties == properties) {
     return;
   }
 
   // Preserve the state of mWinsInCascade and mIsRunningOnCompositor flags.
   nsCSSPropertySet winningInCascadeProperties;
@@ -701,16 +710,51 @@ KeyframeEffectReadOnly::ResetIsRunningOn
     property.mIsRunningOnCompositor = false;
   }
 }
 
 KeyframeEffectReadOnly::~KeyframeEffectReadOnly()
 {
 }
 
+static const KeyframeEffectOptions&
+KeyframeEffectOptionsFromUnion(
+  const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions)
+{
+  MOZ_ASSERT(aOptions.IsKeyframeEffectOptions());
+  return aOptions.GetAsKeyframeEffectOptions();
+}
+
+static const KeyframeEffectOptions&
+KeyframeEffectOptionsFromUnion(
+  const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions)
+{
+  MOZ_ASSERT(aOptions.IsKeyframeAnimationOptions());
+  return aOptions.GetAsKeyframeAnimationOptions();
+}
+
+template <class OptionsType>
+static KeyframeEffectParams
+KeyframeEffectParamsFromUnion(const OptionsType& aOptions,
+                              nsAString& aInvalidPacedProperty,
+                              ErrorResult& aRv)
+{
+  KeyframeEffectParams result;
+  if (!aOptions.IsUnrestrictedDouble()) {
+    const KeyframeEffectOptions& options =
+      KeyframeEffectOptionsFromUnion(aOptions);
+    KeyframeEffectParams::ParseSpacing(options.mSpacing,
+                                       result.mSpacingMode,
+                                       result.mPacedProperty,
+                                       aInvalidPacedProperty,
+                                       aRv);
+  }
+  return result;
+}
+
 static Maybe<OwningAnimationTarget>
 ConvertTarget(const Nullable<ElementOrCSSPseudoElement>& aTarget)
 {
   // Return value optimization.
   Maybe<OwningAnimationTarget> result;
 
   if (aTarget.IsNull()) {
     return result;
@@ -745,19 +789,36 @@ KeyframeEffectReadOnly::ConstructKeyfram
   }
 
   TimingParams timingParams =
     TimingParams::FromOptionsUnion(aOptions, doc, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
 
+  nsAutoString invalidPacedProperty;
+  KeyframeEffectParams effectOptions =
+    KeyframeEffectParamsFromUnion(aOptions, invalidPacedProperty, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  if (!invalidPacedProperty.IsEmpty()) {
+    const char16_t* params[] = { invalidPacedProperty.get() };
+    nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
+                                    NS_LITERAL_CSTRING("Animation"),
+                                    doc,
+                                    nsContentUtils::eDOM_PROPERTIES,
+                                    "UnanimatablePacedProperty",
+                                    params, ArrayLength(params));
+  }
+
   Maybe<OwningAnimationTarget> target = ConvertTarget(aTarget);
   RefPtr<KeyframeEffectType> effect =
-    new KeyframeEffectType(doc, target, timingParams);
+    new KeyframeEffectType(doc, target, timingParams, effectOptions);
 
   effect->SetKeyframes(aGlobal.Context(), aKeyframes, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
 
   return effect.forget();
 }
@@ -815,16 +876,38 @@ KeyframeEffectReadOnly::RequestRestyle(
   nsPresContext* presContext = GetPresContext();
   if (presContext && mTarget && mAnimation) {
     presContext->EffectCompositor()->
       RequestRestyle(mTarget->mElement, mTarget->mPseudoType,
                      aRestyleType, mAnimation->CascadeLevel());
   }
 }
 
+already_AddRefed<nsStyleContext>
+KeyframeEffectReadOnly::GetTargetStyleContext(nsIDocument* aDoc)
+{
+  if (!mTarget) {
+    return nullptr;
+  }
+
+  if (!aDoc) {
+    aDoc = mTarget->mElement->OwnerDoc();
+    if (!aDoc) {
+      return nullptr;
+    }
+  }
+
+  nsIAtom* pseudo = mTarget->mPseudoType < CSSPseudoElementType::Count
+                    ? nsCSSPseudoElements::GetPseudoAtom(mTarget->mPseudoType)
+                    : nullptr;
+  return nsComputedDOMStyle::GetStyleContextForElement(mTarget->mElement,
+                                                       pseudo,
+                                                       aDoc->GetShell());
+}
+
 #ifdef DEBUG
 void
 DumpAnimationProperties(nsTArray<AnimationProperty>& aAnimationProperties)
 {
   for (auto& p : aAnimationProperties) {
     printf("%s\n", nsCSSProps::GetStringValue(p.mProperty).get());
     for (auto& s : p.mSegments) {
       nsString fromValue, toValue;
@@ -983,16 +1066,18 @@ KeyframeEffectReadOnly::GetKeyframes(JSC
   }
 
   for (const Keyframe& keyframe : mKeyframes) {
     // Set up a dictionary object for the explicit members
     BaseComputedKeyframe keyframeDict;
     if (keyframe.mOffset) {
       keyframeDict.mOffset.SetValue(keyframe.mOffset.value());
     }
+    MOZ_ASSERT(keyframe.mComputedOffset != Keyframe::kComputedOffsetNotSet,
+               "Invalid computed offset");
     keyframeDict.mComputedOffset.Construct(keyframe.mComputedOffset);
     if (keyframe.mTimingFunction) {
       keyframeDict.mEasing.Truncate();
       keyframe.mTimingFunction.ref().AppendToString(keyframeDict.mEasing);
     } // else if null, leave easing as its default "linear".
 
     JS::Rooted<JS::Value> keyframeJSValue(aCx);
     if (!ToJSValue(aCx, keyframeDict, &keyframeJSValue)) {
@@ -1362,19 +1447,21 @@ KeyframeEffectReadOnly::CanIgnoreIfNotVi
 //---------------------------------------------------------------------
 //
 // KeyframeEffect
 //
 //---------------------------------------------------------------------
 
 KeyframeEffect::KeyframeEffect(nsIDocument* aDocument,
                                const Maybe<OwningAnimationTarget>& aTarget,
-                               const TimingParams& aTiming)
+                               const TimingParams& aTiming,
+                               const KeyframeEffectParams& aOptions)
   : KeyframeEffectReadOnly(aDocument, aTarget,
-                           new AnimationEffectTiming(aDocument, aTiming, this))
+                           new AnimationEffectTiming(aDocument, aTiming, this),
+                           aOptions)
 {
 }
 
 JSObject*
 KeyframeEffect::WrapObject(JSContext* aCx,
                            JS::Handle<JSObject*> aGivenProto)
 {
   return KeyframeEffectBinding::Wrap(aCx, this, aGivenProto);
@@ -1444,55 +1531,38 @@ KeyframeEffect::SetTarget(const Nullable
       nsNodeUtils::AnimationRemoved(mAnimation);
     }
   }
 
   mTarget = newTarget;
 
   if (mTarget) {
     UpdateTargetRegistration();
-    MaybeUpdateProperties();
+    RefPtr<nsStyleContext> styleContext = GetTargetStyleContext();
+    if (styleContext) {
+      UpdateProperties(styleContext);
+    } else if (mEffectOptions.mSpacingMode == SpacingMode::paced) {
+      KeyframeUtils::ApplyDistributeSpacing(mKeyframes);
+    }
 
     RequestRestyle(EffectCompositor::RestyleType::Layer);
 
     nsAutoAnimationMutationBatch mb(mTarget->mElement->OwnerDoc());
     if (mAnimation) {
       nsNodeUtils::AnimationAdded(mAnimation);
     }
+  } else if (mEffectOptions.mSpacingMode == SpacingMode::paced) {
+    // New target is null, so fall back to distribute spacing.
+    KeyframeUtils::ApplyDistributeSpacing(mKeyframes);
   }
 }
 
 KeyframeEffect::~KeyframeEffect()
 {
   // mTiming is cycle collected, so we have to do null check first even though
   // mTiming shouldn't be null during the lifetime of KeyframeEffect.
   if (mTiming) {
     mTiming->Unlink();
   }
 }
 
-void
-KeyframeEffect::MaybeUpdateProperties()
-{
-  if (!mTarget) {
-    return;
-  }
-
-  nsIDocument* doc = mTarget->mElement->OwnerDoc();
-  if (!doc) {
-    return;
-  }
-
-  nsIAtom* pseudo = mTarget->mPseudoType < CSSPseudoElementType::Count ?
-                    nsCSSPseudoElements::GetPseudoAtom(mTarget->mPseudoType) :
-                    nullptr;
-  RefPtr<nsStyleContext> styleContext =
-    nsComputedDOMStyle::GetStyleContextForElement(mTarget->mElement, pseudo,
-                                                  doc->GetShell());
-  if (!styleContext) {
-    return;
-  }
-
-  UpdateProperties(styleContext);
-}
-
 } // namespace dom
 } // namespace mozilla
--- a/dom/animation/KeyframeEffect.h
+++ b/dom/animation/KeyframeEffect.h
@@ -14,19 +14,19 @@
 #include "nsTArray.h"
 #include "nsWrapperCache.h"
 #include "mozilla/AnimationPerformanceWarning.h"
 #include "mozilla/AnimationTarget.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ComputedTiming.h"
 #include "mozilla/ComputedTimingFunction.h"
 #include "mozilla/EffectCompositor.h"
+#include "mozilla/KeyframeEffectParams.h"
 #include "mozilla/LayerAnimationInfo.h" // LayerAnimations::kRecords
 #include "mozilla/Maybe.h"
-#include "mozilla/OwningNonNull.h"      // OwningNonNull<...>
 #include "mozilla/StickyTimeDuration.h"
 #include "mozilla/StyleAnimationValue.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/TimingParams.h"
 #include "mozilla/dom/AnimationEffectReadOnly.h"
 #include "mozilla/dom/AnimationEffectTimingReadOnly.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/Nullable.h"
@@ -102,17 +102,18 @@ struct Keyframe
     mOffset         = aOther.mOffset;
     mComputedOffset = aOther.mComputedOffset;
     mTimingFunction = Move(aOther.mTimingFunction);
     mPropertyValues = Move(aOther.mPropertyValues);
     return *this;
   }
 
   Maybe<double>                 mOffset;
-  double                        mComputedOffset = 0.0;
+  static MOZ_CONSTEXPR_VAR double kComputedOffsetNotSet = -1.0;
+  double                        mComputedOffset = kComputedOffsetNotSet;
   Maybe<ComputedTimingFunction> mTimingFunction; // Nothing() here means
                                                  // "linear"
   nsTArray<PropertyValuePair>   mPropertyValues;
 };
 
 struct AnimationPropertySegment
 {
   float mFromKey, mToKey;
@@ -191,17 +192,18 @@ namespace dom {
 
 class Animation;
 
 class KeyframeEffectReadOnly : public AnimationEffectReadOnly
 {
 public:
   KeyframeEffectReadOnly(nsIDocument* aDocument,
                          const Maybe<OwningAnimationTarget>& aTarget,
-                         const TimingParams& aTiming);
+                         const TimingParams& aTiming,
+                         const KeyframeEffectParams& aOptions);
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(KeyframeEffectReadOnly,
                                                         AnimationEffectReadOnly)
 
   virtual JSObject* WrapObject(JSContext* aCx,
                                JS::Handle<JSObject*> aGivenProto) override;
 
@@ -231,18 +233,19 @@ public:
   void GetKeyframes(JSContext*& aCx,
                     nsTArray<JSObject*>& aResult,
                     ErrorResult& aRv);
   void GetProperties(nsTArray<AnimationPropertyDetails>& aProperties,
                      ErrorResult& aRv) const;
 
   IterationCompositeOperation IterationComposite() const;
   CompositeOperation Composite() const;
-  void GetSpacing(nsString& aRetVal) const {
-    aRetVal.AssignLiteral("distribute");
+  void GetSpacing(nsString& aRetVal) const
+  {
+    mEffectOptions.GetSpacingAsString(aRetVal);
   }
 
   already_AddRefed<AnimationEffectTimingReadOnly> Timing() const override;
 
   const TimingParams& SpecifiedTiming() const
   {
     return mTiming->AsTimingParams();
   }
@@ -350,17 +353,18 @@ public:
   // can ignore painting if the animation is not visible.
   // See nsChangeHint_Hints_CanIgnoreIfNotVisible in nsChangeHint.h
   // in detail which change hint can be ignored.
   bool CanIgnoreIfNotVisible() const;
 
 protected:
   KeyframeEffectReadOnly(nsIDocument* aDocument,
                          const Maybe<OwningAnimationTarget>& aTarget,
-                         AnimationEffectTimingReadOnly* aTiming);
+                         AnimationEffectTimingReadOnly* aTiming,
+                         const KeyframeEffectParams& aOptions);
 
   virtual ~KeyframeEffectReadOnly();
 
   template<class KeyframeEffectType, class OptionsType>
   static already_AddRefed<KeyframeEffectType>
   ConstructKeyframeEffect(const GlobalObject& aGlobal,
                           const Nullable<ElementOrCSSPseudoElement>& aTarget,
                           JS::Handle<JSObject*> aKeyframes,
@@ -380,20 +384,30 @@ protected:
   // owning Animation's timing.
   void UpdateTargetRegistration();
 
   // Remove the current effect target from its EffectSet.
   void UnregisterTarget();
 
   void RequestRestyle(EffectCompositor::RestyleType aRestyleType);
 
+  // Looks up the style context associated with the target element, if any.
+  // We need to be careful to *not* call this when we are updating the style
+  // context. That's because calling GetStyleContextForElement when we are in
+  // the process of building a style context may trigger various forms of
+  // infinite recursion.
+  // If aDoc is nullptr, we will use the owner doc of the target element.
+  already_AddRefed<nsStyleContext>
+  GetTargetStyleContext(nsIDocument* aDoc = nullptr);
+
   Maybe<OwningAnimationTarget> mTarget;
   RefPtr<Animation> mAnimation;
 
   RefPtr<AnimationEffectTimingReadOnly> mTiming;
+  KeyframeEffectParams mEffectOptions;
 
   // The specified keyframes.
   nsTArray<Keyframe>          mKeyframes;
 
   // A set of per-property value arrays, derived from |mKeyframes|.
   nsTArray<AnimationProperty> mProperties;
 
   // The computed progress last time we composed the style rule. This is
@@ -424,17 +438,18 @@ private:
   static const TimeDuration OverflowRegionRefreshInterval();
 };
 
 class KeyframeEffect : public KeyframeEffectReadOnly
 {
 public:
   KeyframeEffect(nsIDocument* aDocument,
                  const Maybe<OwningAnimationTarget>& aTarget,
-                 const TimingParams& aTiming);
+                 const TimingParams& aTiming,
+                 const KeyframeEffectParams& aOptions);
 
   JSObject* WrapObject(JSContext* aCx,
                        JS::Handle<JSObject*> aGivenProto) override;
 
   static already_AddRefed<KeyframeEffect>
   Constructor(const GlobalObject& aGlobal,
               const Nullable<ElementOrCSSPseudoElement>& aTarget,
               JS::Handle<JSObject*> aKeyframes,
@@ -448,29 +463,23 @@ public:
   Constructor(const GlobalObject& aGlobal,
               const Nullable<ElementOrCSSPseudoElement>& aTarget,
               JS::Handle<JSObject*> aKeyframes,
               const UnrestrictedDoubleOrKeyframeAnimationOptions& aOptions,
               ErrorResult& aRv);
 
   void NotifySpecifiedTimingUpdated();
 
-  // This method calls MaybeUpdateProperties which is not safe to use when
+  // This method calls GetTargetStyleContext which is not safe to use when
   // we are in the middle of updating style. If we need to use this when
   // updating style, we should pass the nsStyleContext into this method and use
   // that to update the properties rather than calling
   // GetStyleContextForElement.
   void SetTarget(const Nullable<ElementOrCSSPseudoElement>& aTarget);
 
 protected:
   ~KeyframeEffect() override;
-
-  // We need to be careful to *not* call this when we are updating the style
-  // context. That's because calling GetStyleContextForElement when we are in
-  // the process of building a style context may trigger various forms of
-  // infinite recursion.
-  void MaybeUpdateProperties();
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_KeyframeEffect_h
new file mode 100644
--- /dev/null
+++ b/dom/animation/KeyframeEffectParams.cpp
@@ -0,0 +1,160 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/KeyframeEffectParams.h"
+
+#include "mozilla/KeyframeUtils.h"
+#include "mozilla/RangedPtr.h"
+#include "nsReadableUtils.h"
+
+namespace mozilla {
+
+static inline bool
+IsLetter(char16_t aCh)
+{
+  return (0x41 <= aCh && aCh <= 0x5A) || (0x61 <= aCh && aCh <= 0x7A);
+}
+
+static inline bool
+IsDigit(char16_t aCh)
+{
+  return 0x30 <= aCh && aCh <= 0x39;
+}
+
+static inline bool
+IsNameStartCode(char16_t aCh)
+{
+  return IsLetter(aCh) || aCh >= 0x80 || aCh == '_';
+}
+
+static inline bool
+IsNameCode(char16_t aCh)
+{
+  return IsNameStartCode(aCh) || IsDigit(aCh) || aCh == '-';
+}
+
+static inline bool
+IsNewLine(char16_t aCh)
+{
+  // 0x0A (LF), 0x0C (FF), 0x0D (CR), or pairs of CR followed by LF are
+  // replaced by LF.
+  return aCh == 0x0A || aCh == 0x0C || aCh == 0x0D;
+}
+
+static inline bool
+IsValidEscape(char16_t aFirst, char16_t aSecond)
+{
+  return aFirst == '\\' && !IsNewLine(aSecond);
+}
+
+static bool
+IsIdentStart(RangedPtr<const char16_t> aIter,
+             const char16_t* const aEnd)
+{
+  if (aIter == aEnd) {
+    return false;
+  }
+
+  if (*aIter == '-') {
+    if (aIter + 1 == aEnd) {
+      return false;
+    }
+    char16_t second = *(aIter + 1);
+    return IsNameStartCode(second) ||
+           second == '-' ||
+           (aIter + 2 != aEnd && IsValidEscape(second, *(aIter + 2)));
+  }
+  return IsNameStartCode(*aIter) ||
+         (aIter + 1 != aEnd && IsValidEscape(*aIter, *(aIter + 1)));
+}
+
+static void
+ConsumeIdentToken(RangedPtr<const char16_t>& aIter,
+                  const char16_t* const aEnd,
+                  nsAString& aResult)
+{
+  aResult.Truncate();
+
+  // Check if it starts with an identifier.
+  if (!IsIdentStart(aIter, aEnd)) {
+    return;
+  }
+
+  // Start to consume.
+  while (aIter != aEnd) {
+    if (IsNameCode(*aIter)) {
+      aResult.Append(*aIter);
+    } else if (*aIter == '\\') {
+      const RangedPtr<const char16_t> secondChar = aIter + 1;
+      if (secondChar == aEnd || !IsValidEscape(*aIter, *secondChar)) {
+        break;
+      }
+      // Consume '\\' and append the character following this '\\'.
+      ++aIter;
+      aResult.Append(*aIter);
+    } else {
+      break;
+    }
+    ++aIter;
+  }
+}
+
+/* static */ void
+KeyframeEffectParams::ParseSpacing(const nsAString& aSpacing,
+                                   SpacingMode& aSpacingMode,
+                                   nsCSSProperty& aPacedProperty,
+                                   nsAString& aInvalidPacedProperty,
+                                   ErrorResult& aRv)
+{
+  aInvalidPacedProperty.Truncate();
+
+  // Parse spacing.
+  // distribute | paced({ident})
+  // https://w3c.github.io/web-animations/#dom-keyframeeffectreadonly-spacing
+  // 1. distribute spacing.
+  if (aSpacing.EqualsLiteral("distribute")) {
+    aSpacingMode = SpacingMode::distribute;
+    return;
+  }
+
+  // 2. paced spacing.
+  static const nsLiteralString kPacedPrefix = NS_LITERAL_STRING("paced(");
+  if (!StringBeginsWith(aSpacing, kPacedPrefix)) {
+    aRv.ThrowTypeError<dom::MSG_INVALID_SPACING_MODE_ERROR>(aSpacing);
+    return;
+  }
+
+  RangedPtr<const char16_t> iter(aSpacing.Data() + kPacedPrefix.Length(),
+                                 aSpacing.Data(), aSpacing.Length());
+  const char16_t* const end = aSpacing.EndReading();
+
+  nsAutoString identToken;
+  ConsumeIdentToken(iter, end, identToken);
+  if (identToken.IsEmpty()) {
+    aRv.ThrowTypeError<dom::MSG_INVALID_SPACING_MODE_ERROR>(aSpacing);
+    return;
+  }
+
+  aPacedProperty =
+    nsCSSProps::LookupProperty(identToken, CSSEnabledState::eForAllContent);
+  if (aPacedProperty == eCSSProperty_UNKNOWN ||
+      aPacedProperty == eCSSPropertyExtra_variable ||
+      !KeyframeUtils::IsAnimatableProperty(aPacedProperty)) {
+    aPacedProperty = eCSSProperty_UNKNOWN;
+    aInvalidPacedProperty = identToken;
+  }
+
+  if (end - iter.get() != 1 || *iter != ')') {
+    aRv.ThrowTypeError<dom::MSG_INVALID_SPACING_MODE_ERROR>(aSpacing);
+    return;
+  }
+
+  aSpacingMode = aPacedProperty == eCSSProperty_UNKNOWN
+                 ? SpacingMode::distribute
+                 : SpacingMode::paced;
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/animation/KeyframeEffectParams.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_KeyframeEffectParams_h
+#define mozilla_KeyframeEffectParams_h
+
+#include "nsCSSProps.h"
+#include "nsString.h"
+
+namespace mozilla {
+
+enum class SpacingMode
+{
+  distribute,
+  paced
+};
+
+struct KeyframeEffectParams
+{
+  void GetSpacingAsString(nsAString& aSpacing) const
+  {
+    if (mSpacingMode == SpacingMode::distribute) {
+      aSpacing.AssignLiteral("distribute");
+    } else {
+      aSpacing.AssignLiteral("paced(");
+      aSpacing.AppendASCII(nsCSSProps::GetStringValue(mPacedProperty).get());
+      aSpacing.AppendLiteral(")");
+    }
+  }
+
+  /**
+   * Parse spacing string.
+   *
+   * @param aSpacing The input spacing string.
+   * @param [out] aSpacingMode The parsed spacing mode.
+   * @param [out] aPacedProperty The parsed CSS property if using paced spacing.
+   * @param [out] aInvalidPacedProperty A string that, if we parsed a string of
+   *                                    the form 'paced(<ident>)' where <ident>
+   *                                    is not a recognized animatable property,
+   *                                    will be set to <ident>.
+   * @param [out] aRv The error result.
+   */
+  static void ParseSpacing(const nsAString& aSpacing,
+                           SpacingMode& aSpacingMode,
+                           nsCSSProperty& aPacedProperty,
+                           nsAString& aInvalidPacedProperty,
+                           ErrorResult& aRv);
+
+  // FIXME: Bug 1216843: Add IterationCompositeOperations and
+  //        Bug 1216844: Add CompositeOperation
+  SpacingMode mSpacingMode = SpacingMode::distribute;
+  nsCSSProperty mPacedProperty = eCSSProperty_UNKNOWN;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_KeyframeEffectParams_h
--- a/dom/animation/KeyframeUtils.cpp
+++ b/dom/animation/KeyframeUtils.cpp
@@ -3,16 +3,17 @@
  * 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/KeyframeUtils.h"
 
 #include "mozilla/AnimationUtils.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/Move.h"
+#include "mozilla/StyleAnimationValue.h"
 #include "mozilla/TimingParams.h"
 #include "mozilla/dom/BaseKeyframeTypesBinding.h" // For FastBaseKeyframe etc.
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/KeyframeEffect.h"
 #include "mozilla/dom/KeyframeEffectBinding.h"
 #include "jsapi.h" // For ForOfIterator etc.
 #include "nsClassHashtable.h"
 #include "nsCSSParser.h"
@@ -24,16 +25,20 @@
 namespace mozilla {
 
 // ------------------------------------------------------------------
 //
 // Internal data types
 //
 // ------------------------------------------------------------------
 
+// This is used while calculating paced spacing. If the keyframe is not pacable,
+// we set its cumulative distance to kNotPaceable, so we can use this to check.
+const double kNotPaceable = -1.0;
+
 // For the aAllowList parameter of AppendStringOrStringSequence and
 // GetPropertyValuesPairs.
 enum class ListAllowance { eDisallow, eAllow };
 
 /**
  * A comparator to sort nsCSSProperty values such that longhands are sorted
  * before shorthands, and shorthands with less components are sorted before
  * shorthands with more components.
@@ -347,19 +352,16 @@ ConvertKeyframeSequence(JSContext* aCx,
 
 static bool
 GetPropertyValuesPairs(JSContext* aCx,
                        JS::Handle<JSObject*> aObject,
                        ListAllowance aAllowLists,
                        nsTArray<PropertyValuesPair>& aResult);
 
 static bool
-IsAnimatableProperty(nsCSSProperty aProperty);
-
-static bool
 AppendStringOrStringSequenceToArray(JSContext* aCx,
                                     JS::Handle<JS::Value> aValue,
                                     ListAllowance aAllowLists,
                                     nsTArray<nsString>& aValues);
 
 static bool
 AppendValueAsString(JSContext* aCx,
                     nsTArray<nsString>& aValues,
@@ -368,30 +370,50 @@ AppendValueAsString(JSContext* aCx,
 static PropertyValuePair
 MakePropertyValuePair(nsCSSProperty aProperty, const nsAString& aStringValue,
                       nsCSSParser& aParser, nsIDocument* aDocument);
 
 static bool
 HasValidOffsets(const nsTArray<Keyframe>& aKeyframes);
 
 static void
+MarkAsComputeValuesFailureKey(PropertyValuePair& aPair);
+
+static bool
+IsComputeValuesFailureKey(const PropertyValuePair& aPair);
+
+static void
 BuildSegmentsFromValueEntries(nsStyleContext* aStyleContext,
                               nsTArray<KeyframeValueEntry>& aEntries,
                               nsTArray<AnimationProperty>& aResult);
 
 static void
 GetKeyframeListFromPropertyIndexedKeyframe(JSContext* aCx,
                                            JS::Handle<JS::Value> aValue,
                                            nsTArray<Keyframe>& aResult,
                                            ErrorResult& aRv);
 
 static bool
 RequiresAdditiveAnimation(const nsTArray<Keyframe>& aKeyframes,
                           nsIDocument* aDocument);
 
+static void
+DistributeRange(const Range<Keyframe>& aSpacingRange,
+                const Range<Keyframe>& aRangeToAdjust);
+
+static void
+DistributeRange(const Range<Keyframe>& aSpacingRange);
+
+static void
+PaceRange(const Range<Keyframe>& aKeyframes,
+          const Range<double>& aCumulativeDistances);
+
+static nsTArray<double>
+GetCumulativeDistances(const nsTArray<ComputedKeyframeValues>& aValues,
+                       nsCSSProperty aProperty);
 
 // ------------------------------------------------------------------
 //
 // Public API
 //
 // ------------------------------------------------------------------
 
 /* static */ nsTArray<Keyframe>
@@ -448,82 +470,163 @@ KeyframeUtils::GetKeyframesFromObject(JS
     aRv.Throw(NS_ERROR_DOM_ANIM_MISSING_PROPS_ERR);
     keyframes.Clear();
   }
 
   return keyframes;
 }
 
 /* static */ void
-KeyframeUtils::ApplyDistributeSpacing(nsTArray<Keyframe>& aKeyframes)
+KeyframeUtils::ApplySpacing(nsTArray<Keyframe>& aKeyframes,
+                            SpacingMode aSpacingMode,
+                            nsCSSProperty aProperty,
+                            nsTArray<ComputedKeyframeValues>& aComputedValues)
 {
   if (aKeyframes.IsEmpty()) {
     return;
   }
 
-  // If the first or last keyframes have an unspecified offset,
-  // fill them in with 0% and 100%.  If there is only a single keyframe,
-  // then it gets 100%.
-  Keyframe& lastElement = aKeyframes.LastElement();
-  lastElement.mComputedOffset = lastElement.mOffset.valueOr(1.0);
+  nsTArray<double> cumulativeDistances;
+  if (aSpacingMode == SpacingMode::paced) {
+    MOZ_ASSERT(IsAnimatableProperty(aProperty),
+               "Paced property should be animatable");
+
+    cumulativeDistances = GetCumulativeDistances(aComputedValues, aProperty);
+    // Reset the computed offsets if using paced spacing.
+    for (Keyframe& keyframe : aKeyframes) {
+      keyframe.mComputedOffset = Keyframe::kComputedOffsetNotSet;
+    }
+  }
+
+  // If the first keyframe has an unspecified offset, fill it in with 0%.
+  // If there is only a single keyframe, then it gets 100%.
   if (aKeyframes.Length() > 1) {
     Keyframe& firstElement = aKeyframes[0];
     firstElement.mComputedOffset = firstElement.mOffset.valueOr(0.0);
+    // We will fill in the last keyframe's offset below
+  } else {
+    Keyframe& lastElement = aKeyframes.LastElement();
+    lastElement.mComputedOffset = lastElement.mOffset.valueOr(1.0);
   }
 
   // Fill in remaining missing offsets.
-  size_t i = 0;
-  while (i < aKeyframes.Length() - 1) {
-    double start = aKeyframes[i].mComputedOffset;
-    size_t j = i + 1;
-    while (aKeyframes[j].mOffset.isNothing() && j < aKeyframes.Length() - 1) {
-      ++j;
+  const Keyframe* const last = aKeyframes.cend() - 1;
+  const RangedPtr<Keyframe> begin(aKeyframes.begin(), aKeyframes.Length());
+  RangedPtr<Keyframe> keyframeA = begin;
+  while (keyframeA != last) {
+    // Find keyframe A and keyframe B *between* which we will apply spacing.
+    RangedPtr<Keyframe> keyframeB = keyframeA + 1;
+    while (keyframeB->mOffset.isNothing() && keyframeB != last) {
+      ++keyframeB;
     }
-    double end = aKeyframes[j].mOffset.valueOr(1.0);
-    size_t n = j - i;
-    for (size_t k = 1; k < n; ++k) {
-      double offset = start + double(k) / n * (end - start);
-      aKeyframes[i + k].mComputedOffset = offset;
+    keyframeB->mComputedOffset = keyframeB->mOffset.valueOr(1.0);
+
+    // Fill computed offsets in (keyframe A, keyframe B).
+    if (aSpacingMode == SpacingMode::distribute) {
+      // Bug 1276573: Use the new constructor accepting two RangedPtr<T>
+      // arguments, so we can make the code simpler.
+      DistributeRange(Range<Keyframe>(keyframeA.get(),
+                                      keyframeB - keyframeA + 1));
+    } else {
+      // a) Find Paced A (first paceable keyframe) and
+      //    Paced B (last paceable keyframe) in [keyframe A, keyframe B].
+      RangedPtr<Keyframe> pacedA = keyframeA;
+      while (pacedA < keyframeB &&
+             cumulativeDistances[pacedA - begin] == kNotPaceable) {
+        ++pacedA;
+      }
+      RangedPtr<Keyframe> pacedB = keyframeB;
+      while (pacedB > keyframeA &&
+             cumulativeDistances[pacedB - begin] == kNotPaceable) {
+        --pacedB;
+      }
+      // As spec says, if there is no paceable keyframe
+      // in [keyframe A, keyframe B], we let Paced A and Paced B refer to
+      // keyframe B.
+      if (pacedA > pacedB) {
+        pacedA = pacedB = keyframeB;
+      }
+      // b) Apply distributing offsets in (keyframe A, Paced A] and
+      //    [Paced B, keyframe B).
+      DistributeRange(Range<Keyframe>(keyframeA.get(),
+                                      keyframeB - keyframeA + 1),
+                      Range<Keyframe>((keyframeA + 1).get(),
+                                      pacedA - keyframeA));
+      DistributeRange(Range<Keyframe>(keyframeA.get(),
+                                      keyframeB - keyframeA + 1),
+                      Range<Keyframe>(pacedB.get(),
+                                      keyframeB - pacedB));
+      // c) Apply paced offsets to each paceable keyframe in (Paced A, Paced B).
+      //    We pass the range [Paced A, Paced B] since PaceRange needs the end
+      //    points of the range in order to calculate the correct offset.
+      PaceRange(Range<Keyframe>(pacedA.get(), pacedB - pacedA + 1),
+                Range<double>(&cumulativeDistances[pacedA - begin],
+                              pacedB - pacedA + 1));
+      // d) Fill in any computed offsets in (Paced A, Paced B) that are still
+      //    not set (e.g. because the keyframe was not paceable, or because the
+      //    cumulative distance between paceable properties was zero).
+      for (RangedPtr<Keyframe> frame = pacedA + 1; frame < pacedB; ++frame) {
+        if (frame->mComputedOffset != Keyframe::kComputedOffsetNotSet) {
+          continue;
+        }
+
+        RangedPtr<Keyframe> start = frame - 1;
+        RangedPtr<Keyframe> end = frame + 1;
+        while (end < pacedB &&
+               end->mComputedOffset == Keyframe::kComputedOffsetNotSet) {
+          ++end;
+        }
+        DistributeRange(Range<Keyframe>(start.get(), end - start + 1));
+        frame = end;
+      }
     }
-    i = j;
-    aKeyframes[j].mComputedOffset = end;
+    keyframeA = keyframeB;
   }
 }
 
-/* static */ nsTArray<AnimationProperty>
-KeyframeUtils::GetAnimationPropertiesFromKeyframes(
-    nsStyleContext* aStyleContext,
-    dom::Element* aElement,
-    CSSPseudoElementType aPseudoType,
-    const nsTArray<Keyframe>& aFrames)
+/* static */ void
+KeyframeUtils::ApplyDistributeSpacing(nsTArray<Keyframe>& aKeyframes)
+{
+  nsTArray<ComputedKeyframeValues> emptyArray;
+  ApplySpacing(aKeyframes, SpacingMode::distribute, eCSSProperty_UNKNOWN,
+               emptyArray);
+}
+
+/* static */ nsTArray<ComputedKeyframeValues>
+KeyframeUtils::GetComputedKeyframeValues(const nsTArray<Keyframe>& aKeyframes,
+                                         dom::Element* aElement,
+                                         nsStyleContext* aStyleContext)
 {
   MOZ_ASSERT(aStyleContext);
   MOZ_ASSERT(aElement);
 
-  nsTArray<KeyframeValueEntry> entries;
+  const size_t len = aKeyframes.Length();
+  nsTArray<ComputedKeyframeValues> result(len);
 
-  for (const Keyframe& frame : aFrames) {
+  for (const Keyframe& frame : aKeyframes) {
     nsCSSPropertySet propertiesOnThisKeyframe;
+    ComputedKeyframeValues* computedValues = result.AppendElement();
     for (const PropertyValuePair& pair :
            PropertyPriorityIterator(frame.mPropertyValues)) {
       if (IsInvalidValuePair(pair)) {
         continue;
       }
 
       // Expand each value into the set of longhands and produce
       // a KeyframeValueEntry for each value.
       nsTArray<PropertyStyleAnimationValuePair> values;
 
       // For shorthands, we store the string as a token stream so we need to
       // extract that first.
       if (nsCSSProps::IsShorthand(pair.mProperty)) {
         nsCSSValueTokenStream* tokenStream = pair.mValue.GetTokenStreamValue();
         if (!StyleAnimationValue::ComputeValues(pair.mProperty,
               CSSEnabledState::eForAllContent, aElement, aStyleContext,
-              tokenStream->mTokenStream, /* aUseSVGMode */ false, values)) {
+              tokenStream->mTokenStream, /* aUseSVGMode */ false, values) ||
+            IsComputeValuesFailureKey(pair)) {
           continue;
         }
       } else {
         if (!StyleAnimationValue::ComputeValues(pair.mProperty,
               CSSEnabledState::eForAllContent, aElement, aStyleContext,
               pair.mValue, /* aUseSVGMode */ false, values)) {
           continue;
         }
@@ -533,34 +636,76 @@ KeyframeUtils::GetAnimationPropertiesFro
       }
 
       for (auto& value : values) {
         // If we already got a value for this property on the keyframe,
         // skip this one.
         if (propertiesOnThisKeyframe.HasProperty(value.mProperty)) {
           continue;
         }
-
-        KeyframeValueEntry* entry = entries.AppendElement();
-        entry->mOffset = frame.mComputedOffset;
-        entry->mProperty = value.mProperty;
-        entry->mValue = value.mValue;
-        entry->mTimingFunction = frame.mTimingFunction;
-
+        computedValues->AppendElement(value);
         propertiesOnThisKeyframe.AddProperty(value.mProperty);
       }
     }
   }
 
-  nsTArray<AnimationProperty> result;
-  BuildSegmentsFromValueEntries(aStyleContext, entries, result);
-
+  MOZ_ASSERT(result.Length() == aKeyframes.Length(), "Array length mismatch");
   return result;
 }
 
+/* static */ nsTArray<AnimationProperty>
+KeyframeUtils::GetAnimationPropertiesFromKeyframes(
+  const nsTArray<Keyframe>& aKeyframes,
+  const nsTArray<ComputedKeyframeValues>& aComputedValues,
+  nsStyleContext* aStyleContext)
+{
+  MOZ_ASSERT(aKeyframes.Length() == aComputedValues.Length(),
+             "Array length mismatch");
+
+  nsTArray<KeyframeValueEntry> entries(aKeyframes.Length());
+
+  const size_t len = aKeyframes.Length();
+  for (size_t i = 0; i < len; ++i) {
+    const Keyframe& frame = aKeyframes[i];
+    for (auto& value : aComputedValues[i]) {
+      MOZ_ASSERT(frame.mComputedOffset != Keyframe::kComputedOffsetNotSet,
+                 "Invalid computed offset");
+      KeyframeValueEntry* entry = entries.AppendElement();
+      entry->mOffset = frame.mComputedOffset;
+      entry->mProperty = value.mProperty;
+      entry->mValue = value.mValue;
+      entry->mTimingFunction = frame.mTimingFunction;
+    }
+  }
+
+  nsTArray<AnimationProperty> result;
+  BuildSegmentsFromValueEntries(aStyleContext, entries, result);
+  return result;
+}
+
+/* static */ bool
+KeyframeUtils::IsAnimatableProperty(nsCSSProperty aProperty)
+{
+  if (aProperty == eCSSProperty_UNKNOWN) {
+    return false;
+  }
+
+  if (!nsCSSProps::IsShorthand(aProperty)) {
+    return nsCSSProps::kAnimTypeTable[aProperty] != eStyleAnimType_None;
+  }
+
+  CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, aProperty,
+                                       CSSEnabledState::eForAllContent) {
+    if (nsCSSProps::kAnimTypeTable[*subprop] != eStyleAnimType_None) {
+      return true;
+    }
+  }
+
+  return false;
+}
 
 // ------------------------------------------------------------------
 //
 // Internal helpers
 //
 // ------------------------------------------------------------------
 
 /**
@@ -672,16 +817,27 @@ ConvertKeyframeSequence(JSContext* aCx,
         return false;
       }
     }
 
     for (PropertyValuesPair& pair : propertyValuePairs) {
       MOZ_ASSERT(pair.mValues.Length() == 1);
       keyframe->mPropertyValues.AppendElement(
         MakePropertyValuePair(pair.mProperty, pair.mValues[0], parser, doc));
+
+      // When we go to convert keyframes into arrays of property values we
+      // call StyleAnimation::ComputeValues. This should normally return true
+      // but in order to test the case where it does not, BaseKeyframeDict
+      // includes a chrome-only member that can be set to indicate that
+      // ComputeValues should fail for shorthand property values on that
+      // keyframe.
+      if (nsCSSProps::IsShorthand(pair.mProperty) &&
+          keyframeDict.mSimulateComputeValuesFailure) {
+        MarkAsComputeValuesFailureKey(keyframe->mPropertyValues.LastElement());
+      }
     }
   }
 
   return true;
 }
 
 /**
  * Reads the property-values pairs from the specified JS object.
@@ -716,17 +872,17 @@ GetPropertyValuesPairs(JSContext* aCx,
   for (size_t i = 0, n = ids.length(); i < n; i++) {
     nsAutoJSString propName;
     if (!propName.init(aCx, ids[i])) {
       return false;
     }
     nsCSSProperty property =
       nsCSSProps::LookupPropertyByIDLName(propName,
                                           CSSEnabledState::eForAllContent);
-    if (IsAnimatableProperty(property)) {
+    if (KeyframeUtils::IsAnimatableProperty(property)) {
       AdditionalProperty* p = properties.AppendElement();
       p->mProperty = property;
       p->mJsidIndex = i;
     }
   }
 
   // Sort the entries by IDL name and then get each value and
   // convert it either to a DOMString or to a
@@ -746,41 +902,16 @@ GetPropertyValuesPairs(JSContext* aCx,
       return false;
     }
   }
 
   return true;
 }
 
 /**
- * Returns true if |aProperty| or, for shorthands, one or more of
- * |aProperty|'s subproperties, is animatable.
- */
-static bool
-IsAnimatableProperty(nsCSSProperty aProperty)
-{
-  if (aProperty == eCSSProperty_UNKNOWN) {
-    return false;
-  }
-
-  if (!nsCSSProps::IsShorthand(aProperty)) {
-    return nsCSSProps::kAnimTypeTable[aProperty] != eStyleAnimType_None;
-  }
-
-  CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, aProperty,
-                                       CSSEnabledState::eForAllContent) {
-    if (nsCSSProps::kAnimTypeTable[*subprop] != eStyleAnimType_None) {
-      return true;
-    }
-  }
-
-  return false;
-}
-
-/**
  * Converts aValue to DOMString, if aAllowLists is eDisallow, or
  * to (DOMString or sequence<DOMString>) if aAllowLists is aAllow.
  * The resulting strings are appended to aValues.
  */
 static bool
 AppendStringOrStringSequenceToArray(JSContext* aCx,
                                     JS::Handle<JS::Value> aValue,
                                     ListAllowance aAllowLists,
@@ -904,16 +1035,61 @@ HasValidOffsets(const nsTArray<Keyframe>
         return false;
       }
       offset = thisOffset;
     }
   }
   return true;
 }
 
+/**
+ * Takes a property-value pair for a shorthand property and modifies the
+ * value to indicate that when we call StyleAnimationValue::ComputeValues on
+ * that value we should behave as if that function had failed.
+ *
+ * @param aPair The PropertyValuePair to modify. |aPair.mProperty| must be
+ *              a shorthand property.
+ */
+static void
+MarkAsComputeValuesFailureKey(PropertyValuePair& aPair)
+{
+  MOZ_ASSERT(nsCSSProps::IsShorthand(aPair.mProperty),
+             "Only shorthand property values can be marked as failure values");
+
+  // We store shorthand values as nsCSSValueTokenStream objects whose mProperty
+  // and mShorthandPropertyID are eCSSProperty_UNKNOWN and whose mTokenStream
+  // member contains the shorthand property's value as a string.
+  //
+  // We need to leave mShorthandPropertyID as eCSSProperty_UNKNOWN so that
+  // nsCSSValue::AppendToString returns the mTokenStream value, but we can
+  // update mPropertyID to a special value to indicate that this is
+  // a special failure sentinel.
+  nsCSSValueTokenStream* tokenStream = aPair.mValue.GetTokenStreamValue();
+  MOZ_ASSERT(tokenStream->mPropertyID == eCSSProperty_UNKNOWN,
+             "Shorthand value should initially have an unknown property ID");
+  tokenStream->mPropertyID = eCSSPropertyExtra_no_properties;
+}
+
+/**
+ * Returns true if |aPair| is a property-value pair on which we have
+ * previously called MarkAsComputeValuesFailureKey (and hence we should
+ * simulate failure when calling StyleAnimationValue::ComputeValues using its
+ * value).
+ *
+ * @param aPair The property-value pair to test.
+ * @return True if |aPair| represents a failure value.
+ */
+static bool
+IsComputeValuesFailureKey(const PropertyValuePair& aPair)
+{
+  return nsCSSProps::IsShorthand(aPair.mProperty) &&
+         aPair.mValue.GetTokenStreamValue()->mPropertyID ==
+           eCSSPropertyExtra_no_properties;
+}
+
 static already_AddRefed<nsStyleContext>
 CreateStyleContextForAnimationValue(nsCSSProperty aProperty,
                                     StyleAnimationValue aValue,
                                     nsStyleContext* aBaseStyleContext)
 {
   MOZ_ASSERT(aBaseStyleContext,
              "CreateStyleContextForAnimationValue needs to be called "
              "with a valid nsStyleContext");
@@ -1228,17 +1404,17 @@ RequiresAdditiveAnimation(const nsTArray
     } else if (aOffset == 1.0) {
       propertiesWithToValue.AddProperty(aProperty);
     }
   };
 
   for (size_t i = 0, len = aKeyframes.Length(); i < len; i++) {
     const Keyframe& frame = aKeyframes[i];
 
-    // We won't have called ApplyDistributeSpacing when this is called so
+    // We won't have called ApplySpacing when this is called so
     // we can't use frame.mComputedOffset. Instead we do a rough version
     // of that algorithm that substitutes null offsets with 0.0 for the first
     // frame, 1.0 for the last frame, and 0.5 for everything else.
     double computedOffset = i == len - 1
                             ? 1.0
                             : i == 0 ? 0.0 : 0.5;
     double offsetToUse = frame.mOffset
                          ? frame.mOffset.value()
@@ -1265,9 +1441,209 @@ RequiresAdditiveAnimation(const nsTArray
       }
     }
   }
 
   return !propertiesWithFromValue.Equals(properties) ||
          !propertiesWithToValue.Equals(properties);
 }
 
+/**
+ * Evenly distribute the computed offsets in (A, B).
+ * We pass the range keyframes in [A, B] and use A, B to calculate distributing
+ * computed offsets in (A, B). The second range, aRangeToAdjust, is passed, so
+ * we can know which keyframe we want to apply to. aRangeToAdjust should be in
+ * the range of aSpacingRange.
+ *
+ * @param aSpacingRange The sequence of keyframes between whose endpoints we
+ *   should apply distribute spacing.
+ * @param aRangeToAdjust The range of keyframes we want to apply to.
+ */
+static void
+DistributeRange(const Range<Keyframe>& aSpacingRange,
+                const Range<Keyframe>& aRangeToAdjust)
+{
+  MOZ_ASSERT(aRangeToAdjust.start() >= aSpacingRange.start() &&
+             aRangeToAdjust.end() <= aSpacingRange.end(),
+             "Out of range");
+  const size_t n = aSpacingRange.length() - 1;
+  const double startOffset = aSpacingRange[0].mComputedOffset;
+  const double diffOffset = aSpacingRange[n].mComputedOffset - startOffset;
+  for (auto iter = aRangeToAdjust.start();
+       iter != aRangeToAdjust.end();
+       ++iter) {
+    size_t index = iter - aSpacingRange.start();
+    iter->mComputedOffset = startOffset + double(index) / n * diffOffset;
+  }
+}
+
+/**
+ * Overload of DistributeRange to apply distribute spacing to all keyframes in
+ * between the endpoints of the given range.
+ *
+ * @param aSpacingRange The sequence of keyframes between whose endpoints we
+ *   should apply distribute spacing.
+ */
+static void
+DistributeRange(const Range<Keyframe>& aSpacingRange)
+{
+  // We don't need to apply distribute spacing to keyframe A and keyframe B.
+  DistributeRange(aSpacingRange,
+                  Range<Keyframe>((aSpacingRange.start() + 1).get(),
+                                  aSpacingRange.end() - aSpacingRange.start()
+                                    - 2));
+}
+
+/**
+ * Apply paced spacing to all paceable keyframes in between the endpoints of the
+ * given range.
+ *
+ * @param aKeyframes The range of keyframes between whose endpoints we should
+ *   apply paced spacing. Both endpoints should be paceable, i.e. the
+ *   corresponding elements in |aCumulativeDist| should not be kNotPaceable.
+ *   Within this function, we refer to the start and end points of this range
+ *   as Paced A and Paced B respectively in keeping with the notation used in
+ *   the spec.
+ * @param aCumulativeDistances The sequence of cumulative distances of the paced
+ *   property as returned by GetCumulativeDistances(). This acts as a
+ *   parallel range to |aKeyframes|.
+ */
+static void
+PaceRange(const Range<Keyframe>& aKeyframes,
+          const Range<double>& aCumulativeDistances)
+{
+  MOZ_ASSERT(aKeyframes.length() == aCumulativeDistances.length(),
+             "Range length mismatch");
+
+  const size_t len = aKeyframes.length();
+  // If there is nothing between the end points, there is nothing to space.
+  if (len < 3) {
+    return;
+  }
+
+  const double distA = *(aCumulativeDistances.start());
+  const double distB = *(aCumulativeDistances.end() - 1);
+  MOZ_ASSERT(distA != kNotPaceable && distB != kNotPaceable,
+             "Both Paced A and Paced B should be paceable");
+
+  // If the total distance is zero, we should fall back to distribute spacing.
+  // The caller will fill-in any keyframes without a computed offset using
+  // distribute spacing so we can just return here.
+  if (distA == distB) {
+    return;
+  }
+
+  const RangedPtr<Keyframe> pacedA = aKeyframes.start();
+  const RangedPtr<Keyframe> pacedB = aKeyframes.end() - 1;
+  MOZ_ASSERT(pacedA->mComputedOffset != Keyframe::kComputedOffsetNotSet &&
+             pacedB->mComputedOffset != Keyframe::kComputedOffsetNotSet,
+             "Both Paced A and Paced B should have valid computed offsets");
+
+  // Apply computed offset.
+  const double offsetA     = pacedA->mComputedOffset;
+  const double diffOffset  = pacedB->mComputedOffset - offsetA;
+  const double initialDist = distA;
+  const double totalDist   = distB - initialDist;
+  for (auto iter = pacedA + 1; iter != pacedB; ++iter) {
+    size_t k = iter - aKeyframes.start();
+    if (aCumulativeDistances[k] == kNotPaceable) {
+      continue;
+    }
+
+    double dist = aCumulativeDistances[k] - initialDist;
+    iter->mComputedOffset = offsetA + diffOffset * dist / totalDist;
+  }
+}
+
+/**
+ * Get cumulative distances for the paced property.
+ *
+ * @param aValues The computed values returned by GetComputedKeyframeValues.
+ * @param aPacedProperty The paced property.
+ * @return The cumulative distances for the paced property. The length will be
+ *   the same as aValues.
+ */
+static nsTArray<double>
+GetCumulativeDistances(const nsTArray<ComputedKeyframeValues>& aValues,
+                       nsCSSProperty aPacedProperty)
+{
+  // a) If aPacedProperty is a shorthand property, get its components.
+  //    Otherwise, just add the longhand property into the set.
+  size_t pacedPropertyCount = 0;
+  nsCSSPropertySet pacedPropertySet;
+  bool isShorthand = nsCSSProps::IsShorthand(aPacedProperty);
+  if (isShorthand) {
+    CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(p, aPacedProperty,
+                                         CSSEnabledState::eForAllContent) {
+      pacedPropertySet.AddProperty(*p);
+      ++pacedPropertyCount;
+    }
+  } else {
+    pacedPropertySet.AddProperty(aPacedProperty);
+    pacedPropertyCount = 1;
+  }
+
+  // b) Search each component (shorthand) or the longhand property, and
+  //    calculate the cumulative distances of paceable keyframe pairs.
+  const size_t len = aValues.Length();
+  nsTArray<double> cumulativeDistances(len);
+  // cumulativeDistances is a parallel array to |aValues|, so set its length to
+  // the length of |aValues|.
+  cumulativeDistances.SetLength(len);
+  ComputedKeyframeValues prevPacedValues;
+  size_t preIdx = 0;
+  for (size_t i = 0; i < len; ++i) {
+    // Find computed values of the paced property.
+    ComputedKeyframeValues pacedValues;
+    for (const PropertyStyleAnimationValuePair& pair : aValues[i]) {
+      if (pacedPropertySet.HasProperty(pair.mProperty)) {
+        pacedValues.AppendElement(pair);
+      }
+    }
+
+    // Check we have values for all the paceable longhand components.
+    if (pacedValues.Length() != pacedPropertyCount) {
+      // This keyframe is not paceable, assign kNotPaceable and skip it.
+      cumulativeDistances[i] = kNotPaceable;
+      continue;
+    }
+
+    if (prevPacedValues.IsEmpty()) {
+      // This is the first paceable keyframe so its cumulative distance is 0.0.
+      cumulativeDistances[i] = 0.0;
+    } else {
+      double dist = 0.0;
+      if (isShorthand) {
+        // Apply the distance by the square root of the sum of squares of
+        // longhand component distances.
+        for (size_t propIdx = 0; propIdx < pacedPropertyCount; ++propIdx) {
+          nsCSSProperty prop = prevPacedValues[propIdx].mProperty;
+          MOZ_ASSERT(pacedValues[propIdx].mProperty == prop,
+                     "Property mismatch");
+
+          double componentDistance = 0.0;
+          if (StyleAnimationValue::ComputeDistance(
+                prop,
+                prevPacedValues[propIdx].mValue,
+                pacedValues[propIdx].mValue,
+                componentDistance)) {
+            dist += componentDistance * componentDistance;
+          }
+        }
+        dist = sqrt(dist);
+      } else {
+        // If the property is longhand, we just use the 1st value.
+        // If ComputeDistance() fails, |dist| will remain zero so there will be
+        // no distance between the previous paced value and this value.
+        StyleAnimationValue::ComputeDistance(aPacedProperty,
+                                             prevPacedValues[0].mValue,
+                                             pacedValues[0].mValue,
+                                             dist);
+      }
+      cumulativeDistances[i] = cumulativeDistances[preIdx] + dist;
+    }
+    prevPacedValues.SwapElements(pacedValues);
+    preIdx = i;
+  }
+  return cumulativeDistances;
+}
+
 } // namespace mozilla
--- a/dom/animation/KeyframeUtils.h
+++ b/dom/animation/KeyframeUtils.h
@@ -13,25 +13,30 @@
 struct JSContext;
 class JSObject;
 
 namespace mozilla {
 struct AnimationProperty;
 enum class CSSPseudoElementType : uint8_t;
 class ErrorResult;
 struct Keyframe;
+struct PropertyStyleAnimationValuePair;
 
 namespace dom {
 class Element;
 } // namespace dom
 } // namespace mozilla
 
 
 namespace mozilla {
 
+// Represents the set of property-value pairs on a Keyframe converted to
+// computed values.
+using ComputedKeyframeValues = nsTArray<PropertyStyleAnimationValuePair>;
+
 /**
  * Utility methods for processing keyframes.
  */
 class KeyframeUtils
 {
 public:
   /**
    * Converts a JS value representing a property-indexed keyframe or a sequence
@@ -47,39 +52,92 @@ public:
    *   returned.
    */
   static nsTArray<Keyframe>
   GetKeyframesFromObject(JSContext* aCx,
                          JS::Handle<JSObject*> aFrames,
                          ErrorResult& aRv);
 
   /**
-   * Fills in the mComputedOffset member of each keyframe in the given array
-   * using the "distribute" spacing algorithm.
+   * Calculate the StyleAnimationValues of properties of each keyframe.
+   * This involves expanding shorthand properties into longhand properties,
+   * removing the duplicated properties for each keyframe, and creating an
+   * array of |property:computed value| pairs for each keyframe.
+   *
+   * These computed values are used *both* when computing the final set of
+   * per-property animation values (see GetAnimationPropertiesFromKeyframes) as
+   * well when applying paced spacing. By returning these values here, we allow
+   * the result to be re-used in both operations.
    *
-   * http://w3c.github.io/web-animations/#distribute-keyframe-spacing-mode
+   * @param aKeyframes The input keyframes.
+   * @param aElement The context element.
+   * @param aStyleContext The style context to use when computing values.
+   * @return The set of ComputedKeyframeValues. The length will be the same as
+   *   aFrames.
+   */
+  static nsTArray<ComputedKeyframeValues>
+  GetComputedKeyframeValues(const nsTArray<Keyframe>& aKeyframes,
+                            dom::Element* aElement,
+                            nsStyleContext* aStyleContext);
+
+  /**
+   * Fills in the mComputedOffset member of each keyframe in the given array
+   * using the specified spacing mode.
+   *
+   * https://w3c.github.io/web-animations/#spacing-keyframes
    *
-   * @param keyframes The set of keyframes to adjust.
+   * @param aKeyframes The set of keyframes to adjust.
+   * @param aSpacingMode The spacing mode to apply.
+   * @param aProperty The paced property. Only used when |aSpacingMode| is
+   *   SpacingMode::paced. In all other cases it is ignored and hence may be
+   *   any value, e.g. eCSSProperty_UNKNOWN.
+   * @param aComputedValues The set of computed keyframe values as returned by
+   *   GetComputedKeyframeValues. Only used when |aSpacingMode| is
+   *   SpacingMode::paced. In all other cases this parameter is unused and may
+   *   be any value including an empty array.
+   */
+  static void ApplySpacing(nsTArray<Keyframe>& aKeyframes,
+                           SpacingMode aSpacingMode,
+                           nsCSSProperty aProperty,
+                           nsTArray<ComputedKeyframeValues>& aComputedValues);
+
+  /**
+   * Wrapper for ApplySpacing to simplify using distribute spacing.
+   *
+   * @param aKeyframes The set of keyframes to adjust.
    */
   static void ApplyDistributeSpacing(nsTArray<Keyframe>& aKeyframes);
 
   /**
    * Converts an array of Keyframe objects into an array of AnimationProperty
-   * objects. This involves expanding shorthand properties into longhand
-   * properties, creating an array of computed values for each longhand
-   * property and determining the offset and timing function to use for each
-   * value.
+   * objects. This involves creating an array of computed values for each
+   * longhand property and determining the offset and timing function to use
+   * for each value.
    *
-   * @param aStyleContext The style context to use when computing values.
-   * @param aFrames The input keyframes.
+   * @param aKeyframes The input keyframes.
+   * @param aComputedValues The computed keyframe values (as returned by
+   *   GetComputedKeyframeValues) used to fill in the individual
+   *   AnimationPropertySegment objects. Although these values could be
+   *   calculated from |aKeyframes|, passing them in as a separate parameter
+   *   allows the result of GetComputedKeyframeValues to be re-used both
+   *   here and in ApplySpacing.
+   * @param aStyleContext The style context to calculate the style difference.
    * @return The set of animation properties. If an error occurs, the returned
    *   array will be empty.
    */
-  static nsTArray<AnimationProperty>
-  GetAnimationPropertiesFromKeyframes(nsStyleContext* aStyleContext,
-                                      dom::Element* aElement,
-                                      CSSPseudoElementType aPseudoType,
-                                      const nsTArray<Keyframe>& aFrames);
+  static nsTArray<AnimationProperty> GetAnimationPropertiesFromKeyframes(
+    const nsTArray<Keyframe>& aKeyframes,
+    const nsTArray<ComputedKeyframeValues>& aComputedValues,
+    nsStyleContext* aStyleContext);
+
+  /**
+   * Check if the property or, for shorthands, one or more of
+   * its subproperties, is animatable.
+   *
+   * @param aProperty The property to check.
+   * @return true if |aProperty| is animatable.
+   */
+  static bool IsAnimatableProperty(nsCSSProperty aProperty);
 };
 
 } // namespace mozilla
 
 #endif // mozilla_KeyframeUtils_h
--- a/dom/animation/moz.build
+++ b/dom/animation/moz.build
@@ -23,16 +23,17 @@ EXPORTS.mozilla += [
     'AnimationPerformanceWarning.h',
     'AnimationTarget.h',
     'AnimationUtils.h',
     'AnimValuesStyleRule.h',
     'ComputedTiming.h',
     'ComputedTimingFunction.h',
     'EffectCompositor.h',
     'EffectSet.h',
+    'KeyframeEffectParams.h',
     'KeyframeUtils.h',
     'PendingAnimationTracker.h',
     'PseudoElementHashEntry.h',
     'TimingParams.h',
 ]
 
 UNIFIED_SOURCES += [
     'Animation.cpp',
@@ -44,16 +45,17 @@ UNIFIED_SOURCES += [
     'AnimationUtils.cpp',
     'AnimValuesStyleRule.cpp',
     'ComputedTimingFunction.cpp',
     'CSSPseudoElement.cpp',
     'DocumentTimeline.cpp',
     'EffectCompositor.cpp',
     'EffectSet.cpp',
     'KeyframeEffect.cpp',
+    'KeyframeEffectParams.cpp',
     'KeyframeUtils.cpp',
     'PendingAnimationTracker.cpp',
     'TimingParams.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/dom/base',
     '/layout/base',
--- a/dom/animation/test/chrome/test_animation_properties.html
+++ b/dom/animation/test/chrome/test_animation_properties.html
@@ -500,16 +500,17 @@ var gTests = [
                             value(1, 'calc(100px + 10%)', 'replace') ] } ]
   },
 
   // ---------------------------------------------------------------------
   //
   // Tests for CSS variable handling conversion
   //
   // ---------------------------------------------------------------------
+
   { desc:     'CSS variables are resolved to their corresponding values',
     frames:   { left: ['10px', 'var(--var-100px)'] },
     expected: [ { property: 'left',
                   values: [ value(0, '10px', 'replace', 'linear'),
                             value(1, '100px', 'replace') ] } ]
   },
   { desc:     'CSS variables in calc() expressions are resolved',
     frames:   { left: ['10px', 'calc(var(--var-100px) / 2 - 10%)'] },
@@ -528,16 +529,300 @@ var gTests = [
                             value(1, '200px', 'replace') ] },
                 { property: 'margin-bottom',
                   values: [ value(0, '10px', 'replace', 'linear'),
                             value(1, '100px', 'replace') ] },
                 { property: 'margin-left',
                   values: [ value(0, '10px', 'replace', 'linear'),
                             value(1, '200px', 'replace') ] } ]
   },
+
+  // ---------------------------------------------------------------------
+  //
+  // Tests for properties that parse correctly but which we fail to
+  // convert to computed values.
+  //
+  // ---------------------------------------------------------------------
+
+  { desc:     'a property that can\'t be resolved to computed values in'
+              + ' initial keyframe',
+    frames:   [ { margin: '5px', simulateComputeValuesFailure: true },
+                { margin: '5px' } ],
+    expected: [  ]
+  },
+  { desc:     'a property that can\'t be resolved to computed values in'
+              + ' initial keyframe where we have enough values to create'
+              + ' a final segment',
+    frames:   [ { margin: '5px', simulateComputeValuesFailure: true },
+                { margin: '5px' },
+                { margin: '5px' } ],
+    expected: [  ]
+  },
+  { desc:     'a property that can\'t be resolved to computed values in'
+              + ' initial overlapping keyframes (first in series of two)',
+    frames:   [ { margin: '5px', offset: 0,
+                  simulateComputeValuesFailure: true },
+                { margin: '5px', offset: 0 },
+                { margin: '5px' } ],
+    expected: [ { property: 'margin-top',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-right',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-bottom',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-left',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] } ]
+  },
+  { desc:     'a property that can\'t be resolved to computed values in'
+              + ' initial overlapping keyframes (second in series of two)',
+    frames:   [ { margin: '5px', offset: 0 },
+                { margin: '5px', offset: 0,
+                  simulateComputeValuesFailure: true },
+                { margin: '5px' } ],
+    expected: [ { property: 'margin-top',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-right',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-bottom',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-left',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] } ]
+  },
+  { desc:     'a property that can\'t be resolved to computed values in'
+              + ' initial overlapping keyframes (second in series of three)',
+    frames:   [ { margin: '5px', offset: 0 },
+                { margin: '5px', offset: 0,
+                  simulateComputeValuesFailure: true },
+                { margin: '5px', offset: 0 },
+                { margin: '5px' } ],
+    expected: [ { property: 'margin-top',
+                  values: [ value(0, '5px', 'replace'),
+                            value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-right',
+                  values: [ value(0, '5px', 'replace'),
+                            value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-bottom',
+                  values: [ value(0, '5px', 'replace'),
+                            value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-left',
+                  values: [ value(0, '5px', 'replace'),
+                            value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] } ]
+  },
+  { desc:     'a property that can\'t be resolved to computed values in'
+              + ' final keyframe',
+    frames:   [ { margin: '5px' },
+                { margin: '5px', simulateComputeValuesFailure: true } ],
+    expected: [  ]
+  },
+  { desc:     'a property that can\'t be resolved to computed values in'
+              + ' final keyframe where it forms the last segment in the series',
+    frames:   [ { margin: '5px' },
+                { margin: '5px',
+                  marginLeft: '5px',
+                  marginRight: '5px',
+                  marginBottom: '5px',
+                  // margin-top sorts last and only it will be missing since
+                  // the other longhand components are specified
+                  simulateComputeValuesFailure: true } ],
+    expected: [ { property: 'margin-bottom',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-left',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-right',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] } ]
+  },
+  { desc:     'a property that can\'t be resolved to computed values in'
+              + ' final keyframe where we have enough values to create'
+              + ' an initial segment',
+    frames:   [ { margin: '5px' },
+                { margin: '5px' },
+                { margin: '5px', simulateComputeValuesFailure: true } ],
+    expected: [  ]
+  },
+  { desc:     'a property that can\'t be resolved to computed values in'
+              + ' final overlapping keyframes (first in series of two)',
+    frames:   [ { margin: '5px' },
+                { margin: '5px', offset: 1,
+                  simulateComputeValuesFailure: true },
+                { margin: '5px', offset: 1 } ],
+    expected: [ { property: 'margin-top',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-right',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-bottom',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-left',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] } ]
+  },
+  { desc:     'a property that can\'t be resolved to computed values in'
+              + ' final overlapping keyframes (second in series of two)',
+    frames:   [ { margin: '5px' },
+                { margin: '5px', offset: 1 },
+                { margin: '5px', offset: 1,
+                  simulateComputeValuesFailure: true } ],
+    expected: [ { property: 'margin-top',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-right',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-bottom',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-left',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] } ]
+  },
+  { desc:     'a property that can\'t be resolved to computed values in'
+              + ' final overlapping keyframes (second in series of three)',
+    frames:   [ { margin: '5px' },
+                { margin: '5px', offset: 1 },
+                { margin: '5px', offset: 1,
+                  simulateComputeValuesFailure: true },
+                { margin: '5px', offset: 1 } ],
+    expected: [ { property: 'margin-top',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-right',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-bottom',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-left',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace'),
+                            value(1, '5px', 'replace') ] } ]
+  },
+  { desc:     'a property that can\'t be resolved to computed values in'
+              + ' intermediate keyframe',
+    frames:   [ { margin: '5px' },
+                { margin: '5px', simulateComputeValuesFailure: true },
+                { margin: '5px' } ],
+    expected: [ { property: 'margin-top',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-right',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-bottom',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-left',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] } ]
+  },
+  { desc:     'a property that can\'t be resolved to computed values in'
+              + ' initial keyframe along with other values',
+    // simulateComputeValuesFailure only applies to shorthands so we can set
+    // it on the same keyframe and it will only apply to |margin| and not
+    // |left|.
+    frames:   [ { margin: '77%', left: '10px',
+                  simulateComputeValuesFailure: true },
+                { margin: '5px', left: '20px' } ],
+    expected: [ { property: 'left',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(1, '20px', 'replace') ] } ],
+  },
+  { desc:     'a property that can\'t be resolved to computed values in'
+              + ' initial keyframe along with other values where those'
+              + ' values sort after the property with missing values',
+    frames:   [ { margin: '77%', right: '10px',
+                  simulateComputeValuesFailure: true },
+                { margin: '5px', right: '20px' } ],
+    expected: [ { property: 'right',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(1, '20px', 'replace') ] } ],
+  },
+  { desc:     'a property that can\'t be resolved to computed values in'
+              + ' final keyframe along with other values',
+    frames:   [ { margin: '5px', left: '10px' },
+                { margin: '5px', left: '20px',
+                  simulateComputeValuesFailure: true } ],
+    expected: [ { property: 'left',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(1, '20px', 'replace') ] } ],
+  },
+  { desc:     'a property that can\'t be resolved to computed values in'
+              + ' final keyframe along with other values where those'
+              + ' values sort after the property with missing values',
+    frames:   [ { margin: '5px', right: '10px' },
+                { margin: '5px', right: '20px',
+                  simulateComputeValuesFailure: true } ],
+    expected: [ { property: 'right',
+                  values: [ value(0, '10px', 'replace', 'linear'),
+                            value(1, '20px', 'replace') ] } ],
+  },
+  { desc:     'a property that can\'t be resolved to computed values in'
+              + ' an intermediate keyframe along with other values',
+    frames:   [ { margin: '5px', left: '10px' },
+                { margin: '5px', left: '20px',
+                  simulateComputeValuesFailure: true },
+                { margin: '5px', left: '30px' } ],
+    expected: [ { property: 'margin-top',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-right',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-bottom',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-left',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'left',
+                  values: [ value(0,   '10px', 'replace', 'linear'),
+                            value(0.5, '20px', 'replace', 'linear'),
+                            value(1,   '30px', 'replace') ] } ]
+  },
+  { desc:     'a property that can\'t be resolved to computed values in'
+              + ' an intermediate keyframe by itself',
+    frames:   [ { margin: '5px', left: '10px' },
+                { margin: '5px',
+                  simulateComputeValuesFailure: true },
+                { margin: '5px', left: '30px' } ],
+    expected: [ { property: 'margin-top',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-right',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-bottom',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'margin-left',
+                  values: [ value(0, '5px', 'replace', 'linear'),
+                            value(1, '5px', 'replace') ] },
+                { property: 'left',
+                  values: [ value(0,   '10px', 'replace', 'linear'),
+                            value(1,   '30px', 'replace') ] } ]
+  },
 ];
 
 gTests.forEach(function(subtest) {
   test(function(t) {
     var div = addDiv(t);
     var animation = div.animate(subtest.frames, 100 * MS_PER_SEC);
     assert_properties_equal(animation.effect.getProperties(),
                             subtest.expected);
--- a/dom/base/BlobSet.h
+++ b/dom/base/BlobSet.h
@@ -7,16 +7,17 @@
 #ifndef mozilla_dom_BlobSet_h
 #define mozilla_dom_BlobSet_h
 
 #include "mozilla/RefPtr.h"
 
 namespace mozilla {
 namespace dom {
 
+class Blob;
 class BlobImpl;
 
 class BlobSet final
 {
 public:
   BlobSet()
     : mData(nullptr)
     , mDataLen(0)
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -27,16 +27,19 @@
 #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/DeviceStorageAreaListener.h"
+#ifdef MOZ_GAMEPAD
+#include "mozilla/dom/GamepadServiceTest.h"
+#endif
 #include "mozilla/dom/PowerManager.h"
 #include "mozilla/dom/WakeLock.h"
 #include "mozilla/dom/power/PowerManagerService.h"
 #include "mozilla/dom/CellBroadcast.h"
 #include "mozilla/dom/FlyWebPublishedServer.h"
 #include "mozilla/dom/FlyWebService.h"
 #include "mozilla/dom/IccManager.h"
 #include "mozilla/dom/InputPortManager.h"
@@ -260,16 +263,19 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mServiceWorkerContainer)
 
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
 #ifdef MOZ_EME
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mMediaKeySystemAccessManager)
 #endif
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mDeviceStorageAreaListener)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPresentation)
+#ifdef MOZ_GAMEPAD
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mGamepadServiceTest)
+#endif
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mVRGetDevicesPromises)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(Navigator)
 
 void
 Navigator::Invalidate()
@@ -402,16 +408,23 @@ Navigator::Invalidate()
     mMediaKeySystemAccessManager = nullptr;
   }
 #endif
 
   if (mDeviceStorageAreaListener) {
     mDeviceStorageAreaListener = nullptr;
   }
 
+#ifdef MOZ_GAMEPAD
+  if (mGamepadServiceTest) {
+    mGamepadServiceTest->Shutdown();
+    mGamepadServiceTest = nullptr;
+  }
+#endif
+
   mVRGetDevicesPromises.Clear();
 }
 
 //*****************************************************************************
 //    Navigator::nsIDOMNavigator
 //*****************************************************************************
 
 NS_IMETHODIMP
@@ -2015,16 +2028,25 @@ Navigator::GetGamepads(nsTArray<RefPtr<G
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return;
   }
   NS_ENSURE_TRUE_VOID(mWindow->GetDocShell());
   nsGlobalWindow* win = nsGlobalWindow::Cast(mWindow);
   win->SetHasGamepadEventListener(true);
   win->GetGamepads(aGamepads);
 }
+
+GamepadServiceTest*
+Navigator::RequestGamepadServiceTest()
+{
+  if (!mGamepadServiceTest) {
+    mGamepadServiceTest = GamepadServiceTest::CreateTestService(mWindow);
+  }
+  return mGamepadServiceTest;
+}
 #endif
 
 already_AddRefed<Promise>
 Navigator::GetVRDevices(ErrorResult& aRv)
 {
   if (!mWindow || !mWindow->GetDocShell()) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
--- a/dom/base/Navigator.h
+++ b/dom/base/Navigator.h
@@ -66,16 +66,17 @@ class FMRadio;
 
 class Promise;
 
 class DesktopNotificationCenter;
 class MobileMessageManager;
 class MozIdleObserver;
 #ifdef MOZ_GAMEPAD
 class Gamepad;
+class GamepadServiceTest;
 #endif // MOZ_GAMEPAD
 class NavigatorUserMediaSuccessCallback;
 class NavigatorUserMediaErrorCallback;
 class MozGetUserMediaDevicesSuccessCallback;
 
 namespace network {
 class Connection;
 } // namespace network
@@ -260,16 +261,17 @@ public:
   already_AddRefed<Promise> GetMobileIdAssertion(const MobileIdOptions& options,
                                                  ErrorResult& aRv);
 #endif
 #ifdef MOZ_B2G_RIL
   MobileConnectionArray* GetMozMobileConnections(ErrorResult& aRv);
 #endif // MOZ_B2G_RIL
 #ifdef MOZ_GAMEPAD
   void GetGamepads(nsTArray<RefPtr<Gamepad> >& aGamepads, ErrorResult& aRv);
+  GamepadServiceTest* RequestGamepadServiceTest();
 #endif // MOZ_GAMEPAD
   already_AddRefed<Promise> GetVRDevices(ErrorResult& aRv);
   void NotifyVRDevicesUpdated();
 #ifdef MOZ_B2G_FM
   FMRadio* GetMozFMRadio(ErrorResult& aRv);
 #endif
 #ifdef MOZ_B2G_BT
   bluetooth::BluetoothManager* GetMozBluetooth(ErrorResult& aRv);
@@ -393,17 +395,19 @@ private:
   RefPtr<MediaDevices> mMediaDevices;
   nsCOMPtr<nsIDOMNavigatorSystemMessages> mMessagesManager;
   nsTArray<nsWeakPtr> mDeviceStorageStores;
   RefPtr<time::TimeManager> mTimeManager;
   RefPtr<ServiceWorkerContainer> mServiceWorkerContainer;
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
   RefPtr<DeviceStorageAreaListener> mDeviceStorageAreaListener;
   RefPtr<Presentation> mPresentation;
-
+#ifdef MOZ_GAMEPAD
+  RefPtr<GamepadServiceTest> mGamepadServiceTest;
+#endif
   nsTArray<RefPtr<Promise> > mVRGetDevicesPromises;
   nsTArray<uint32_t> mRequestedVibrationPattern;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_Navigator_h
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -30,17 +30,16 @@ XPIDL_SOURCES += [
     'nsISelection.idl',
     'nsISelectionController.idl',
     'nsISelectionDisplay.idl',
     'nsISelectionListener.idl',
     'nsISelectionPrivate.idl',
     'nsISimpleContentPolicy.idl',
     'nsISiteSpecificUserAgent.idl',
     'nsISlowScriptDebug.idl',
-    'nsIXMLHttpRequest.idl',
 ]
 
 XPIDL_MODULE = 'dom'
 
 EXPORTS += [
     'AutocompleteFieldList.h',
     'Crypto.h',
     'HTMLSplitOnSpacesTokenizer.h',
@@ -317,17 +316,16 @@ UNIFIED_SOURCES += [
     'nsTraversal.cpp',
     'nsTreeSanitizer.cpp',
     'nsViewportInfo.cpp',
     'nsWindowMemoryReporter.cpp',
     'nsWindowRoot.cpp',
     'nsWrapperCache.cpp',
     'nsXHTMLContentSerializer.cpp',
     'nsXMLContentSerializer.cpp',
-    'nsXMLHttpRequest.cpp',
     'nsXMLNameSpaceMap.cpp',
     'PostMessageEvent.cpp',
     'ProcessGlobal.cpp',
     'ResponsiveImageSelector.cpp',
     'SameProcessMessageQueue.cpp',
     'ScreenOrientation.cpp',
     'ScriptSettings.cpp',
     'ShadowRoot.cpp',
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -2293,17 +2293,17 @@ nsDOMWindowUtils::GetSupportsHardwareH26
   NS_ENSURE_STATE(promise);
   aPromise.setObject(*promise->PromiseObj());
 #else
   ErrorResult rv;
   RefPtr<Promise> promise = Promise::Create(parentObject, rv);
   if (rv.Failed()) {
     return rv.StealNSResult();
   }
-  promise.MaybeResolve(NS_LITERAL_STRING("No; Compiled without MP4 support."));
+  promise->MaybeResolve(NS_LITERAL_STRING("No; Compiled without MP4 support."));
   aPromise.setObject(*promise->PromiseObj());
 #endif
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMWindowUtils::StartFrameTimeRecording(uint32_t *startIndex)
 {
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -184,17 +184,17 @@
 #include "prprf.h"
 
 #include "mozilla/dom/IDBFactory.h"
 #include "mozilla/dom/MessageChannel.h"
 #include "mozilla/dom/Promise.h"
 
 #ifdef MOZ_GAMEPAD
 #include "mozilla/dom/Gamepad.h"
-#include "mozilla/dom/GamepadService.h"
+#include "mozilla/dom/GamepadManager.h"
 #endif
 
 #include "mozilla/dom/VRDevice.h"
 
 #include "nsRefreshDriver.h"
 #include "Layers.h"
 
 #include "mozilla/AddonPathService.h"
@@ -9914,34 +9914,34 @@ void nsGlobalWindow::MaybeUpdateTouchSta
 
 void
 nsGlobalWindow::EnableGamepadUpdates()
 {
   MOZ_ASSERT(IsInnerWindow());
 
   if (mHasGamepad) {
 #ifdef MOZ_GAMEPAD
-    RefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
-    if (gamepadsvc) {
-      gamepadsvc->AddListener(this);
+    RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService());
+    if (gamepadManager) {
+      gamepadManager->AddListener(this);
     }
 #endif
   }
 }
 
 void
 nsGlobalWindow::DisableGamepadUpdates()
 {
   MOZ_ASSERT(IsInnerWindow());
 
   if (mHasGamepad) {
 #ifdef MOZ_GAMEPAD
-    RefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
-    if (gamepadsvc) {
-      gamepadsvc->RemoveListener(this);
+    RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService());
+    if (gamepadManager) {
+      gamepadManager->RemoveListener(this);
     }
 #endif
   }
 }
 
 void
 nsGlobalWindow::SetChromeEventHandler(EventTarget* aChromeEventHandler)
 {
@@ -13398,19 +13398,19 @@ nsGlobalWindow::HasSeenGamepadInput()
   return mHasSeenGamepadInput;
 }
 
 void
 nsGlobalWindow::SyncGamepadState()
 {
   MOZ_ASSERT(IsInnerWindow());
   if (mHasSeenGamepadInput) {
-    RefPtr<GamepadService> gamepadsvc(GamepadService::GetService());
+    RefPtr<GamepadManager> gamepadManager(GamepadManager::GetService());
     for (auto iter = mGamepads.Iter(); !iter.Done(); iter.Next()) {
-      gamepadsvc->SyncGamepadState(iter.Key(), iter.UserData());
+      gamepadManager->SyncGamepadState(iter.Key(), iter.UserData());
     }
   }
 }
 #endif // MOZ_GAMEPAD
 
 bool
 nsGlobalWindow::UpdateVRDevices(nsTArray<RefPtr<mozilla::dom::VRDevice>>& aDevices)
 {
--- a/dom/base/nsObjectLoadingContent.cpp
+++ b/dom/base/nsObjectLoadingContent.cpp
@@ -2259,17 +2259,20 @@ nsObjectLoadingContent::LoadObject(bool 
 
   if (mType != eType_Null) {
     bool allowLoad = true;
     if (IsJavaMIME(mContentType)) {
       allowLoad = CheckJavaCodebase();
     }
     int16_t contentPolicy = nsIContentPolicy::ACCEPT;
     // If mChannelLoaded is set we presumably already passed load policy
-    if (allowLoad && mURI && !mChannelLoaded) {
+    // If mType == eType_Loading then we call OpenChannel() which internally
+    // creates a new channel and calls asyncOpen2() on that channel which
+    // then enforces content policy checks.
+    if (allowLoad && mURI && !mChannelLoaded && mType != eType_Loading) {
       allowLoad = CheckLoadPolicy(&contentPolicy);
     }
     // If we're loading a type now, check ProcessPolicy. Note that we may check
     // both now in the case of plugins whose type is determined before opening a
     // channel.
     if (allowLoad && mType != eType_Loading) {
       allowLoad = CheckProcessPolicy(&contentPolicy);
     }
@@ -2612,44 +2615,39 @@ nsObjectLoadingContent::CloseChannel()
   return NS_OK;
 }
 
 nsresult
 nsObjectLoadingContent::OpenChannel()
 {
   nsCOMPtr<nsIContent> thisContent =
     do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
-  nsCOMPtr<nsIScriptSecurityManager> secMan =
-    nsContentUtils::GetSecurityManager();
   NS_ASSERTION(thisContent, "must be a content");
   nsIDocument* doc = thisContent->OwnerDoc();
   NS_ASSERTION(doc, "No owner document?");
 
   nsresult rv;
   mChannel = nullptr;
 
   // E.g. mms://
   if (!mURI || !CanHandleURI(mURI)) {
     return NS_ERROR_NOT_AVAILABLE;
   }
 
-  rv = secMan->CheckLoadURIWithPrincipal(thisContent->NodePrincipal(), mURI, 0);
-  NS_ENSURE_SUCCESS(rv, rv);
-
   nsCOMPtr<nsILoadGroup> group = doc->GetDocumentLoadGroup();
   nsCOMPtr<nsIChannel> chan;
   RefPtr<ObjectInterfaceRequestorShim> shim =
     new ObjectInterfaceRequestorShim(this);
 
   bool isSandBoxed = doc->GetSandboxFlags() & SANDBOXED_ORIGIN;
   bool inherit = nsContentUtils::ChannelShouldInheritPrincipal(thisContent->NodePrincipal(),
                                                                mURI,
                                                                true,   // aInheritForAboutBlank
                                                                false); // aForceInherit
-  nsSecurityFlags securityFlags = nsILoadInfo::SEC_NORMAL;
+  nsSecurityFlags securityFlags = nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_INHERITS;
   if (inherit) {
     securityFlags |= nsILoadInfo::SEC_FORCE_INHERIT_PRINCIPAL;
   }
   if (isSandBoxed) {
     securityFlags |= nsILoadInfo::SEC_SANDBOXED;
   }
 
   nsContentPolicyType contentPolicyType = GetContentPolicyType();
@@ -2681,18 +2679,18 @@ nsObjectLoadingContent::OpenChannel()
   }
 
   nsCOMPtr<nsIScriptChannel> scriptChannel = do_QueryInterface(chan);
   if (scriptChannel) {
     // Allow execution against our context if the principals match
     scriptChannel->SetExecutionPolicy(nsIScriptChannel::EXECUTE_NORMAL);
   }
 
-  // AsyncOpen can fail if a file does not exist.
-  rv = chan->AsyncOpen(shim, nullptr);
+  // AsyncOpen2 can fail if a file does not exist.
+  rv = chan->AsyncOpen2(shim);
   NS_ENSURE_SUCCESS(rv, rv);
   LOG(("OBJLC [%p]: Channel opened", this));
   mChannel = chan;
   return NS_OK;
 }
 
 uint32_t
 nsObjectLoadingContent::GetCapabilities() const
--- a/dom/base/test/fileutils.js
+++ b/dom/base/test/fileutils.js
@@ -44,17 +44,17 @@ function testFile(file, contents, test) 
   var fd = new FormData;
   fd.append("hello", "world");
   fd.append("myfile", file);
   xhr.send(fd);
   expectedTestCount++;
 
   // Send file to server using plain XMLHttpRequest
   var xhr = new XMLHttpRequest;
-  xhr.open("POST", "file_XHRSendData.sjs");
+  xhr.open("POST", "../../../dom/xhr/tests/file_XHRSendData.sjs");
   xhr.onload = function (event) {
     is(event.target.getResponseHeader("Result-Content-Type"),
        file.type ? file.type : null,
        "request content-type in XMLHttpRequest send of " + test);
     is(event.target.getResponseHeader("Result-Content-Length"),
        String(file.size),
        "request content-length in XMLHttpRequest send of " + test);
   };
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -53,52 +53,22 @@ support-files =
   bug696301-script-2.js
   bug704320.sjs
   bug704320_counter.sjs
   bug819051.sjs
   chrome/bug418986-1.js
   copypaste.js
   create_file_objects.js
   delayedServerEvents.sjs
-  echo.sjs
   eventsource.resource
   eventsource.resource^headers^
   eventsource_redirect.resource
   eventsource_redirect.resource^headers^
   eventsource_redirect_to.resource
   eventsource_redirect_to.resource^headers^
-  file_XHRDocURI.text
-  file_XHRDocURI.text^headers^
-  file_XHRDocURI.xml
-  file_XHRDocURI.xml^headers^
-  file_XHRDocURI.html
-  file_XHRDocURI.html^headers^
-  file_XHRDocURI.sjs
-  file_XHRResponseURL.js
-  file_XHRResponseURL.sjs
-  file_XHRResponseURL.text
-  file_XHRResponseURL.text^headers^
-  file_XHRResponseURL_nocors.text
-  file_XHRSendData.sjs
-  file_XHRSendData_doc.xml
-  file_XHRSendData_doc.xml^headers^
-  file_XHR_anon.sjs
-  file_XHR_binary1.bin
-  file_XHR_binary1.bin^headers^
-  file_XHR_binary2.bin
-  file_XHR_fail1.txt
-  file_XHR_fail1.txt^headers^
-  file_XHR_header.sjs
-  file_XHR_pass1.xml
-  file_XHR_pass2.txt
-  file_XHR_pass3.txt
-  file_XHR_pass3.txt^headers^
-  file_XHR_system_redirect.html
-  file_XHR_system_redirect.html^headers^
-  file_XHR_timeout.sjs
   file_base_xbl.xml
   file_bug1091883_frame.html
   file_bug1091883_subframe.html
   file_bug1091883_target.html
   file_bug28293.sjs
   file_bug326337.xml
   file_bug326337_inner.html
   file_bug326337_outer.html
@@ -157,20 +127,16 @@ support-files =
   file_bug902350.html
   file_bug902350_frame.html
   file_bug907892.html
   file_bug945152.jar
   file_bug1263696_frame_pass.html
   file_bug1263696_frame_fail.html
   file_bug1274806.html
   file_general_document.html
-  file_html_in_xhr.html
-  file_html_in_xhr.sjs
-  file_html_in_xhr2.html
-  file_html_in_xhr3.html
   file_htmlserializer_1.html
   file_htmlserializer_1_bodyonly.html
   file_htmlserializer_1_format.html
   file_htmlserializer_1_linebreak.html
   file_htmlserializer_1_links.html
   file_htmlserializer_1_nested_body.html
   file_htmlserializer_1_no_body.html
   file_htmlserializer_1_noflag.html
@@ -227,18 +193,16 @@ support-files =
   fileutils.js
   forRemoval.resource
   forRemoval.resource^headers^
   formReset.html
   invalid_accesscontrol.resource
   invalid_accesscontrol.resource^headers^
   mutationobserver_dialog.html
   orientationcommon.js
-  progressserver.sjs
-  responseIdentical.sjs
   script-1_bug597345.sjs
   script-2_bug597345.js
   script_bug602838.sjs
   send_gzip_content.sjs
   somedatas.resource
   somedatas.resource^headers^
   variable_style_sheet.sjs
   viewport_helpers.js
@@ -264,27 +228,26 @@ support-files =
   file_bug1198095.js
   file_bug1250148.sjs
   mozbrowser_api_utils.js
   websocket_helpers.js
   websocket_tests.js
   !/dom/html/test/form_submit_server.sjs
   !/dom/security/test/cors/file_CrossSiteXHR_server.sjs
   !/image/test/mochitest/blue.png
+  !/dom/xhr/tests/file_XHRSendData.sjs
   script_bug1238440.js
 
 [test_anchor_area_referrer.html]
 [test_anchor_area_referrer_changing.html]
 [test_anchor_area_referrer_invalid.html]
 [test_anchor_area_referrer_rel.html]
 [test_anonymousContent_api.html]
 [test_anonymousContent_append_after_reflow.html]
 [test_anonymousContent_canvas.html]
-[test_xhr_overridemimetype_throws_on_invalid_state.html]
-skip-if = buildapp == 'b2g' # Requires webgl support
 [test_anonymousContent_insert.html]
 [test_anonymousContent_manipulate_content.html]
 [test_anonymousContent_style_csp.html]
 [test_applet_alternate_content.html]
 [test_appname_override.html]
 [test_async_setTimeout_stack.html]
 [test_async_setTimeout_stack_across_globals.html]
 [test_audioWindowUtils.html]
@@ -721,17 +684,16 @@ skip-if = e10s || os != 'linux' || build
 [test_gsp-qualified.html]
 [test_gsp-quirks.html]
 [test_gsp-standards.html]
 [test_hasFeature.html]
 [test_history_document_open.html]
 [test_history_state_null.html]
 [test_html_colors_quirks.html]
 [test_html_colors_standards.html]
-[test_html_in_xhr.html]
 [test_htmlcopyencoder.html]
 [test_htmlcopyencoder.xhtml]
 [test_iframe_referrer.html]
 [test_iframe_referrer_changing.html]
 [test_iframe_referrer_invalid.html]
 [test_Image_constructor.html]
 [test_img_referrer.html]
 [test_innersize_scrollport.html]
@@ -822,18 +784,16 @@ skip-if = toolkit == 'android'
 [test_setInterval_uncatchable_exception.html]
 skip-if = debug == false
 [test_settimeout_extra_arguments.html]
 [test_settimeout_inner.html]
 [test_setting_opener.html]
 skip-if = (buildapp == 'b2g' && toolkit != 'gonk') #Bug 931116, b2g desktop specific, initial triage
 [test_simplecontentpolicy.html]
 skip-if = e10s || buildapp == 'b2g' # Bug 1156489.
-[test_sync_xhr_timer.xhtml]
-skip-if = toolkit == 'android'
 [test_text_wholeText.html]
 [test_textnode_normalize_in_selection.html]
 [test_textnode_split_in_selection.html]
 [test_title.html]
 [test_treewalker_nextsibling.xml]
 [test_unknown_url_origin.html]
 [test_url.html]
 [test_url_data.html]
@@ -884,32 +844,10 @@ skip-if = release_build
 [test_window_indexing.html]
 [test_window_named_frame_enumeration.html]
 [test_window_orientation.html]
 skip-if = toolkit != 'gonk'
 [test_window_proto.html]
 [test_writable-replaceable.html]
 [test_x-frame-options.html]
 [test_xbl_userdata.xhtml]
-[test_XHR.html]
-[test_xhr_abort_after_load.html]
-skip-if = toolkit == 'android'
-[test_XHR_anon.html]
-[test_xhr_forbidden_headers.html]
-[test_XHR_header.html]
-[test_XHR_onuploadprogress.html]
-[test_XHR_parameters.html]
-skip-if = buildapp == 'b2g' # b2g(86 total, 4 failing - testing mozAnon - got false, expected true) b2g-debug(86 total, 4 failing - testing mozAnon - got false, expected true) b2g-desktop(86 total, 4 failing - testing mozAnon - got false, expected true)
-[test_xhr_progressevents.html]
-skip-if = toolkit == 'android'
-[test_xhr_send.html]
-[test_xhr_send_readystate.html]
-[test_XHR_system.html]
-skip-if = (buildapp == 'b2g' && (toolkit != 'gonk' || debug)) # b2g-debug(12 total, 2 failing - .mozSystem == true - got false, expected true + ) b2g-desktop(12 total, 2 failing - .mozSystem == true - got false, expected true + )
-[test_XHR_timeout.html]
-skip-if = buildapp == 'b2g' || (android_version == '18' && debug) # b2g(flaky on B2G, bug 960743) b2g-debug(flaky on B2G, bug 960743) b2g-desktop(flaky on B2G, bug 960743)
-support-files = test_XHR_timeout.js
-[test_xhr_withCredentials.html]
-[test_XHRDocURI.html]
-[test_XHRResponseURL.html]
-[test_XHRSendData.html]
 [test_youtube_flash_embed.html]
 # Please keep alphabetical order.
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -1572,40 +1572,24 @@ DOMInterfaces = {
     ],
     # Rename a few things so we don't have both classes and methods
     # with the same name
     'binaryNames': {
         'performance': 'getPerformance',
     },
 },
 
-'XMLHttpRequest': [
-{
-    'nativeType': 'nsXMLHttpRequest',
+'XMLHttpRequest': {
     'implicitJSContext': [ 'send'],
 },
-{
-    'workers': True,
-}],
 
 'XMLHttpRequestEventTarget': {
-    'nativeType': 'nsXHREventTarget',
-    'headerFile': 'nsXMLHttpRequest.h',
     'concrete': False
 },
 
-'XMLHttpRequestUpload': [
-{
-    'nativeType': 'nsXMLHttpRequestUpload',
-    'headerFile': 'nsXMLHttpRequest.h'
-},
-{
-    'workers': True,
-}],
-
 'XMLSerializer': {
     'nativeType': 'nsDOMSerializer',
 },
 
 'XPathEvaluator': {
     'wrapperCache': False
 },
 
--- a/dom/bindings/Errors.msg
+++ b/dom/bindings/Errors.msg
@@ -90,12 +90,13 @@ MSG_DEF(MSG_PROMISE_ARG_NOT_ITERABLE, 1,
 MSG_DEF(MSG_IS_NOT_PROMISE, 1, JSEXN_TYPEERR, "{0} is not a Promise")
 MSG_DEF(MSG_SW_INSTALL_ERROR, 2, JSEXN_TYPEERR, "ServiceWorker script at {0} for scope {1} encountered an error during installation.")
 MSG_DEF(MSG_SW_SCRIPT_THREW, 2, JSEXN_TYPEERR, "ServiceWorker script at {0} for scope {1} threw an exception during script evaluation.")
 MSG_DEF(MSG_TYPEDARRAY_IS_SHARED, 1, JSEXN_TYPEERR, "{0} can't be a typed array on SharedArrayBuffer")
 MSG_DEF(MSG_CACHE_ADD_FAILED_RESPONSE, 3, JSEXN_TYPEERR, "Cache got {0} response with bad status {1} while trying to add request {2}")
 MSG_DEF(MSG_SW_UPDATE_BAD_REGISTRATION, 2, JSEXN_TYPEERR, "Failed to update the ServiceWorker for scope {0] because the registration has been {1} since the update was scheduled.")
 MSG_DEF(MSG_INVALID_DURATION_ERROR, 1, JSEXN_TYPEERR, "Invalid duration '{0}'.")
 MSG_DEF(MSG_INVALID_EASING_ERROR, 1, JSEXN_TYPEERR, "Invalid easing '{0}'.")
+MSG_DEF(MSG_INVALID_SPACING_MODE_ERROR, 1, JSEXN_TYPEERR, "Invalid spacing '{0}'.")
 MSG_DEF(MSG_USELESS_SETTIMEOUT, 1, JSEXN_TYPEERR, "Useless {0} call (missing quotes around argument?)")
 MSG_DEF(MSG_TOKENLIST_NO_SUPPORTED_TOKENS, 2, JSEXN_TYPEERR, "{0} attribute of <{1}> does not define any supported tokens")
 MSG_DEF(MSG_CACHE_STREAM_CLOSED, 0, JSEXN_TYPEERR, "Response body is a cache file stream that has already been closed.")
 MSG_DEF(MSG_ONLY_IF_CACHED_WITHOUT_SAME_ORIGIN, 1, JSEXN_TYPEERR, "Request mode '{0}' was used, but request cache mode 'only-if-cached' can only be used with request mode 'same-origin'.")
--- a/dom/bluetooth/common/webapi/BluetoothPbapRequestHandle.h
+++ b/dom/bluetooth/common/webapi/BluetoothPbapRequestHandle.h
@@ -5,17 +5,16 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_bluetooth_bluetoothpbaprequesthandle_h
 #define mozilla_dom_bluetooth_bluetoothpbaprequesthandle_h
 
 #include "nsCOMPtr.h"
 #include "mozilla/dom/bluetooth/BluetoothCommon.h"
 #include "mozilla/dom/DOMRequest.h"
-#include "mozilla/dom/BlobSet.h"
 
 namespace mozilla {
   class ErrorResult;
   namespace dom {
     class Blob;
     class DOMRequest;
   }
 }
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -62,16 +62,17 @@
 #include "WebGLExtensions.h"
 #include "WebGLFramebuffer.h"
 #include "WebGLMemoryTracker.h"
 #include "WebGLObjectModel.h"
 #include "WebGLProgram.h"
 #include "WebGLQuery.h"
 #include "WebGLSampler.h"
 #include "WebGLShader.h"
+#include "WebGLSync.h"
 #include "WebGLTimerQuery.h"
 #include "WebGLTransformFeedback.h"
 #include "WebGLVertexArray.h"
 #include "WebGLVertexAttribData.h"
 
 #ifdef MOZ_WIDGET_COCOA
 #include "nsCocoaFeatures.h"
 #endif
@@ -261,16 +262,17 @@ WebGLContext::DestroyResourcesAndContext
 
     ClearLinkedList(mBuffers);
     ClearLinkedList(mFramebuffers);
     ClearLinkedList(mPrograms);
     ClearLinkedList(mQueries);
     ClearLinkedList(mRenderbuffers);
     ClearLinkedList(mSamplers);
     ClearLinkedList(mShaders);
+    ClearLinkedList(mSyncs);
     ClearLinkedList(mTextures);
     ClearLinkedList(mTimerQueries);
     ClearLinkedList(mTransformFeedbacks);
     ClearLinkedList(mVertexArrays);
 
     //////
 
     mFakeBlack_2D_0000       = nullptr;
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -95,16 +95,17 @@ class WebGLBuffer;
 class WebGLExtensionBase;
 class WebGLFramebuffer;
 class WebGLProgram;
 class WebGLQuery;
 class WebGLRenderbuffer;
 class WebGLSampler;
 class WebGLShader;
 class WebGLShaderPrecisionFormat;
+class WebGLSync;
 class WebGLTexture;
 class WebGLTimerQuery;
 class WebGLTransformFeedback;
 class WebGLUniformLocation;
 class WebGLVertexArray;
 
 namespace dom {
 class Element;
@@ -1397,16 +1398,17 @@ protected:
 
     LinkedList<WebGLBuffer> mBuffers;
     LinkedList<WebGLFramebuffer> mFramebuffers;
     LinkedList<WebGLProgram> mPrograms;
     LinkedList<WebGLQuery> mQueries;
     LinkedList<WebGLRenderbuffer> mRenderbuffers;
     LinkedList<WebGLSampler> mSamplers;
     LinkedList<WebGLShader> mShaders;
+    LinkedList<WebGLSync> mSyncs;
     LinkedList<WebGLTexture> mTextures;
     LinkedList<WebGLTimerQuery> mTimerQueries;
     LinkedList<WebGLTransformFeedback> mTransformFeedbacks;
     LinkedList<WebGLVertexArray> mVertexArrays;
 
     WebGLRefPtr<WebGLTransformFeedback> mDefaultTransformFeedback;
     WebGLRefPtr<WebGLVertexArray> mDefaultVertexArray;
 
--- a/dom/canvas/WebGLContextDraw.cpp
+++ b/dom/canvas/WebGLContextDraw.cpp
@@ -839,16 +839,32 @@ WebGLContext::FakeBlackTexture::FakeBlac
     // minimize the risk of running into a driver bug in texImage2D, as it is a bit
     // unusual maybe to create 1x1 textures, and the stack may not have the alignment that
     // TexImage2D expects.
 
     const webgl::DriverUnpackInfo dui = {texFormat, texFormat, LOCAL_GL_UNSIGNED_BYTE};
     UniqueBuffer zeros = moz_xcalloc(1, 16); // Infallible allocation.
 
     MOZ_ASSERT(gl->IsCurrent());
+    auto logANGLEError = [](GLenum source, GLenum type, GLuint id, GLenum severity,
+                            GLsizei length, const GLchar* message, const GLvoid* userParam)
+    {
+        gfxCriticalNote << message;
+    };
+
+    if (gl->IsANGLE()) {
+      gl->fEnable(LOCAL_GL_DEBUG_OUTPUT);
+      gl->fDebugMessageCallback(logANGLEError, nullptr);
+      gl->fDebugMessageControl(LOCAL_GL_DONT_CARE,
+                               LOCAL_GL_DONT_CARE,
+                               LOCAL_GL_DONT_CARE,
+                               0, nullptr,
+                               true);
+    }
+
     if (target == LOCAL_GL_TEXTURE_CUBE_MAP) {
         for (int i = 0; i < 6; ++i) {
             const TexImageTarget curTarget = LOCAL_GL_TEXTURE_CUBE_MAP_POSITIVE_X + i;
             const GLenum error = DoTexImage(mGL, curTarget.get(), 0, &dui, 1, 1, 1,
                                             zeros.get());
             if (error) {
                 const nsPrintfCString text("DoTexImage failed with `error`: 0x%04x, "
                                            "for `curTarget`: 0x%04x, "
@@ -867,16 +883,20 @@ WebGLContext::FakeBlackTexture::FakeBlac
                                        "for `target`: 0x%04x, "
                                        "`dui`: {0x%04x, 0x%04x, 0x%04x}.",
                                        error, target.get(), dui.internalFormat,
                                        dui.unpackFormat, dui.unpackType);
             gfxCriticalError() << text.BeginReading();
             MOZ_CRASH("GFX: Unexpected error during FakeBlack creation.");
         }
     }
+
+    if (gl->IsANGLE()) {
+      gl->fDisable(LOCAL_GL_DEBUG_OUTPUT);
+    }
 }
 
 WebGLContext::FakeBlackTexture::~FakeBlackTexture()
 {
     mGL->MakeCurrent();
     mGL->fDeleteTextures(1, &mGLName);
 }
 
--- a/dom/canvas/WebGLSync.cpp
+++ b/dom/canvas/WebGLSync.cpp
@@ -9,31 +9,32 @@
 #include "mozilla/dom/WebGL2RenderingContextBinding.h"
 #include "WebGLContext.h"
 
 namespace mozilla {
 
 WebGLSync::WebGLSync(WebGLContext* webgl, GLenum condition, GLbitfield flags)
     : WebGLContextBoundObject(webgl)
 {
+   mContext->mSyncs.insertBack(this);
    mGLName = mContext->gl->fFenceSync(condition, flags);
 }
 
 WebGLSync::~WebGLSync()
 {
     DeleteOnce();
 }
 
 void
 WebGLSync::Delete()
 {
     mContext->MakeContextCurrent();
     mContext->gl->fDeleteSync(mGLName);
     mGLName = 0;
-    LinkedListElement<WebGLSync>::remove();
+    LinkedListElement<WebGLSync>::removeFrom(mContext->mSyncs);
 }
 
 WebGLContext*
 WebGLSync::GetParentObject() const
 {
     return mContext;
 }
 
deleted file mode 100644
--- a/dom/gamepad/GamepadFunctions.cpp
+++ /dev/null
@@ -1,106 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "mozilla/dom/GamepadFunctions.h"
-#include "mozilla/dom/GamepadService.h"
-#include "nsHashKeys.h"
-#include "mozilla/dom/ContentParent.h"
-#include "mozilla/unused.h"
-
-namespace mozilla {
-namespace dom {
-namespace GamepadFunctions {
-
-namespace {
-// Increments as gamepads are added
-uint32_t gGamepadIndex = 0;
-}
-
-template<class T>
-void
-NotifyGamepadChange(const T& aInfo)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(XRE_IsParentProcess());
-  GamepadChangeEvent e(aInfo);
-  nsTArray<ContentParent*> t;
-  ContentParent::GetAll(t);
-  for(uint32_t i = 0; i < t.Length(); ++i) {
-    Unused << t[i]->SendGamepadUpdate(e);
-  }
-  // If we have a GamepadService in the main process, send directly to it.
-  if (GamepadService::IsServiceRunning()) {
-    RefPtr<GamepadService> svc = GamepadService::GetService();
-    svc->Update(e);
-  }
-}
-
-uint32_t
-AddGamepad(const char* aID,
-           GamepadMappingType aMapping,
-           uint32_t aNumButtons, uint32_t aNumAxes)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(XRE_IsParentProcess());
-
-  int index = gGamepadIndex;
-  gGamepadIndex++;
-  GamepadAdded a(NS_ConvertUTF8toUTF16(nsDependentCString(aID)), index,
-                 (uint32_t)aMapping, aNumButtons, aNumAxes);
-  NotifyGamepadChange<GamepadAdded>(a);
-  return index;
-}
-
-void
-RemoveGamepad(uint32_t aIndex)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(XRE_IsParentProcess());
-  GamepadRemoved a(aIndex);
-  NotifyGamepadChange<GamepadRemoved>(a);
-}
-
-void
-NewButtonEvent(uint32_t aIndex, uint32_t aButton,
-               bool aPressed, double aValue)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(XRE_IsParentProcess());
-  GamepadButtonInformation a(aIndex, aButton, aPressed, aValue);
-  NotifyGamepadChange<GamepadButtonInformation>(a);
-}
-
-void
-NewButtonEvent(uint32_t aIndex, uint32_t aButton,
-               bool aPressed)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(XRE_IsParentProcess());
-  // When only a digital button is available the value will be synthesized.
-  NewButtonEvent(aIndex, aButton, aPressed, aPressed ? 1.0L : 0.0L);
-}
-
-void
-NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis,
-                 double aValue)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(XRE_IsParentProcess());
-  GamepadAxisInformation a(aIndex, aAxis, aValue);
-  NotifyGamepadChange<GamepadAxisInformation>(a);
-}
-
-void
-ResetGamepadIndexes()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(XRE_IsParentProcess());
-  gGamepadIndex = 0;
-}
-
-} // namespace GamepadFunctions
-} // namespace dom
-} // namespace mozilla
deleted file mode 100644
--- a/dom/gamepad/GamepadFunctions.h
+++ /dev/null
@@ -1,47 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef mozilla_dom_GamepadFunctions_h_
-#define mozilla_dom_GamepadFunctions_h_
-
-#include "mozilla/dom/GamepadBinding.h"
-
-namespace mozilla {
-namespace dom {
-namespace GamepadFunctions {
-
-// Functions for building and transmitting IPDL messages through the HAL
-// sandbox. Used by platform specific Gamepad implementations
-
-// Add a gamepad to the list of known gamepads, and return its index.
-uint32_t AddGamepad(const char* aID, GamepadMappingType aMapping,
-                    uint32_t aNumButtons, uint32_t aNumAxes);
-// Remove the gamepad at |aIndex| from the list of known gamepads.
-void RemoveGamepad(uint32_t aIndex);
-
-// Update the state of |aButton| for the gamepad at |aIndex| for all
-// windows that are listening and visible, and fire one of
-// a gamepadbutton{up,down} event at them as well.
-// aPressed is used for digital buttons, aValue is for analog buttons.
-void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed,
-                    double aValue);
-// When only a digital button is available the value will be synthesized.
-void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed);
-
-// Update the state of |aAxis| for the gamepad at |aIndex| for all
-// windows that are listening and visible, and fire a gamepadaxismove
-// event at them as well.
-void NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis, double aValue);
-
-// When shutting down the platform communications for gamepad, also reset the
-// indexes.
-void ResetGamepadIndexes();
-
-} // namespace GamepadFunctions
-} // namespace dom
-} // namespace mozilla
-
-#endif
new file mode 100644
--- /dev/null
+++ b/dom/gamepad/GamepadManager.cpp
@@ -0,0 +1,596 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/GamepadManager.h"
+
+#include "mozilla/dom/Gamepad.h"
+#include "mozilla/dom/GamepadAxisMoveEvent.h"
+#include "mozilla/dom/GamepadButtonEvent.h"
+#include "mozilla/dom/GamepadEvent.h"
+#include "mozilla/dom/GamepadEventChannelChild.h"
+#include "mozilla/dom/GamepadMonitoring.h"
+
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPtr.h"
+
+#include "nsAutoPtr.h"
+#include "nsGlobalWindow.h"
+#include "nsIDOMEvent.h"
+#include "nsIDOMDocument.h"
+#include "nsIDOMWindow.h"
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+#include "nsIServiceManager.h"
+#include "nsThreadUtils.h"
+#include "mozilla/Services.h"
+#include "mozilla/unused.h"
+
+#include <cstddef>
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+const char* kGamepadEnabledPref = "dom.gamepad.enabled";
+const char* kGamepadEventsEnabledPref =
+  "dom.gamepad.non_standard_events.enabled";
+
+const nsTArray<RefPtr<nsGlobalWindow>>::index_type NoIndex =
+  nsTArray<RefPtr<nsGlobalWindow>>::NoIndex;
+
+bool sShutdown = false;
+
+StaticRefPtr<GamepadManager> gGamepadManagerSingleton;
+
+} // namespace
+
+NS_IMPL_ISUPPORTS(GamepadManager, nsIObserver)
+
+GamepadManager::GamepadManager()
+  : mEnabled(false),
+    mNonstandardEventsEnabled(false),
+    mShuttingDown(false),
+    mChild(nullptr)
+{}
+
+nsresult
+GamepadManager::Init()
+{
+  mEnabled = IsAPIEnabled();
+  mNonstandardEventsEnabled =
+    Preferences::GetBool(kGamepadEventsEnabledPref, false);
+  nsCOMPtr<nsIObserverService> observerService =
+    mozilla::services::GetObserverService();
+
+  if (NS_WARN_IF(!observerService)) {
+    return NS_ERROR_FAILURE;
+  }
+
+  nsresult rv;
+  rv = observerService->AddObserver(this,
+                                    NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID,
+                                    false);
+
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+GamepadManager::Observe(nsISupports* aSubject,
+                        const char* aTopic,
+                        const char16_t* aData)
+{
+  nsCOMPtr<nsIObserverService> observerService =
+    mozilla::services::GetObserverService();
+  if (observerService) {
+    observerService->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
+  }
+  BeginShutdown();
+  return NS_OK;
+}
+
+void
+GamepadManager::StopMonitoring()
+{
+  if(mChild) {
+    mChild->SendGamepadListenerRemoved();
+    mChild = nullptr;
+  }
+  mGamepads.Clear();
+}
+
+void
+GamepadManager::BeginShutdown()
+{
+  mShuttingDown = true;
+  StopMonitoring();
+  // Don't let windows call back to unregister during shutdown
+  for (uint32_t i = 0; i < mListeners.Length(); i++) {
+    mListeners[i]->SetHasGamepadEventListener(false);
+  }
+  mListeners.Clear();
+  sShutdown = true;
+}
+
+void
+GamepadManager::AddListener(nsGlobalWindow* aWindow)
+{
+  MOZ_ASSERT(aWindow);
+  MOZ_ASSERT(aWindow->IsInnerWindow());
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!mEnabled || mShuttingDown) {
+    return;
+  }
+
+  if (mListeners.IndexOf(aWindow) != NoIndex) {
+    return; // already exists
+  }
+
+  mListeners.AppendElement(aWindow);
+
+  // IPDL child has been created
+  if (mChild) {
+    return;
+  }
+  PBackgroundChild *actor = BackgroundChild::GetForCurrentThread();
+  //Try to get the PBackground Child actor
+  if (actor) {
+    ActorCreated(actor);
+  } else {
+    Unused << BackgroundChild::GetOrCreateForCurrentThread(this);
+  }
+}
+
+void
+GamepadManager::RemoveListener(nsGlobalWindow* aWindow)
+{
+  MOZ_ASSERT(aWindow);
+  MOZ_ASSERT(aWindow->IsInnerWindow());
+
+  if (mShuttingDown) {
+    // Doesn't matter at this point. It's possible we're being called
+    // as a result of our own destructor here, so just bail out.
+    return;
+  }
+
+  if (mListeners.IndexOf(aWindow) == NoIndex) {
+    return; // doesn't exist
+  }
+
+  mListeners.RemoveElement(aWindow);
+
+  if (mListeners.IsEmpty()) {
+    StopMonitoring();
+  }
+}
+
+already_AddRefed<Gamepad>
+GamepadManager::GetGamepad(uint32_t aIndex) const
+{
+  RefPtr<Gamepad> gamepad;
+  if (mGamepads.Get(aIndex, getter_AddRefs(gamepad))) {
+    return gamepad.forget();
+  }
+
+  return nullptr;
+}
+
+void
+GamepadManager::AddGamepad(uint32_t aIndex,
+                           const nsAString& aId,
+                           GamepadMappingType aMapping,
+                           uint32_t aNumButtons,
+                           uint32_t aNumAxes)
+{
+  //TODO: bug 852258: get initial button/axis state
+  RefPtr<Gamepad> gamepad =
+    new Gamepad(nullptr,
+                aId,
+                0, // index is set by global window
+                aMapping,
+                aNumButtons,
+                aNumAxes);
+
+  // We store the gamepad related to its index given by the parent process,
+  // and no duplicate index is allowed.
+  MOZ_ASSERT(!mGamepads.Get(aIndex, nullptr));
+  mGamepads.Put(aIndex, gamepad);
+  NewConnectionEvent(aIndex, true);
+}
+
+void
+GamepadManager::RemoveGamepad(uint32_t aIndex)
+{
+  RefPtr<Gamepad> gamepad = GetGamepad(aIndex);
+  if (!gamepad) {
+    NS_WARNING("Trying to delete gamepad with invalid index");
+    return;
+  }
+  gamepad->SetConnected(false);
+  NewConnectionEvent(aIndex, false);
+  mGamepads.Remove(aIndex);
+}
+
+void
+GamepadManager::NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed,
+                               double aValue)
+{
+  if (mShuttingDown) {
+    return;
+  }
+
+  RefPtr<Gamepad> gamepad = GetGamepad(aIndex);
+  if (!gamepad) {
+    return;
+  }
+
+  gamepad->SetButton(aButton, aPressed, aValue);
+
+  // Hold on to listeners in a separate array because firing events
+  // can mutate the mListeners array.
+  nsTArray<RefPtr<nsGlobalWindow>> listeners(mListeners);
+  MOZ_ASSERT(!listeners.IsEmpty());
+
+  for (uint32_t i = 0; i < listeners.Length(); i++) {
+
+    MOZ_ASSERT(listeners[i]->IsInnerWindow());
+
+    // Only send events to non-background windows
+    if (!listeners[i]->AsInner()->IsCurrentInnerWindow() ||
+        listeners[i]->GetOuterWindow()->IsBackground()) {
+      continue;
+    }
+
+    bool firstTime = MaybeWindowHasSeenGamepad(listeners[i], aIndex);
+
+    RefPtr<Gamepad> listenerGamepad = listeners[i]->GetGamepad(aIndex);
+    if (listenerGamepad) {
+      listenerGamepad->SetButton(aButton, aPressed, aValue);
+      if (firstTime) {
+        FireConnectionEvent(listeners[i], listenerGamepad, true);
+      }
+      if (mNonstandardEventsEnabled) {
+        // Fire event
+        FireButtonEvent(listeners[i], listenerGamepad, aButton, aValue);
+      }
+    }
+  }
+}
+
+void
+GamepadManager::FireButtonEvent(EventTarget* aTarget,
+                                Gamepad* aGamepad,
+                                uint32_t aButton,
+                                double aValue)
+{
+  nsString name = aValue == 1.0L ? NS_LITERAL_STRING("gamepadbuttondown") :
+                                   NS_LITERAL_STRING("gamepadbuttonup");
+  GamepadButtonEventInit init;
+  init.mBubbles = false;
+  init.mCancelable = false;
+  init.mGamepad = aGamepad;
+  init.mButton = aButton;
+  RefPtr<GamepadButtonEvent> event =
+    GamepadButtonEvent::Constructor(aTarget, name, init);
+
+  event->SetTrusted(true);
+
+  bool defaultActionEnabled = true;
+  aTarget->DispatchEvent(event, &defaultActionEnabled);
+}
+
+void
+GamepadManager::NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis, double aValue)
+{
+  if (mShuttingDown) {
+    return;
+  }
+
+  RefPtr<Gamepad> gamepad = GetGamepad(aIndex);
+  if (!gamepad) {
+    return;
+  }
+  gamepad->SetAxis(aAxis, aValue);
+
+  // Hold on to listeners in a separate array because firing events
+  // can mutate the mListeners array.
+  nsTArray<RefPtr<nsGlobalWindow>> listeners(mListeners);
+  MOZ_ASSERT(!listeners.IsEmpty());
+
+  for (uint32_t i = 0; i < listeners.Length(); i++) {
+
+    MOZ_ASSERT(listeners[i]->IsInnerWindow());
+
+    // Only send events to non-background windows
+    if (!listeners[i]->AsInner()->IsCurrentInnerWindow() ||
+        listeners[i]->GetOuterWindow()->IsBackground()) {
+      continue;
+    }
+
+    bool firstTime = MaybeWindowHasSeenGamepad(listeners[i], aIndex);
+
+    RefPtr<Gamepad> listenerGamepad = listeners[i]->GetGamepad(aIndex);
+    if (listenerGamepad) {
+      listenerGamepad->SetAxis(aAxis, aValue);
+      if (firstTime) {
+        FireConnectionEvent(listeners[i], listenerGamepad, true);
+      }
+      if (mNonstandardEventsEnabled) {
+        // Fire event
+        FireAxisMoveEvent(listeners[i], listenerGamepad, aAxis, aValue);
+      }
+    }
+  }
+}
+
+void
+GamepadManager::FireAxisMoveEvent(EventTarget* aTarget,
+                                  Gamepad* aGamepad,
+                                  uint32_t aAxis,
+                                  double aValue)
+{
+  GamepadAxisMoveEventInit init;
+  init.mBubbles = false;
+  init.mCancelable = false;
+  init.mGamepad = aGamepad;
+  init.mAxis = aAxis;
+  init.mValue = aValue;
+  RefPtr<GamepadAxisMoveEvent> event =
+    GamepadAxisMoveEvent::Constructor(aTarget,
+                                      NS_LITERAL_STRING("gamepadaxismove"),
+                                      init);
+
+  event->SetTrusted(true);
+
+  bool defaultActionEnabled = true;
+  aTarget->DispatchEvent(event, &defaultActionEnabled);
+}
+
+void
+GamepadManager::NewConnectionEvent(uint32_t aIndex, bool aConnected)
+{
+  if (mShuttingDown) {
+    return;
+  }
+
+  RefPtr<Gamepad> gamepad = GetGamepad(aIndex);
+  if (!gamepad) {
+    return;
+  }
+
+  // Hold on to listeners in a separate array because firing events
+  // can mutate the mListeners array.
+  nsTArray<RefPtr<nsGlobalWindow>> listeners(mListeners);
+  MOZ_ASSERT(!listeners.IsEmpty());
+
+  if (aConnected) {
+    for (uint32_t i = 0; i < listeners.Length(); i++) {
+
+      MOZ_ASSERT(listeners[i]->IsInnerWindow());
+
+      // Only send events to non-background windows
+      if (!listeners[i]->AsInner()->IsCurrentInnerWindow() ||
+          listeners[i]->GetOuterWindow()->IsBackground()) {
+        continue;
+      }
+
+      // We don't fire a connected event here unless the window
+      // has seen input from at least one device.
+      if (!listeners[i]->HasSeenGamepadInput()) {
+        continue;
+      }
+
+      SetWindowHasSeenGamepad(listeners[i], aIndex);
+
+      RefPtr<Gamepad> listenerGamepad = listeners[i]->GetGamepad(aIndex);
+      if (listenerGamepad) {
+        // Fire event
+        FireConnectionEvent(listeners[i], listenerGamepad, aConnected);
+      }
+    }
+  } else {
+    // For disconnection events, fire one at every window that has received
+    // data from this gamepad.
+    for (uint32_t i = 0; i < listeners.Length(); i++) {
+
+      // Even background windows get these events, so we don't have to
+      // deal with the hassle of syncing the state of removed gamepads.
+
+      if (WindowHasSeenGamepad(listeners[i], aIndex)) {
+        RefPtr<Gamepad> listenerGamepad = listeners[i]->GetGamepad(aIndex);
+        if (listenerGamepad) {
+          listenerGamepad->SetConnected(false);
+          // Fire event
+          FireConnectionEvent(listeners[i], listenerGamepad, false);
+          listeners[i]->RemoveGamepad(aIndex);
+        }
+      }
+    }
+  }
+}
+
+void
+GamepadManager::FireConnectionEvent(EventTarget* aTarget,
+                                    Gamepad* aGamepad,
+                                    bool aConnected)
+{
+  nsString name = aConnected ? NS_LITERAL_STRING("gamepadconnected") :
+                               NS_LITERAL_STRING("gamepaddisconnected");
+  GamepadEventInit init;
+  init.mBubbles = false;
+  init.mCancelable = false;
+  init.mGamepad = aGamepad;
+  RefPtr<GamepadEvent> event =
+    GamepadEvent::Constructor(aTarget, name, init);
+
+  event->SetTrusted(true);
+
+  bool defaultActionEnabled = true;
+  aTarget->DispatchEvent(event, &defaultActionEnabled);
+}
+
+void
+GamepadManager::SyncGamepadState(uint32_t aIndex, Gamepad* aGamepad)
+{
+  if (mShuttingDown || !mEnabled) {
+    return;
+  }
+
+  RefPtr<Gamepad> gamepad = GetGamepad(aIndex);
+  if (!gamepad) {
+    return;
+  }
+
+  aGamepad->SyncState(gamepad);
+}
+
+// static
+bool
+GamepadManager::IsServiceRunning()
+{
+  return !!gGamepadManagerSingleton;
+}
+
+// static
+already_AddRefed<GamepadManager>
+GamepadManager::GetService()
+{
+  if (sShutdown) {
+    return nullptr;
+  }
+
+  if (!gGamepadManagerSingleton) {
+    RefPtr<GamepadManager> manager = new GamepadManager();
+    nsresult rv = manager->Init();
+    if(NS_WARN_IF(NS_FAILED(rv))) {
+      return nullptr;
+    }
+    gGamepadManagerSingleton = manager;
+    ClearOnShutdown(&gGamepadManagerSingleton);
+  }
+
+  RefPtr<GamepadManager> service(gGamepadManagerSingleton);
+  return service.forget();
+}
+
+// static
+bool
+GamepadManager::IsAPIEnabled() {
+  return Preferences::GetBool(kGamepadEnabledPref, false);
+}
+
+bool
+GamepadManager::MaybeWindowHasSeenGamepad(nsGlobalWindow* aWindow, uint32_t aIndex)
+{
+  if (!WindowHasSeenGamepad(aWindow, aIndex)) {
+    // This window hasn't seen this gamepad before, so
+    // send a connection event first.
+    SetWindowHasSeenGamepad(aWindow, aIndex);
+    return true;
+  }
+  return false;
+}
+
+bool
+GamepadManager::WindowHasSeenGamepad(nsGlobalWindow* aWindow, uint32_t aIndex) const
+{
+  RefPtr<Gamepad> gamepad = aWindow->GetGamepad(aIndex);
+  return gamepad != nullptr;
+}
+
+void
+GamepadManager::SetWindowHasSeenGamepad(nsGlobalWindow* aWindow,
+                                        uint32_t aIndex,
+                                        bool aHasSeen)
+{
+  MOZ_ASSERT(aWindow);
+  MOZ_ASSERT(aWindow->IsInnerWindow());
+
+  if (mListeners.IndexOf(aWindow) == NoIndex) {
+    // This window isn't even listening for gamepad events.
+    return;
+  }
+
+  if (aHasSeen) {
+    aWindow->SetHasSeenGamepadInput(true);
+    nsCOMPtr<nsISupports> window = ToSupports(aWindow);
+    RefPtr<Gamepad> gamepad = GetGamepad(aIndex);
+    if (!gamepad) {
+      return;
+    }
+    RefPtr<Gamepad> clonedGamepad = gamepad->Clone(window);
+    aWindow->AddGamepad(aIndex, clonedGamepad);
+  } else {
+    aWindow->RemoveGamepad(aIndex);
+  }
+}
+
+void
+GamepadManager::Update(const GamepadChangeEvent& aEvent)
+{
+  if (aEvent.type() == GamepadChangeEvent::TGamepadAdded) {
+    const GamepadAdded& a = aEvent.get_GamepadAdded();
+    AddGamepad(a.index(), a.id(),
+               static_cast<GamepadMappingType>(a.mapping()),
+               a.num_buttons(), a.num_axes());
+    return;
+  }
+  if (aEvent.type() == GamepadChangeEvent::TGamepadRemoved) {
+    const GamepadRemoved& a = aEvent.get_GamepadRemoved();
+    RemoveGamepad(a.index());
+    return;
+  }
+  if (aEvent.type() == GamepadChangeEvent::TGamepadButtonInformation) {
+    const GamepadButtonInformation& a = aEvent.get_GamepadButtonInformation();
+    NewButtonEvent(a.index(), a.button(), a.pressed(), a.value());
+    return;
+  }
+  if (aEvent.type() == GamepadChangeEvent::TGamepadAxisInformation) {
+    const GamepadAxisInformation& a = aEvent.get_GamepadAxisInformation();
+    NewAxisMoveEvent(a.index(), a.axis(), a.value());
+    return;
+  }
+
+  MOZ_CRASH("We shouldn't be here!");
+
+}
+
+//Override nsIIPCBackgroundChildCreateCallback
+void
+GamepadManager::ActorCreated(PBackgroundChild *aActor)
+{
+  MOZ_ASSERT(aActor);
+  GamepadEventChannelChild *child = new GamepadEventChannelChild();
+  PGamepadEventChannelChild *initedChild =
+    aActor->SendPGamepadEventChannelConstructor(child);
+  if (NS_WARN_IF(!initedChild)) {
+    ActorFailed();
+    return;
+  }
+  MOZ_ASSERT(initedChild == child);
+  mChild = child;
+  mChild->SendGamepadListenerAdded();
+}
+
+//Override nsIIPCBackgroundChildCreateCallback
+void
+GamepadManager::ActorFailed()
+{
+  MOZ_CRASH("Gamepad IPC actor create failed!");
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/gamepad/GamepadManager.h
@@ -0,0 +1,142 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_GamepadManager_h_
+#define mozilla_dom_GamepadManager_h_
+
+#include "nsIIPCBackgroundChildCreateCallback.h"
+#include "nsIObserver.h"
+// Needed for GamepadMappingType
+#include "mozilla/dom/GamepadBinding.h"
+
+class nsGlobalWindow;
+
+namespace mozilla {
+namespace dom {
+
+class EventTarget;
+class Gamepad;
+class GamepadChangeEvent;
+class GamepadEventChannelChild;
+
+class GamepadManager final : public nsIObserver,
+                             public nsIIPCBackgroundChildCreateCallback
+{
+ public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+  NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK
+
+  // Returns true if we actually have a service up and running
+  static bool IsServiceRunning();
+  // Get the singleton service
+  static already_AddRefed<GamepadManager> GetService();
+  // Return true if the API is preffed on.
+  static bool IsAPIEnabled();
+
+  void BeginShutdown();
+  void StopMonitoring();
+
+  // Indicate that |aWindow| wants to receive gamepad events.
+  void AddListener(nsGlobalWindow* aWindow);
+  // Indicate that |aWindow| should no longer receive gamepad events.
+  void RemoveListener(nsGlobalWindow* aWindow);
+
+  // Add a gamepad to the list of known gamepads.
+  void AddGamepad(uint32_t aIndex, const nsAString& aID, GamepadMappingType aMapping,
+                  uint32_t aNumButtons, uint32_t aNumAxes);
+
+  // Remove the gamepad at |aIndex| from the list of known gamepads.
+  void RemoveGamepad(uint32_t aIndex);
+
+  // Update the state of |aButton| for the gamepad at |aIndex| for all
+  // windows that are listening and visible, and fire one of
+  // a gamepadbutton{up,down} event at them as well.
+  // aPressed is used for digital buttons, aValue is for analog buttons.
+  void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed,
+                      double aValue);
+
+  // Update the state of |aAxis| for the gamepad at |aIndex| for all
+  // windows that are listening and visible, and fire a gamepadaxismove
+  // event at them as well.
+  void NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis, double aValue);
+
+  // Synchronize the state of |aGamepad| to match the gamepad stored at |aIndex|
+  void SyncGamepadState(uint32_t aIndex, Gamepad* aGamepad);
+
+  // Returns gamepad object if index exists, null otherwise
+  already_AddRefed<Gamepad> GetGamepad(uint32_t aIndex) const;
+
+  // Receive GamepadChangeEvent messages from parent process to fire DOM events
+  void Update(const GamepadChangeEvent& aGamepadEvent);
+
+ protected:
+  GamepadManager();
+  ~GamepadManager() {};
+
+  // Fire a gamepadconnected or gamepaddisconnected event for the gamepad
+  // at |aIndex| to all windows that are listening and have received
+  // gamepad input.
+  void NewConnectionEvent(uint32_t aIndex, bool aConnected);
+
+  // Fire a gamepadaxismove event to the window at |aTarget| for |aGamepad|.
+  void FireAxisMoveEvent(EventTarget* aTarget,
+                         Gamepad* aGamepad,
+                         uint32_t axis,
+                         double value);
+
+  // Fire one of gamepadbutton{up,down} event at the window at |aTarget| for
+  // |aGamepad|.
+  void FireButtonEvent(EventTarget* aTarget,
+                       Gamepad* aGamepad,
+                       uint32_t aButton,
+                       double aValue);
+
+  // Fire one of gamepad{connected,disconnected} event at the window at
+  // |aTarget| for |aGamepad|.
+  void FireConnectionEvent(EventTarget* aTarget,
+                           Gamepad* aGamepad,
+                           bool aConnected);
+
+  // true if this feature is enabled in preferences
+  bool mEnabled;
+  // true if non-standard events are enabled in preferences
+  bool mNonstandardEventsEnabled;
+  // true when shutdown has begun
+  bool mShuttingDown;
+
+  // Gamepad IPDL child
+  // This pointer is only used by this singleton instance and
+  // will be destroyed during the IPDL shutdown chain, so we
+  // don't need to refcount it here
+  GamepadEventChannelChild MOZ_NON_OWNING_REF *mChild;
+
+ private:
+
+  nsresult Init();
+
+  bool MaybeWindowHasSeenGamepad(nsGlobalWindow* aWindow, uint32_t aIndex);
+  // Returns true if we have already sent data from this gamepad
+  // to this window. This should only return true if the user
+  // explicitly interacted with a gamepad while this window
+  // was focused, by pressing buttons or similar actions.
+  bool WindowHasSeenGamepad(nsGlobalWindow* aWindow, uint32_t aIndex) const;
+  // Indicate that a window has recieved data from a gamepad.
+  void SetWindowHasSeenGamepad(nsGlobalWindow* aWindow, uint32_t aIndex,
+                               bool aHasSeen = true);
+
+  // Gamepads connected to the system. Copies of these are handed out
+  // to each window.
+  nsRefPtrHashtable<nsUint32HashKey, Gamepad> mGamepads;
+  // Inner windows that are listening for gamepad events.
+  // has been sent to that window.
+  nsTArray<RefPtr<nsGlobalWindow>> mListeners;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_GamepadManager_h_
--- a/dom/gamepad/GamepadMonitoring.cpp
+++ b/dom/gamepad/GamepadMonitoring.cpp
@@ -1,33 +1,32 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/GamepadMonitoring.h"
-#include "mozilla/dom/GamepadFunctions.h"
-#include "mozilla/dom/PContentParent.h"
+#include "mozilla/dom/GamepadPlatformService.h"
+#include "mozilla/ipc/BackgroundParent.h"
+
+using namespace mozilla::ipc;
 
 namespace mozilla {
 namespace dom {
 
-using namespace GamepadFunctions;
-
 void
 MaybeStopGamepadMonitoring()
 {
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(XRE_IsParentProcess());
-  nsTArray<ContentParent*> t;
-  ContentParent::GetAll(t);
-  for(uint32_t i = 0; i < t.Length(); ++i) {
-    if (t[i]->HasGamepadListener()) {
-      return;
-    }
+  AssertIsOnBackgroundThread();
+  RefPtr<GamepadPlatformService> service =
+    GamepadPlatformService::GetParentService();
+  MOZ_ASSERT(service);
+  if(service->HasGamepadListeners()) {
+    return;
   }
   StopGamepadMonitoring();
-  ResetGamepadIndexes();
+  service->ResetGamepadIndexes();
+  service->MaybeShutdown();
 }
 
 } // namespace dom
 } // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/gamepad/GamepadPlatformService.cpp
@@ -0,0 +1,234 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/GamepadPlatformService.h"
+
+#include "mozilla/dom/GamepadEventChannelParent.h"
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/unused.h"
+
+#include "nsCOMPtr.h"
+#include "nsHashKeys.h"
+#include "nsIThread.h"
+
+using namespace mozilla::ipc;
+
+namespace mozilla {
+namespace dom {
+
+namespace {
+
+// This is the singleton instance of GamepadPlatformService, can be called
+// by both background and monitor thread.
+StaticRefPtr<GamepadPlatformService> gGamepadPlatformServiceSingleton;
+
+} //namepsace
+
+GamepadPlatformService::GamepadPlatformService()
+  : mGamepadIndex(0),
+    mMutex("mozilla::dom::GamepadPlatformService")
+{}
+
+GamepadPlatformService::~GamepadPlatformService()
+{
+  Cleanup();
+}
+
+// static
+already_AddRefed<GamepadPlatformService>
+GamepadPlatformService::GetParentService()
+{
+  //GamepadPlatformService can only be accessed in parent process
+  MOZ_ASSERT(XRE_IsParentProcess());
+  if (!gGamepadPlatformServiceSingleton) {
+    // Only Background Thread can create new GamepadPlatformService instance.
+    if (IsOnBackgroundThread()) {
+      gGamepadPlatformServiceSingleton = new GamepadPlatformService();
+    } else {
+      return nullptr;
+    }
+  }
+  RefPtr<GamepadPlatformService> service(gGamepadPlatformServiceSingleton);
+  return service.forget();
+}
+
+template<class T>
+void
+GamepadPlatformService::NotifyGamepadChange(const T& aInfo)
+{
+  // This method is called by monitor populated in
+  // platform-dependent backends
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  GamepadChangeEvent e(aInfo);
+
+  // mChannelParents may be accessed by background thread in the
+  // same time, we use mutex to prevent possible race condtion
+  MutexAutoLock autoLock(mMutex);
+  for(uint32_t i = 0; i < mChannelParents.Length(); ++i) {
+    mChannelParents[i]->DispatchUpdateEvent(e);
+  }
+}
+
+uint32_t
+GamepadPlatformService::AddGamepad(const char* aID,
+                                   GamepadMappingType aMapping,
+                                   uint32_t aNumButtons, uint32_t aNumAxes)
+{
+  // This method is called by monitor thread populated in
+  // platform-dependent backends
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  uint32_t index = ++mGamepadIndex;
+  GamepadAdded a(NS_ConvertUTF8toUTF16(nsDependentCString(aID)), index,
+                 (uint32_t)aMapping, aNumButtons, aNumAxes);
+  NotifyGamepadChange<GamepadAdded>(a);
+  return index;
+}
+
+void
+GamepadPlatformService::RemoveGamepad(uint32_t aIndex)
+{
+  // This method is called by monitor thread populated in
+  // platform-dependent backends
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(!NS_IsMainThread());
+  GamepadRemoved a(aIndex);
+  NotifyGamepadChange<GamepadRemoved>(a);
+}
+
+void
+GamepadPlatformService::NewButtonEvent(uint32_t aIndex, uint32_t aButton,
+                                       bool aPressed, double aValue)
+{
+  // This method is called by monitor thread populated in
+  // platform-dependent backends
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(!NS_IsMainThread());
+  GamepadButtonInformation a(aIndex, aButton, aPressed, aValue);
+  NotifyGamepadChange<GamepadButtonInformation>(a);
+}
+
+void
+GamepadPlatformService::NewButtonEvent(uint32_t aIndex, uint32_t aButton,
+                                       bool aPressed)
+{
+  // This method is called by monitor thread populated in
+  // platform-dependent backends
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(!NS_IsMainThread());
+  // When only a digital button is available the value will be synthesized.
+  NewButtonEvent(aIndex, aButton, aPressed, aPressed ? 1.0L : 0.0L);
+}
+
+void
+GamepadPlatformService::NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis,
+                                         double aValue)
+{
+  // This method is called by monitor thread populated in
+  // platform-dependent backends
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(!NS_IsMainThread());
+  GamepadAxisInformation a(aIndex, aAxis, aValue);
+  NotifyGamepadChange<GamepadAxisInformation>(a);
+}
+
+void
+GamepadPlatformService::ResetGamepadIndexes()
+{
+  // This method is called by monitor thread populated in
+  // platform-dependent backends
+  MOZ_ASSERT(XRE_IsParentProcess());
+  MOZ_ASSERT(!NS_IsMainThread());
+  mGamepadIndex = 0;
+}
+
+void
+GamepadPlatformService::AddChannelParent(GamepadEventChannelParent* aParent)
+{
+  // mChannelParents can only be modified once GamepadEventChannelParent
+  // is created or removed in Background thread
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aParent);
+  MOZ_ASSERT(!mChannelParents.Contains(aParent));
+
+  // We use mutex here to prevent race condition with monitor thread
+  MutexAutoLock autoLock(mMutex);
+  mChannelParents.AppendElement(aParent);
+}
+
+void
+GamepadPlatformService::RemoveChannelParent(GamepadEventChannelParent* aParent)
+{
+  // mChannelParents can only be modified once GamepadEventChannelParent
+  // is created or removed in Background thread
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(aParent);
+  MOZ_ASSERT(mChannelParents.Contains(aParent));
+
+  // We use mutex here to prevent race condition with monitor thread
+  MutexAutoLock autoLock(mMutex);
+  mChannelParents.RemoveElement(aParent);
+}
+
+bool
+GamepadPlatformService::HasGamepadListeners()
+{
+  // mChannelParents may be accessed by background thread in the
+  // same time, we use mutex to prevent possible race condtion
+  AssertIsOnBackgroundThread();
+
+  // We use mutex here to prevent race condition with monitor thread
+  MutexAutoLock autoLock(mMutex);
+  for (uint32_t i = 0; i < mChannelParents.Length(); i++) {
+    if(mChannelParents[i]->HasGamepadListener()) {
+      return true;
+    }
+  }
+  return false;
+}
+
+void
+GamepadPlatformService::MaybeShutdown()
+{
+  // This method is invoked in MaybeStopGamepadMonitoring when
+  // an IPDL channel is going to be destroyed
+  AssertIsOnBackgroundThread();
+
+  // We have to release gGamepadPlatformServiceSingleton ouside
+  // the mutex as well as making upcoming GetParentService() call
+  // recreate new singleton, so we use this RefPtr to temporarily
+  // hold the reference, postponing the release process until this
+  // method ends.
+  RefPtr<GamepadPlatformService> kungFuDeathGrip;
+
+  bool isChannelParentEmpty;
+  {
+    MutexAutoLock autoLock(mMutex);
+    isChannelParentEmpty = mChannelParents.IsEmpty();
+    if(isChannelParentEmpty) {
+      kungFuDeathGrip = gGamepadPlatformServiceSingleton;
+      gGamepadPlatformServiceSingleton = nullptr;
+    }
+  }
+}
+
+void
+GamepadPlatformService::Cleanup()
+{
+  // This method is called when GamepadPlatformService is
+  // successfully distructed in background thread
+  AssertIsOnBackgroundThread();
+
+  MutexAutoLock autoLock(mMutex);
+  mChannelParents.Clear();
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/gamepad/GamepadPlatformService.h
@@ -0,0 +1,95 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_GamepadPlatformService_h_
+#define mozilla_dom_GamepadPlatformService_h_
+
+#include "mozilla/dom/GamepadBinding.h"
+
+#include "mozilla/Mutex.h"
+#include "mozilla/StaticPtr.h"
+
+namespace mozilla {
+namespace dom {
+
+class GamepadEventChannelParent;
+
+// Platform Service for building and transmitting IPDL messages
+// through the HAL sandbox. Used by platform specific
+// Gamepad implementations
+//
+// This class can be accessed by the following 2 threads :
+// 1. Background thread:
+//    This thread takes charge of IPDL communications
+//    between here and DOM side
+//
+// 2. Monitor Thread:
+//    This thread is populated in platform-dependent backends, which
+//    is in charge of processing gamepad hardware events from OS
+class GamepadPlatformService final
+{
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GamepadPlatformService)
+ public:
+  //Get the singleton service
+  static already_AddRefed<GamepadPlatformService> GetParentService();
+
+  // Add a gamepad to the list of known gamepads, and return its index.
+  uint32_t AddGamepad(const char* aID, GamepadMappingType aMapping,
+                      uint32_t aNumButtons, uint32_t aNumAxes);
+  // Remove the gamepad at |aIndex| from the list of known gamepads.
+  void RemoveGamepad(uint32_t aIndex);
+
+  // Update the state of |aButton| for the gamepad at |aIndex| for all
+  // windows that are listening and visible, and fire one of
+  // a gamepadbutton{up,down} event at them as well.
+  // aPressed is used for digital buttons, aValue is for analog buttons.
+  void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed,
+                      double aValue);
+  // When only a digital button is available the value will be synthesized.
+  void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed);
+
+  // Update the state of |aAxis| for the gamepad at |aIndex| for all
+  // windows that are listening and visible, and fire a gamepadaxismove
+  // event at them as well.
+  void NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis, double aValue);
+
+  // When shutting down the platform communications for gamepad, also reset the
+  // indexes.
+  void ResetGamepadIndexes();
+
+  //Add IPDL parent instance
+  void AddChannelParent(GamepadEventChannelParent* aParent);
+
+  //Remove IPDL parent instance
+  void RemoveChannelParent(GamepadEventChannelParent* aParent);
+
+  bool HasGamepadListeners();
+
+  void MaybeShutdown();
+
+ private:
+  GamepadPlatformService();
+  ~GamepadPlatformService();
+  template<class T> void NotifyGamepadChange(const T& aInfo);
+  void Cleanup();
+
+  // mGamepadIndex can only be accessed by monitor thread
+  uint32_t mGamepadIndex;
+
+  // mChannelParents stores all the GamepadEventChannelParent instances
+  // which may be accessed by both background thread and monitor thread
+  // simultaneously, so we have a mutex to prevent race condition
+  nsTArray<RefPtr<GamepadEventChannelParent>> mChannelParents;
+
+  // This mutex protects mChannelParents from race condition
+  // between background and monitor thread
+  Mutex mMutex;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif
--- a/dom/gamepad/GamepadServiceTest.cpp
+++ b/dom/gamepad/GamepadServiceTest.cpp
@@ -1,86 +1,278 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "GamepadServiceTest.h"
-#include "mozilla/dom/GamepadService.h"
-#include "mozilla/dom/GamepadFunctions.h"
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/unused.h"
+
+#include "mozilla/dom/GamepadManager.h"
+#include "mozilla/dom/GamepadPlatformService.h"
+#include "mozilla/dom/GamepadServiceTestBinding.h"
+#include "mozilla/dom/GamepadTestChannelChild.h"
 
-using namespace mozilla::dom;
+#include "mozilla/ipc/BackgroundChild.h"
+#include "mozilla/ipc/PBackgroundChild.h"
+
+#include "mozilla/unused.h"
+
+#include "nsIObserver.h"
+#include "nsIObserverService.h"
+
+namespace mozilla {
+namespace dom {
 
 /*
  * Implementation of the test service. This is just to provide a simple binding
- * of the GamepadService to JavaScript via XPCOM so that we can write Mochitests
+ * of the GamepadService to JavaScript via WebIDL so that we can write Mochitests
  * that add and remove fake gamepads, avoiding the platform-specific backends.
  */
-NS_IMPL_ISUPPORTS(GamepadServiceTest, nsIGamepadServiceTest)
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(GamepadServiceTest)
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(GamepadServiceTest,
+                                                  DOMEventTargetHelper)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
-GamepadServiceTest* GamepadServiceTest::sSingleton = nullptr;
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(GamepadServiceTest,
+                                                DOMEventTargetHelper)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(GamepadServiceTest)
+  NS_INTERFACE_MAP_ENTRY(nsIIPCBackgroundChildCreateCallback)
+NS_INTERFACE_MAP_END_INHERITING(DOMEventTargetHelper)
+
+NS_IMPL_ADDREF_INHERITED(GamepadServiceTest, DOMEventTargetHelper)
+NS_IMPL_RELEASE_INHERITED(GamepadServiceTest, DOMEventTargetHelper)
 
 // static
 already_AddRefed<GamepadServiceTest>
-GamepadServiceTest::CreateService()
+GamepadServiceTest::CreateTestService(nsPIDOMWindowInner* aWindow)
 {
-  if (sSingleton == nullptr) {
-    sSingleton = new GamepadServiceTest();
-  }
-  RefPtr<GamepadServiceTest> service = sSingleton;
+  MOZ_ASSERT(aWindow);
+  RefPtr<GamepadServiceTest> service = new GamepadServiceTest(aWindow);
+  service->InitPBackgroundActor();
   return service.forget();
 }
 
-GamepadServiceTest::GamepadServiceTest() :
-  mService(GamepadService::GetService())
+void
+GamepadServiceTest::Shutdown()
 {
+  MOZ_ASSERT(!mShuttingDown);
+  mShuttingDown = true;
+  DestroyPBackgroundActor();
+  mWindow = nullptr;
 }
 
-GamepadServiceTest::~GamepadServiceTest()
+GamepadServiceTest::GamepadServiceTest(nsPIDOMWindowInner* aWindow)
+  : mService(GamepadManager::GetService()),
+    mWindow(aWindow),
+    mEventNumber(0),
+    mShuttingDown(false),
+    mChild(nullptr)
+{}
+
+GamepadServiceTest::~GamepadServiceTest() {}
+
+void
+GamepadServiceTest::InitPBackgroundActor()
 {
+  MOZ_ASSERT(!mChild);
+  PBackgroundChild *actor = BackgroundChild::GetForCurrentThread();
+  //Try to get the PBackground Child actor
+  if (actor) {
+    ActorCreated(actor);
+  } else {
+    Unused << BackgroundChild::GetOrCreateForCurrentThread(this);
+  }
 }
 
-NS_IMETHODIMP
-GamepadServiceTest::AddGamepad(const char* aID,
+void
+GamepadServiceTest::DestroyPBackgroundActor()
+{
+  if (mChild) {
+    // If mChild exists, which means that IPDL channel
+    // has been created, our pending operations should
+    // be empty.
+    MOZ_ASSERT(mPendingOperations.IsEmpty());
+    mChild->SendShutdownChannel();
+    mChild = nullptr;
+  } else {
+    // If the IPDL channel has not been created and we
+    // want to destroy it now, just cancel all pending
+    // operations.
+    mPendingOperations.Clear();
+  }
+}
+
+already_AddRefed<Promise>
+GamepadServiceTest::AddGamepad(const nsAString& aID,
                                uint32_t aMapping,
                                uint32_t aNumButtons,
                                uint32_t aNumAxes,
-                               uint32_t* aGamepadIndex)
+                               ErrorResult& aRv)
 {
-  *aGamepadIndex = GamepadFunctions::AddGamepad(aID,
-                                                static_cast<GamepadMappingType>(aMapping),
-                                                aNumButtons,
-                                                aNumAxes);
-  return NS_OK;
+  if (mShuttingDown) {
+    return nullptr;
+  }
+
+  GamepadAdded a(nsString(aID), 0,
+                (uint32_t)aMapping, aNumButtons, aNumAxes);
+  GamepadChangeEvent e(a);
+  nsCOMPtr<nsIGlobalObject> go = do_QueryInterface(mWindow);
+
+  RefPtr<Promise> p = Promise::Create(go, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  uint32_t id = ++mEventNumber;
+  if (mChild) {
+    mChild->AddPromise(id, p);
+    mChild->SendGamepadTestEvent(id, e);
+  } else {
+    PendingOperation op(id, e, p);
+    mPendingOperations.AppendElement(op);
+  }
+  return p.forget();
 }
 
-NS_IMETHODIMP GamepadServiceTest::RemoveGamepad(uint32_t aIndex)
+void
+GamepadServiceTest::RemoveGamepad(uint32_t aIndex)
 {
-  GamepadFunctions::RemoveGamepad(aIndex);
-  return NS_OK;
+  if (mShuttingDown) {
+    return;
+  }
+
+  GamepadRemoved a(aIndex);
+  GamepadChangeEvent e(a);
+
+  uint32_t id = ++mEventNumber;
+  if (mChild) {
+    mChild->SendGamepadTestEvent(id, e);
+  } else {
+    PendingOperation op(id, e);
+    mPendingOperations.AppendElement(op);
+  }
+}
+
+void
+GamepadServiceTest::NewButtonEvent(uint32_t aIndex,
+                                   uint32_t aButton,
+                                   bool aPressed)
+{
+  if (mShuttingDown) {
+    return;
+  }
+
+  GamepadButtonInformation a(aIndex, aButton, aPressed, aPressed ? 1.0 : 0);
+  GamepadChangeEvent e(a);
+
+  uint32_t id = ++mEventNumber;
+  if (mChild) {
+    mChild->SendGamepadTestEvent(id, e);
+  } else {
+    PendingOperation op(id, e);
+    mPendingOperations.AppendElement(op);
+  }
 }
 
-NS_IMETHODIMP GamepadServiceTest::NewButtonEvent(uint32_t aIndex,
-                                                 uint32_t aButton,
-                                                 bool aPressed)
+void
+GamepadServiceTest::NewButtonValueEvent(uint32_t aIndex,
+                                        uint32_t aButton,
+                                        bool aPressed,
+                                        double aValue)
 {
-  GamepadFunctions::NewButtonEvent(aIndex, aButton, aPressed);
-  return NS_OK;
+  if (mShuttingDown) {
+    return;
+  }
+
+  GamepadButtonInformation a(aIndex, aButton, aPressed, aValue);
+  GamepadChangeEvent e(a);
+
+  uint32_t id = ++mEventNumber;
+  if (mChild) {
+    mChild->SendGamepadTestEvent(id, e);
+  } else {
+    PendingOperation op(id, e);
+    mPendingOperations.AppendElement(op);
+  }
+}
+
+void
+GamepadServiceTest::NewAxisMoveEvent(uint32_t aIndex,
+                                     uint32_t aAxis,
+                                     double aValue)
+{
+  if (mShuttingDown) {
+    return;
+  }
+
+  GamepadAxisInformation a(aIndex, aAxis, aValue);
+  GamepadChangeEvent e(a);
+
+  uint32_t id = ++mEventNumber;
+  if (mChild) {
+    mChild->SendGamepadTestEvent(id, e);
+  } else {
+    PendingOperation op(id, e);
+    mPendingOperations.AppendElement(op);
+  }
 }
 
-NS_IMETHODIMP GamepadServiceTest::NewButtonValueEvent(uint32_t aIndex,
-                                                      uint32_t aButton,
-                                                      bool aPressed,
-                                                      double aValue)
+void
+GamepadServiceTest::FlushPendingOperations()
 {
-  GamepadFunctions::NewButtonEvent(aIndex, aButton, aPressed, aValue);
-  return NS_OK;
+  for (uint32_t i=0; i < mPendingOperations.Length(); ++i) {
+    PendingOperation op = mPendingOperations[i];
+    if (op.mPromise) {
+      mChild->AddPromise(op.mID, op.mPromise);
+    }
+    mChild->SendGamepadTestEvent(op.mID, op.mEvent);
+  }
+  mPendingOperations.Clear();
 }
 
-NS_IMETHODIMP GamepadServiceTest::NewAxisMoveEvent(uint32_t aIndex,
-                                                   uint32_t aAxis,
-                                                   double aValue)
+void
+GamepadServiceTest::ActorCreated(PBackgroundChild* aActor)
 {
-  GamepadFunctions::NewAxisMoveEvent(aIndex, aAxis, aValue);
-  return NS_OK;
+  MOZ_ASSERT(aActor);
+  // If we are shutting down, we don't need to create the
+  // IPDL child/parent pair anymore.
+  if (mShuttingDown) {
+    // mPendingOperations should be cleared in
+    // DestroyPBackgroundActor()
+    MOZ_ASSERT(mPendingOperations.IsEmpty());
+    return;
+  }
+
+  mChild = new GamepadTestChannelChild();
+  PGamepadTestChannelChild* initedChild =
+    aActor->SendPGamepadTestChannelConstructor(mChild);
+  if (NS_WARN_IF(!initedChild)) {
+    ActorFailed();
+    return;
+  }
+  FlushPendingOperations();
 }
 
+void
+GamepadServiceTest::ActorFailed()
+{
+  MOZ_CRASH("Failed to create background child actor!");
+}
+
+JSObject*
+GamepadServiceTest::WrapObject(JSContext* aCx, JS::HandleObject aGivenProto)
+{
+  return GamepadServiceTestBinding::Wrap(aCx, this, aGivenProto);
+}
+
+} // dom
+} // mozilla
--- a/dom/gamepad/GamepadServiceTest.h
+++ b/dom/gamepad/GamepadServiceTest.h
@@ -2,43 +2,86 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_GamepadServiceTest_h_
 #define mozilla_dom_GamepadServiceTest_h_
 
-#include "nsIGamepadServiceTest.h"
+#include "nsIIPCBackgroundChildCreateCallback.h"
 
 namespace mozilla {
 namespace dom {
 
-class GamepadService;
+class GamepadChangeEvent;
+class GamepadManager;
+class GamepadTestChannelChild;
+class Promise;
 
 // Service for testing purposes
-class GamepadServiceTest : public nsIGamepadServiceTest
+class GamepadServiceTest final : public DOMEventTargetHelper,
+                                 public nsIIPCBackgroundChildCreateCallback
 {
 public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSIGAMEPADSERVICETEST
+  NS_DECL_NSIIPCBACKGROUNDCHILDCREATECALLBACK
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(GamepadServiceTest,
+                                           DOMEventTargetHelper)
+
+  uint32_t NoMapping() const { return 0; }
+  uint32_t StandardMapping() const { return 1; }
 
-  GamepadServiceTest();
+  already_AddRefed<Promise> AddGamepad(const nsAString& aID,
+                                       uint32_t aMapping,
+                                       uint32_t aNumButtons,
+                                       uint32_t aNumAxes,
+                                       ErrorResult& aRv);
+  void RemoveGamepad(uint32_t aIndex);
+  void NewButtonEvent(uint32_t aIndex, uint32_t aButton, bool aPressed);
+  void NewButtonValueEvent(uint32_t aIndex, uint32_t aButton, bool aPressed, double aValue);
+  void NewAxisMoveEvent(uint32_t aIndex, uint32_t aAxis, double aValue);
+  void Shutdown();
 
-  static already_AddRefed<GamepadServiceTest> CreateService();
+  static already_AddRefed<GamepadServiceTest> CreateTestService(nsPIDOMWindowInner* aWindow);
+  nsPIDOMWindowInner* GetParentObject() const { return mWindow; }
+  JSObject* WrapObject(JSContext* aCx, JS::HandleObject aGivenProto) override;
 
 private:
-  static GamepadServiceTest* sSingleton;
+
+  // We need to asynchronously create IPDL channel, it is possible that
+  // we send commands before the channel is created, so we have to buffer
+  // them until the channel is created in that case.
+  struct PendingOperation {
+    explicit PendingOperation(const uint32_t& aID,
+                              const GamepadChangeEvent& aEvent,
+                              Promise* aPromise = nullptr)
+               : mID(aID), mEvent(aEvent), mPromise(aPromise) {}
+    uint32_t mID;
+    const GamepadChangeEvent& mEvent;
+    RefPtr<Promise> mPromise;
+  };
+
   // Hold a reference to the gamepad service so we don't have to worry about
   // execution order in tests.
-  RefPtr<GamepadService> mService;
-  virtual ~GamepadServiceTest();
+  RefPtr<GamepadManager> mService;
+  nsCOMPtr<nsPIDOMWindowInner> mWindow;
+  nsTArray<PendingOperation> mPendingOperations;
+  uint32_t mEventNumber;
+  bool mShuttingDown;
+
+  // IPDL Channel for us to send test events to GamepadPlatformService, it
+  // will only be used in this singleton class and deleted during the IPDL
+  // shutdown chain
+  GamepadTestChannelChild* MOZ_NON_OWNING_REF mChild;
+
+  explicit GamepadServiceTest(nsPIDOMWindowInner* aWindow);
+  ~GamepadServiceTest();
+  void InitPBackgroundActor();
+  void DestroyPBackgroundActor();
+  void FlushPendingOperations();
+
 };
 
 } // namespace dom
 } // namespace mozilla
 
-#define NS_GAMEPAD_TEST_CID \
-{ 0xfb1fcb57, 0xebab, 0x4cf4, \
-{ 0x96, 0x3b, 0x1e, 0x4d, 0xb8, 0x52, 0x16, 0x96 } }
-#define NS_GAMEPAD_TEST_CONTRACTID "@mozilla.org/gamepad-test;1"
-
 #endif
--- a/dom/gamepad/cocoa/CocoaGamepad.cpp
+++ b/dom/gamepad/cocoa/CocoaGamepad.cpp
@@ -2,30 +2,31 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // mostly derived from the Allegro source code at:
 // http://alleg.svn.sourceforge.net/viewvc/alleg/allegro/branches/4.9/src/macosx/hidjoy.m?revision=13760&view=markup
 
-#include "mozilla/dom/GamepadFunctions.h"
+#include "mozilla/dom/GamepadPlatformService.h"
 #include "mozilla/ArrayUtils.h"
+#include "nsThreadUtils.h"
 #include <CoreFoundation/CoreFoundation.h>
 #include <IOKit/hid/IOHIDBase.h>
 #include <IOKit/hid/IOHIDKeys.h>
 #include <IOKit/hid/IOHIDManager.h>
 
 #include <stdio.h>
 #include <vector>
 
 namespace {
 
 using namespace mozilla;
-using namespace mozilla::dom::GamepadFunctions;
+using namespace mozilla::dom;
 using std::vector;
 
 struct Button {
   int id;
   bool analog;
   IOHIDElementRef element;
   CFIndex min;
   CFIndex max;
@@ -195,37 +196,68 @@ void Gamepad::init(IOHIDDeviceRef device
   }
 }
 
 class DarwinGamepadService {
  private:
   IOHIDManagerRef mManager;
   vector<Gamepad> mGamepads;
 
+  //Workaround to support running in background thread
+  CFRunLoopRef mMonitorRunLoop;
+  nsCOMPtr<nsIThread> mMonitorThread;
+
   static void DeviceAddedCallback(void* data, IOReturn result,
                                   void* sender, IOHIDDeviceRef device);
   static void DeviceRemovedCallback(void* data, IOReturn result,
                                     void* sender, IOHIDDeviceRef device);
   static void InputValueChangedCallback(void* data, IOReturn result,
                                         void* sender, IOHIDValueRef newValue);
 
   void DeviceAdded(IOHIDDeviceRef device);
   void DeviceRemoved(IOHIDDeviceRef device);
   void InputValueChanged(IOHIDValueRef value);
+  void StartupInternal();
 
  public:
   DarwinGamepadService();
   ~DarwinGamepadService();
   void Startup();
   void Shutdown();
+  friend class DarwinGamepadServiceStartupRunnable;
+};
+
+class DarwinGamepadServiceStartupRunnable final : public Runnable
+{
+ private:
+  ~DarwinGamepadServiceStartupRunnable() {}
+  // This Runnable schedules startup of DarwinGamepadService
+  // in a new thread, pointer to DarwinGamepadService is only
+  // used by this Runnable within its thread.
+  DarwinGamepadService MOZ_NON_OWNING_REF *mService;
+ public:
+  explicit DarwinGamepadServiceStartupRunnable(DarwinGamepadService *service)
+             : mService(service) {}
+  NS_IMETHOD Run() override
+  {
+    MOZ_ASSERT(mService);
+    mService->StartupInternal();
+    return NS_OK;
+  }
 };
 
 void
 DarwinGamepadService::DeviceAdded(IOHIDDeviceRef device)
 {
+  RefPtr<GamepadPlatformService> service =
+    GamepadPlatformService::GetParentService();
+  if (!service) {
+    return;
+  }
+
   size_t slot = size_t(-1);
   for (size_t i = 0; i < mGamepads.size(); i++) {
     if (mGamepads[i] == device)
       return;
     if (slot == size_t(-1) && mGamepads[i].empty())
       slot = i;
   }
 
@@ -245,28 +277,34 @@ DarwinGamepadService::DeviceAdded(IOHIDD
   int vendorId, productId;
   CFNumberGetValue(vendorIdRef, kCFNumberIntType, &vendorId);
   CFNumberGetValue(productIdRef, kCFNumberIntType, &productId);
   char product_name[128];
   CFStringGetCString(productRef, product_name,
                      sizeof(product_name), kCFStringEncodingASCII);
   char buffer[256];
   sprintf(buffer, "%x-%x-%s", vendorId, productId, product_name);
-  mGamepads[slot].mSuperIndex = AddGamepad(buffer,
-                                           mozilla::dom::GamepadMappingType::_empty,
-                                           (int)mGamepads[slot].numButtons(),
-                                           (int)mGamepads[slot].numAxes());
+  uint32_t index = service->AddGamepad(buffer,
+                                       mozilla::dom::GamepadMappingType::_empty,
+                                       (int)mGamepads[slot].numButtons(),
+                                       (int)mGamepads[slot].numAxes());
+  mGamepads[slot].mSuperIndex = index;
 }
 
 void
 DarwinGamepadService::DeviceRemoved(IOHIDDeviceRef device)
 {
+  RefPtr<GamepadPlatformService> service =
+    GamepadPlatformService::GetParentService();
+  if (!service) {
+    return;
+  }
   for (size_t i = 0; i < mGamepads.size(); i++) {
     if (mGamepads[i] == device) {
-      RemoveGamepad(mGamepads[i].mSuperIndex);
+      service->RemoveGamepad(mGamepads[i].mSuperIndex);
       mGamepads[i].clear();
       return;
     }
   }
 }
 
 /*
  * Given a value from a d-pad (POV hat in USB HID terminology),
@@ -305,57 +343,66 @@ UnpackDpad(int dpad_value, int min, int 
   if (value > 0 && value < 4) {
     buttons[kRight] = true;
   }
 }
 
 void
 DarwinGamepadService::InputValueChanged(IOHIDValueRef value)
 {
+  RefPtr<GamepadPlatformService> service =
+    GamepadPlatformService::GetParentService();
+  if (!service) {
+    return;
+  }
+
   uint32_t value_length = IOHIDValueGetLength(value);
   if (value_length > 4) {
     // Workaround for bizarre issue with PS3 controllers that try to return
     // massive (30+ byte) values and crash IOHIDValueGetIntegerValue
     return;
   }
   IOHIDElementRef element = IOHIDValueGetElement(value);
   IOHIDDeviceRef device = IOHIDElementGetDevice(element);
+
   for (unsigned i = 0; i < mGamepads.size(); i++) {
     Gamepad &gamepad = mGamepads[i];
     if (gamepad == device) {
       if (gamepad.isDpad(element)) {
         const dpad_buttons& oldState = gamepad.getDpadState();
         dpad_buttons newState = { false, false, false, false };
         UnpackDpad(IOHIDValueGetIntegerValue(value),
                    IOHIDElementGetLogicalMin(element),
                    IOHIDElementGetLogicalMax(element),
                    newState);
         const int numButtons = gamepad.numButtons();
         for (unsigned b = 0; b < ArrayLength(newState); b++) {
           if (newState[b] != oldState[b]) {
-            NewButtonEvent(gamepad.mSuperIndex, numButtons - 4 + b, newState[b]);
+            service->NewButtonEvent(gamepad.mSuperIndex,
+                                    numButtons - 4 + b,
+                                    newState[b]);
           }
         }
         gamepad.setDpadState(newState);
       } else if (const Axis* axis = gamepad.lookupAxis(element)) {
         double d = IOHIDValueGetIntegerValue(value);
         double v = 2.0f * (d - axis->min) /
           (double)(axis->max - axis->min) - 1.0f;
-        NewAxisMoveEvent(gamepad.mSuperIndex, axis->id, v);
+        service->NewAxisMoveEvent(gamepad.mSuperIndex, axis->id, v);
       } else if (const Button* button = gamepad.lookupButton(element)) {
         int iv = IOHIDValueGetIntegerValue(value);
         bool pressed = iv != 0;
         double v = 0;
         if (button->analog) {
           double dv = iv;
           v = (dv - button->min) / (double)(button->max - button->min);
         } else {
           v = pressed ? 1.0 : 0.0;
         }
-        NewButtonEvent(gamepad.mSuperIndex, button->id, pressed, v);
+        service->NewButtonEvent(gamepad.mSuperIndex, button->id, pressed, v);
       }
       return;
     }
   }
 }
 
 void
 DarwinGamepadService::DeviceAddedCallback(void* data, IOReturn result,
@@ -417,17 +464,18 @@ MatchingDictionary(UInt32 inUsagePage, U
 DarwinGamepadService::DarwinGamepadService() : mManager(nullptr) {}
 
 DarwinGamepadService::~DarwinGamepadService()
 {
   if (mManager != nullptr)
     CFRelease(mManager);
 }
 
-void DarwinGamepadService::Startup()
+void
+DarwinGamepadService::StartupInternal()
 {
   if (mManager != nullptr)
     return;
 
   IOHIDManagerRef manager = IOHIDManagerCreate(kCFAllocatorDefault,
                                                kIOHIDOptionsTypeNone);
 
   CFMutableDictionaryRef criteria_arr[2];
@@ -474,26 +522,44 @@ void DarwinGamepadService::Startup()
                                   kCFRunLoopDefaultMode);
   IOReturn rv = IOHIDManagerOpen(manager, kIOHIDOptionsTypeNone);
   if (rv != kIOReturnSuccess) {
     CFRelease(manager);
     return;
   }
 
   mManager = manager;
+
+  // We held the handle of the CFRunLoop to make sure we
+  // can shut it down explicitly by CFRunLoopStop in another
+  // thread.
+  mMonitorRunLoop = CFRunLoopGetCurrent();
+
+  // CFRunLoopRun() is a blocking message loop when it's called in
+  // non-main thread so this thread cannot receive any other runnables
+  // and nsITimer timeout events after it's called.
+  CFRunLoopRun();
+}
+
+void DarwinGamepadService::Startup()
+{
+  Unused << NS_NewThread(getter_AddRefs(mMonitorThread),
+                         new DarwinGamepadServiceStartupRunnable(this));
 }
 
 void DarwinGamepadService::Shutdown()
 {
   IOHIDManagerRef manager = (IOHIDManagerRef)mManager;
+  CFRunLoopStop(mMonitorRunLoop);
   if (manager) {
     IOHIDManagerClose(manager, 0);
     CFRelease(manager);
     mManager = nullptr;
   }
+  mMonitorThread->Shutdown();
 }
 
 } // namespace
 
 namespace mozilla {
 namespace dom {
 
 DarwinGamepadService* gService = nullptr;
new file mode 100644
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadEventChannelChild.cpp
@@ -0,0 +1,42 @@
+/* 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 "GamepadEventChannelChild.h"
+#include "mozilla/dom/GamepadManager.h"
+
+namespace mozilla {
+namespace dom{
+
+namespace {
+
+class GamepadUpdateRunnable final : public Runnable
+{
+ public:
+  explicit GamepadUpdateRunnable(const GamepadChangeEvent& aGamepadEvent)
+             : mEvent(aGamepadEvent) {}
+  NS_IMETHOD Run() override
+  {
+    RefPtr<GamepadManager> svc(GamepadManager::GetService());
+    if (svc) {
+      svc->Update(mEvent);
+    }
+    return NS_OK;
+  }
+ protected:
+  GamepadChangeEvent mEvent;
+};
+
+} // namespace
+
+bool
+GamepadEventChannelChild::RecvGamepadUpdate(
+                                       const GamepadChangeEvent& aGamepadEvent)
+{
+  nsresult rv;
+  rv = NS_DispatchToMainThread(new GamepadUpdateRunnable(aGamepadEvent));
+  NS_WARN_IF(NS_FAILED(rv));
+  return true;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadEventChannelChild.h
@@ -0,0 +1,24 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "mozilla/dom/PGamepadEventChannelChild.h"
+
+#ifndef mozilla_dom_GamepadEventChannelChild_h_
+#define mozilla_dom_GamepadEventChannelChild_h_
+
+namespace mozilla{
+namespace dom{
+
+class GamepadEventChannelChild final : public PGamepadEventChannelChild
+{
+ public:
+  GamepadEventChannelChild() {}
+  ~GamepadEventChannelChild() {}
+  virtual bool
+  RecvGamepadUpdate(const GamepadChangeEvent& aGamepadEvent) override;
+};
+
+}// namespace dom
+}// namespace mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadEventChannelParent.cpp
@@ -0,0 +1,101 @@
+/* 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 "GamepadEventChannelParent.h"
+#include "GamepadPlatformService.h"
+#include "mozilla/dom/GamepadMonitoring.h"
+#include "nsThreadUtils.h"
+
+namespace mozilla {
+namespace dom {
+
+using namespace mozilla::ipc;
+
+namespace {
+
+class SendGamepadUpdateRunnable final : public Runnable
+{
+ private:
+  ~SendGamepadUpdateRunnable() {}
+  RefPtr<GamepadEventChannelParent> mParent;
+  GamepadChangeEvent mEvent;
+ public:
+  SendGamepadUpdateRunnable(GamepadEventChannelParent* aParent,
+                            GamepadChangeEvent aEvent)
+    : mEvent(aEvent)
+  {
+    MOZ_ASSERT(aParent);
+    mParent = aParent;
+  }
+  NS_IMETHOD Run() override
+  {
+    AssertIsOnBackgroundThread();
+    if(mParent->HasGamepadListener()) {
+      Unused << mParent->SendGamepadUpdate(mEvent);
+    }
+    return NS_OK;
+  }
+};
+
+} // namespace
+
+GamepadEventChannelParent::GamepadEventChannelParent()
+  : mHasGamepadListener(false)
+{
+  RefPtr<GamepadPlatformService> service =
+    GamepadPlatformService::GetParentService();
+  MOZ_ASSERT(service);
+  service->AddChannelParent(this);
+  mBackgroundThread = NS_GetCurrentThread();
+}
+
+bool
+GamepadEventChannelParent::RecvGamepadListenerAdded()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(!mHasGamepadListener);
+  mHasGamepadListener = true;
+  StartGamepadMonitoring();
+  return true;
+}
+
+bool
+GamepadEventChannelParent::RecvGamepadListenerRemoved()
+{
+  AssertIsOnBackgroundThread();
+  MOZ_ASSERT(mHasGamepadListener);
+  mHasGamepadListener = false;
+  RefPtr<GamepadPlatformService> service =
+    GamepadPlatformService::GetParentService();
+  MOZ_ASSERT(service);
+  service->RemoveChannelParent(this);
+  Unused << Send__delete__(this);
+  return true;
+}
+
+void
+GamepadEventChannelParent::ActorDestroy(ActorDestroyReason aWhy)
+{
+  AssertIsOnBackgroundThread();
+
+  // It may be called because IPDL child side crashed, we'll
+  // not receive RecvGamepadListenerRemoved in that case
+  if (mHasGamepadListener) {
+    mHasGamepadListener = false;
+    RefPtr<GamepadPlatformService> service =
+      GamepadPlatformService::GetParentService();
+    MOZ_ASSERT(service);
+    service->RemoveChannelParent(this);
+  }
+  MaybeStopGamepadMonitoring();
+}
+
+void
+GamepadEventChannelParent::DispatchUpdateEvent(const GamepadChangeEvent& aEvent)
+{
+  mBackgroundThread->Dispatch(new SendGamepadUpdateRunnable(this, aEvent),
+                              NS_DISPATCH_NORMAL);
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadEventChannelParent.h
@@ -0,0 +1,31 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+#include "mozilla/dom/PGamepadEventChannelParent.h"
+
+#ifndef mozilla_dom_GamepadEventChannelParent_h_
+#define mozilla_dom_GamepadEventChannelParent_h_
+
+namespace mozilla{
+namespace dom{
+
+class GamepadEventChannelParent final : public PGamepadEventChannelParent
+{
+ public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GamepadEventChannelParent)
+  GamepadEventChannelParent();
+  virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+  virtual bool RecvGamepadListenerAdded() override;
+  virtual bool RecvGamepadListenerRemoved() override;
+  void DispatchUpdateEvent(const GamepadChangeEvent& aEvent);
+  bool HasGamepadListener() const { return mHasGamepadListener; }
+ private:
+  ~GamepadEventChannelParent() {}
+  bool mHasGamepadListener;
+  nsCOMPtr<nsIThread> mBackgroundThread;
+};
+
+}// namespace dom
+}// namespace mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadEventTypes.ipdlh
@@ -0,0 +1,41 @@
+/* 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/. */
+
+namespace mozilla {
+namespace dom {
+
+struct GamepadAdded {
+  nsString id;
+  uint32_t index;
+  uint32_t mapping;
+  uint32_t num_buttons;
+  uint32_t num_axes;
+};
+
+struct GamepadRemoved {
+  uint32_t index;
+};
+
+struct GamepadAxisInformation {
+  uint32_t index;
+  uint32_t axis;
+  double value;
+};
+
+struct GamepadButtonInformation {
+  uint32_t index;
+  uint32_t button;
+  bool pressed;
+  double value;
+};
+
+union GamepadChangeEvent {
+  GamepadAdded;
+  GamepadRemoved;
+  GamepadAxisInformation;
+  GamepadButtonInformation;
+};
+
+} // namespace dom
+} // namespace mozilla
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadTestChannelChild.cpp
@@ -0,0 +1,32 @@
+/* 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 "GamepadTestChannelChild.h"
+
+namespace mozilla {
+namespace dom {
+
+void
+GamepadTestChannelChild::AddPromise(const uint32_t& aID, Promise* aPromise)
+{
+  MOZ_ASSERT(!mPromiseList.Get(aID, nullptr));
+  mPromiseList.Put(aID, aPromise);
+}
+
+bool
+GamepadTestChannelChild::RecvReplyGamepadIndex(const uint32_t& aID,
+                                               const uint32_t& aIndex)
+{
+  RefPtr<Promise> p;
+  if (!mPromiseList.Get(aID, getter_AddRefs(p))) {
+    MOZ_CRASH("We should always have a promise.");
+  }
+
+  p->MaybeResolve(aIndex);
+  mPromiseList.Remove(aID);
+  return true;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadTestChannelChild.h
@@ -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/. */
+
+#include "mozilla/dom/PGamepadTestChannelChild.h"
+#include "mozilla/dom/Promise.h"
+
+#ifndef mozilla_dom_GamepadTestChannelChild_h_
+#define mozilla_dom_GamepadTestChannelChild_h_
+
+namespace mozilla {
+namespace dom {
+
+class GamepadTestChannelChild final : public PGamepadTestChannelChild
+{
+ public:
+  GamepadTestChannelChild() {}
+  ~GamepadTestChannelChild() {}
+  void AddPromise(const uint32_t& aID, Promise* aPromise);
+ private:
+  virtual bool RecvReplyGamepadIndex(const uint32_t& aID,
+                                     const uint32_t& aIndex) override;
+  nsRefPtrHashtable<nsUint32HashKey, Promise> mPromiseList;
+};
+
+}// namespace dom
+}// namespace mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadTestChannelParent.cpp
@@ -0,0 +1,63 @@
+/* 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 "GamepadTestChannelParent.h"
+
+#include "mozilla/dom/GamepadPlatformService.h"
+#include "mozilla/unused.h"
+
+namespace mozilla {
+namespace dom {
+
+bool
+GamepadTestChannelParent::RecvGamepadTestEvent(const uint32_t& aID,
+                                               const GamepadChangeEvent& aEvent)
+{
+  mozilla::ipc::AssertIsOnBackgroundThread();
+  RefPtr<GamepadPlatformService>  service =
+    GamepadPlatformService::GetParentService();
+  MOZ_ASSERT(service);
+  if (aEvent.type() == GamepadChangeEvent::TGamepadAdded) {
+    const GamepadAdded& a = aEvent.get_GamepadAdded();
+    nsCString gamepadID;
+    LossyCopyUTF16toASCII(a.id(), gamepadID);
+    uint32_t index = service->AddGamepad(gamepadID.get(),
+                                         (GamepadMappingType)a.mapping(),
+                                         a.num_buttons(),
+                                         a.num_axes());
+    if (!mShuttingdown) {
+      Unused << SendReplyGamepadIndex(aID, index);
+    }
+    return true;
+  }
+  if (aEvent.type() == GamepadChangeEvent::TGamepadRemoved) {
+    const GamepadRemoved& a = aEvent.get_GamepadRemoved();
+    service->RemoveGamepad(a.index());
+    return true;
+  }
+  if (aEvent.type() == GamepadChangeEvent::TGamepadButtonInformation) {
+    const GamepadButtonInformation& a = aEvent.get_GamepadButtonInformation();
+    service->NewButtonEvent(a.index(), a.button(), a.pressed(), a.value());
+    return true;
+  }
+  if (aEvent.type() == GamepadChangeEvent::TGamepadAxisInformation) {
+    const GamepadAxisInformation& a = aEvent.get_GamepadAxisInformation();
+    service->NewAxisMoveEvent(a.index(), a.axis(), a.value());
+    return true;
+  }
+
+  NS_WARNING("Unknown event type.");
+  return false;
+}
+
+bool
+GamepadTestChannelParent::RecvShutdownChannel()
+{
+  mShuttingdown = true;
+  Unused << Send__delete__(this);
+  return true;
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/gamepad/ipc/GamepadTestChannelParent.h
@@ -0,0 +1,33 @@
+/* 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/PGamepadTestChannelParent.h"
+
+#ifndef mozilla_dom_GamepadTestChannelParent_h_
+#define mozilla_dom_GamepadTestChannelParent_h_
+
+namespace mozilla {
+namespace dom {
+
+class GamepadTestChannelParent final : public PGamepadTestChannelParent
+{
+ public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(GamepadTestChannelParent)
+  GamepadTestChannelParent()
+    : mShuttingdown(false) {}
+  virtual void ActorDestroy(ActorDestroyReason aWhy) override {}
+  virtual bool
+  RecvGamepadTestEvent(const uint32_t& aID,
+                       const GamepadChangeEvent& aGamepadEvent) override;
+  virtual bool
+  RecvShutdownChannel() override;
+ private:
+  ~GamepadTestChannelParent() {}
+  bool mShuttingdown;
+};
+
+}// namespace dom
+}// namespace mozilla
+
+#endif
new file mode 100644
--- /dev/null
+++ b/dom/gamepad/ipc/PGamepadEventChannel.ipdl
@@ -0,0 +1,21 @@
+/* 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 protocol PBackground;
+include GamepadEventTypes;
+
+namespace mozilla {
+namespace dom {
+
+async protocol PGamepadEventChannel {
+  manager PBackground;
+  parent:
+    async GamepadListenerAdded();
+    async GamepadListenerRemoved();
+  child:
+    async __delete__();
+    async GamepadUpdate(GamepadChangeEvent aGamepadEvent);
+};
+
+}
+}
new file mode 100644
--- /dev/null
+++ b/dom/gamepad/ipc/PGamepadTestChannel.ipdl
@@ -0,0 +1,21 @@
+/* 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 protocol PBackground;
+include GamepadEventTypes;
+
+namespace mozilla {
+namespace dom {
+
+async protocol PGamepadTestChannel {
+  manager PBackground;
+  parent:
+    async GamepadTestEvent(uint32_t aID, GamepadChangeEvent aGamepadEvent);
+    async ShutdownChannel();
+  child:
+    async __delete__();
+    async ReplyGamepadIndex(uint32_t aID, uint32_t aIndex);
+};
+
+}
+}
--- a/dom/gamepad/linux/LinuxGamepad.cpp
+++ b/dom/gamepad/linux/LinuxGamepad.cpp
@@ -14,22 +14,22 @@
 
 #include <glib.h>
 #include <linux/joystick.h>
 #include <stdio.h>
 #include <stdint.h>
 #include <sys/ioctl.h>
 #include <unistd.h>
 #include "nscore.h"
-#include "mozilla/dom/GamepadFunctions.h"
+#include "mozilla/dom/GamepadPlatformService.h"
 #include "udev.h"
 
 namespace {
 
-using namespace mozilla::dom::GamepadFunctions;
+using namespace mozilla::dom;
 using mozilla::udev_lib;
 using mozilla::udev_device;
 using mozilla::udev_list_entry;
 using mozilla::udev_enumerate;
 using mozilla::udev_monitor;
 
 static const float kMaxAxisValue = 32767.0;
 static const char kJoystickPath[] = "/dev/input/js";
@@ -81,16 +81,22 @@ private:
 };
 
 // singleton instance
 LinuxGamepadService* gService = nullptr;
 
 void
 LinuxGamepadService::AddDevice(struct udev_device* dev)
 {
+  RefPtr<GamepadPlatformService> service =
+    GamepadPlatformService::GetParentService();
+  if (!service) {
+    return;
+  }
+
   const char* devpath = mUdev.udev_device_get_devnode(dev);
   if (!devpath) {
     return;
   }
 
   // Ensure that this device hasn't already been added.
   for (unsigned int i = 0; i < mGamepads.Length(); i++) {
     if (strcmp(mGamepads[i].devpath, devpath) == 0) {
@@ -134,43 +140,49 @@ LinuxGamepadService::AddDevice(struct ud
            name);
 
   char numAxes = 0, numButtons = 0;
   ioctl(fd, JSIOCGAXES, &numAxes);
   gamepad.numAxes = numAxes;
   ioctl(fd, JSIOCGBUTTONS, &numButtons);
   gamepad.numButtons = numButtons;
 
-  gamepad.index = AddGamepad(gamepad.idstring,
-                             mozilla::dom::GamepadMappingType::_empty,
-                             gamepad.numButtons,
-                             gamepad.numAxes);
+  gamepad.index = service->AddGamepad(gamepad.idstring,
+                                      mozilla::dom::GamepadMappingType::_empty,
+                                      gamepad.numButtons,
+                                      gamepad.numAxes);
 
   gamepad.source_id =
     g_io_add_watch(channel,
                    GIOCondition(G_IO_IN | G_IO_ERR | G_IO_HUP),
                    OnGamepadData,
                    GINT_TO_POINTER(gamepad.index));
   g_io_channel_unref(channel);
 
   mGamepads.AppendElement(gamepad);
 }
 
 void
 LinuxGamepadService::RemoveDevice(struct udev_device* dev)
 {
+  RefPtr<GamepadPlatformService> service =
+    GamepadPlatformService::GetParentService();
+  if (!service) {
+    return;
+  }
+
   const char* devpath = mUdev.udev_device_get_devnode(dev);
   if (!devpath) {
     return;
   }
 
   for (unsigned int i = 0; i < mGamepads.Length(); i++) {
     if (strcmp(mGamepads[i].devpath, devpath) == 0) {
       g_source_remove(mGamepads[i].source_id);
-      RemoveGamepad(mGamepads[i].index);
+      service->RemoveGamepad(mGamepads[i].index);
       mGamepads.RemoveElementAt(i);
       break;
     }
   }
 }
 
 void
 LinuxGamepadService::ScanForDevices()
@@ -290,16 +302,21 @@ LinuxGamepadService::ReadUdevChange()
 }
 
 // static
 gboolean
 LinuxGamepadService::OnGamepadData(GIOChannel* source,
                                    GIOCondition condition,
                                    gpointer data)
 {
+  RefPtr<GamepadPlatformService> service =
+    GamepadPlatformService::GetParentService();
+  if (!service) {
+    return TRUE;
+  }
   int index = GPOINTER_TO_INT(data);
   //TODO: remove gamepad?
   if (condition & G_IO_ERR || condition & G_IO_HUP)
     return FALSE;
 
   while (true) {
     struct js_event event;
     gsize count;
@@ -315,21 +332,21 @@ LinuxGamepadService::OnGamepadData(GIOCh
 
     //TODO: store device state?
     if (event.type & JS_EVENT_INIT) {
       continue;
     }
 
     switch (event.type) {
     case JS_EVENT_BUTTON:
-      NewButtonEvent(index, event.number, !!event.value);
+      service->NewButtonEvent(index, event.number, !!event.value);
       break;
     case JS_EVENT_AXIS:
-      NewAxisMoveEvent(index, event.number,
-                       ((float)event.value) / kMaxAxisValue);
+      service->NewAxisMoveEvent(index, event.number,
+                                ((float)event.value) / kMaxAxisValue);
       break;
     }
   }
 
   return TRUE;
 }
 
 // static
--- a/dom/gamepad/moz.build
+++ b/dom/gamepad/moz.build
@@ -2,29 +2,37 @@
 # 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/.
 
 EXPORTS.mozilla.dom += [
     'Gamepad.h',
     'GamepadButton.h',
-    'GamepadFunctions.h',
+    'GamepadManager.h',
     'GamepadMonitoring.h',
-    'GamepadService.h',
-    'GamepadServiceTest.h'
+    'GamepadPlatformService.h',
+    'GamepadServiceTest.h',
+    'ipc/GamepadEventChannelChild.h',
+    'ipc/GamepadEventChannelParent.h',
+    'ipc/GamepadTestChannelChild.h',
+    'ipc/GamepadTestChannelParent.h'
     ]
 
 UNIFIED_SOURCES = [
     'Gamepad.cpp',
     'GamepadButton.cpp',
-    'GamepadFunctions.cpp',
+    'GamepadManager.cpp',
     'GamepadMonitoring.cpp',
-    'GamepadService.cpp',
-    'GamepadServiceTest.cpp'
+    'GamepadPlatformService.cpp',
+    'GamepadServiceTest.cpp',
+    'ipc/GamepadEventChannelChild.cpp',
+    'ipc/GamepadEventChannelParent.cpp',
+    'ipc/GamepadTestChannelChild.cpp',
+    'ipc/GamepadTestChannelParent.cpp'
     ]
 
 if CONFIG['MOZ_GAMEPAD_BACKEND'] == 'stub':
     UNIFIED_SOURCES += [
         'fallback/FallbackGamepad.cpp'
     ]
 elif CONFIG['MOZ_GAMEPAD_BACKEND'] == 'cocoa':
     UNIFIED_SOURCES += [
@@ -38,16 +46,26 @@ elif CONFIG['MOZ_GAMEPAD_BACKEND'] == 'l
     UNIFIED_SOURCES += [
         'linux/LinuxGamepad.cpp'
     ]
 elif CONFIG['MOZ_GAMEPAD_BACKEND'] == 'android':
     UNIFIED_SOURCES += [
         'android/AndroidGamepad.cpp'
     ]
 
+LOCAL_INCLUDES += [
+    'ipc',
+]
+
+IPDL_SOURCES += [
+    'ipc/GamepadEventTypes.ipdlh',
+    'ipc/PGamepadEventChannel.ipdl',
+    'ipc/PGamepadTestChannel.ipdl'
+]
+
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '/dom/base',
 ]
 
 CFLAGS += CONFIG['GLIB_CFLAGS']
--- a/dom/gamepad/windows/WindowsGamepad.cpp
+++ b/dom/gamepad/windows/WindowsGamepad.cpp
@@ -11,28 +11,30 @@
 #define UNICODE
 #endif
 #include <windows.h>
 #include <hidsdi.h>
 #include <stdio.h>
 #include <xinput.h>
 
 #include "nsIComponentManager.h"
-#include "nsIObserver.h"
-#include "nsIObserverService.h"
 #include "nsITimer.h"
 #include "nsTArray.h"
+#include "nsThreadUtils.h"
+
 #include "mozilla/ArrayUtils.h"
-#include "mozilla/dom/GamepadFunctions.h"
 #include "mozilla/Services.h"
 
+#include "mozilla/ipc/BackgroundParent.h"
+#include "mozilla/dom/GamepadPlatformService.h"
+
 namespace {
 
+using namespace mozilla;
 using namespace mozilla::dom;
-using namespace mozilla::dom::GamepadFunctions;
 using mozilla::ArrayLength;
 
 // USB HID usage tables, page 1 (Hat switch)
 const unsigned kUsageDpad = 0x39;
 // USB HID usage tables, page 1, 0x30 = X
 const unsigned kFirstAxis = 0x30;
 
 // USB HID usage tables
@@ -90,17 +92,22 @@ const size_t kNumMappings = ArrayLength(
 
 enum GamepadType {
   kNoGamepad = 0,
   kRawInputGamepad,
   kXInputGamepad
 };
 
 class WindowsGamepadService;
-WindowsGamepadService* gService = nullptr;
+// This pointer holds a windows gamepad backend service,
+// it will be created and destroyed by background thread and
+// used by gMonitorThread
+WindowsGamepadService* MOZ_NON_OWNING_REF gService = nullptr;
+nsCOMPtr<nsIThread> gMonitorThread = nullptr;
+static bool sIsShutdown = false;
 
 struct Gamepad {
   GamepadType type;
 
   // Handle to raw input device
   HANDLE handle;
 
   // XInput Index of the user's controller. Passed to XInputGetState.
@@ -250,70 +257,16 @@ SupportedUsage(USHORT page, USHORT usage
   for (unsigned i = 0; i < ArrayLength(kUsagePages); i++) {
     if (page == kUsagePages[i].usagePage && usage == kUsagePages[i].usage) {
       return true;
     }
   }
   return false;
 }
 
-class Observer : public nsIObserver {
-public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSIOBSERVER
-
-  Observer(WindowsGamepadService& svc) : mSvc(svc),
-                                         mObserving(true)
-  {
-    nsresult rv;
-    mTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
-    nsCOMPtr<nsIObserverService> observerService =
-      mozilla::services::GetObserverService();
-    observerService->AddObserver(this,
-                                 NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID,
-                                 false);
-  }
-
-  void Stop()
-  {
-    if (mTimer) {
-      mTimer->Cancel();
-    }
-    if (mObserving) {
-      nsCOMPtr<nsIObserverService> observerService =
-        mozilla::services::GetObserverService();
-      observerService->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID);
-      mObserving = false;
-    }
-  }
-
-  void SetDeviceChangeTimer()
-  {
-    // Set stable timer, since we will get multiple devices-changed
-    // notifications at once
-    if (mTimer) {
-      mTimer->Cancel();
-      mTimer->Init(this, kDevicesChangedStableDelay, nsITimer::TYPE_ONE_SHOT);
-    }
-  }
-
-private:
-  virtual ~Observer()
-  {
-    Stop();
-  }
-
-  // Gamepad service owns us, we just hold a reference back to it.
-  WindowsGamepadService& mSvc;
-  nsCOMPtr<nsITimer> mTimer;
-  bool mObserving;
-};
-
-NS_IMPL_ISUPPORTS(Observer, nsIObserver);
-
 class HIDLoader {
 public:
   HIDLoader() : mModule(LoadLibraryW(L"hid.dll")),
                 mHidD_GetProductString(nullptr),
                 mHidP_GetCaps(nullptr),
                 mHidP_GetButtonCaps(nullptr),
                 mHidP_GetValueCaps(nullptr),
                 mHidP_GetUsages(nullptr),
@@ -355,69 +308,65 @@ public:
   decltype(HidP_GetUsages) *mHidP_GetUsages;
   decltype(HidP_GetUsageValue) *mHidP_GetUsageValue;
   decltype(HidP_GetScaledUsageValue) *mHidP_GetScaledUsageValue;
 
 private:
   HMODULE mModule;
 };
 
-class WindowsGamepadService {
-public:
-  WindowsGamepadService();
+class WindowsGamepadService
+{
+ public:
+  WindowsGamepadService()
+  {
+    mXInputTimer = do_CreateInstance("@mozilla.org/timer;1");
+    mDeviceChangeTimer = do_CreateInstance("@mozilla.org/timer;1");
+  }
   virtual ~WindowsGamepadService()
   {
     Cleanup();
   }
 
-  enum DeviceChangeType {
-    DeviceChangeNotification,
-    DeviceChangeStable
-  };
-  void DevicesChanged(DeviceChangeType type);
+  void DevicesChanged(bool aIsStablizing);
   void Startup();
   void Shutdown();
   // Parse gamepad input from a WM_INPUT message.
   bool HandleRawInput(HRAWINPUT handle);
 
-private:
+  static void XInputMessageLoopOnceCallback(nsITimer *aTimer, void* aClosure);
+  static void DevicesChangeCallback(nsITimer *aTimer, void* aService);
+
+ private:
   void ScanForDevices();
   // Look for connected raw input devices.
   void ScanForRawInputDevices();
   // Look for connected XInput devices.
   bool ScanForXInputDevices();
   bool HaveXInputGamepad(int userIndex);
 
-  // Timer callback for XInput polling
-  static void XInputPollTimerCallback(nsITimer* aTimer, void* aClosure);
+  bool mIsXInputMonitoring;
   void PollXInput();
   void CheckXInputChanges(Gamepad& gamepad, XINPUT_STATE& state);
 
   // Get information about a raw input gamepad.
   bool GetRawGamepad(HANDLE handle);
   void Cleanup();
 
   // List of connected devices.
   nsTArray<Gamepad> mGamepads;
 
-  RefPtr<Observer> mObserver;
-  nsCOMPtr<nsITimer> mXInputPollTimer;
-
   HIDLoader mHID;
   XInputLoader mXInput;
+
+  nsCOMPtr<nsITimer> mXInputTimer;
+  nsCOMPtr<nsITimer> mDeviceChangeTimer;
 };
 
 
-WindowsGamepadService::WindowsGamepadService()
-{
-  nsresult rv;
-  mXInputPollTimer = do_CreateInstance("@mozilla.org/timer;1", &rv);
-  mObserver = new Observer(*this);
-}
-
 void
 WindowsGamepadService::ScanForRawInputDevices()
 {
   if (!mHID) {
     return;
   }
 
   UINT numDevices;
@@ -434,16 +383,40 @@ WindowsGamepadService::ScanForRawInputDe
 
   for (unsigned i = 0; i < devices.Length(); i++) {
     if (devices[i].dwType == RIM_TYPEHID) {
       GetRawGamepad(devices[i].hDevice);
     }
   }
 }
 
+// static
+void
+WindowsGamepadService::XInputMessageLoopOnceCallback(nsITimer *aTimer,
+                                                     void* aService)
+{
+  MOZ_ASSERT(aService);
+  WindowsGamepadService* self = static_cast<WindowsGamepadService*>(aService);
+  self->PollXInput();
+  if (self->mIsXInputMonitoring) {
+    aTimer->Cancel();
+    aTimer->InitWithFuncCallback(XInputMessageLoopOnceCallback, self,
+                                 kXInputPollInterval, nsITimer::TYPE_ONE_SHOT);
+  }
+}
+
+// static
+void
+WindowsGamepadService::DevicesChangeCallback(nsITimer *aTimer, void* aService)
+{
+  MOZ_ASSERT(aService);
+  WindowsGamepadService* self = static_cast<WindowsGamepadService*>(aService);
+  self->DevicesChanged(false);
+}
+
 bool
 WindowsGamepadService::HaveXInputGamepad(int userIndex)
 {
   for (unsigned int i = 0; i < mGamepads.Length(); i++) {
     if (mGamepads[i].type == kXInputGamepad
         && mGamepads[i].userIndex == userIndex) {
       mGamepads[i].present = true;
       return true;
@@ -453,16 +426,22 @@ WindowsGamepadService::HaveXInputGamepad
 }
 
 bool
 WindowsGamepadService::ScanForXInputDevices()
 {
   MOZ_ASSERT(mXInput, "XInput should be present!");
 
   bool found = false;
+  RefPtr<GamepadPlatformService> service =
+    GamepadPlatformService::GetParentService();
+  if (!service) {
+    return found;
+  }
+
   for (int i = 0; i < XUSER_MAX_COUNT; i++) {
     XINPUT_STATE state = {};
     if (mXInput.mXInputGetState(i, &state) != ERROR_SUCCESS) {
       continue;
     }
     found = true;
     // See if this device is already present in our list.
     if (HaveXInputGamepad(i)) {
@@ -472,65 +451,63 @@ WindowsGamepadService::ScanForXInputDevi
     // Not already present, add it.
     Gamepad gamepad = {};
     gamepad.type = kXInputGamepad;
     gamepad.present = true;
     gamepad.state = state;
     gamepad.userIndex = i;
     gamepad.numButtons = kStandardGamepadButtons;
     gamepad.numAxes = kStandardGamepadAxes;
-    gamepad.id = AddGamepad("xinput",
-                            GamepadMappingType::Standard,
-                            kStandardGamepadButtons,
-                            kStandardGamepadAxes);
+    gamepad.id = service->AddGamepad("xinput",
+                                     GamepadMappingType::Standard,
+                                     kStandardGamepadButtons,
+                                     kStandardGamepadAxes);
     mGamepads.AppendElement(gamepad);
   }
 
   return found;
 }
 
 void
 WindowsGamepadService::ScanForDevices()
 {
+  RefPtr<GamepadPlatformService> service =
+    GamepadPlatformService::GetParentService();
+  if (!service) {
+    return;
+  }
+
   for (int i = mGamepads.Length() - 1; i >= 0; i--) {
     mGamepads[i].present = false;
   }
 
   if (mHID) {
     ScanForRawInputDevices();
   }
   if (mXInput) {
-    mXInputPollTimer->Cancel();
+    mXInputTimer->Cancel();
     if (ScanForXInputDevices()) {
-      mXInputPollTimer->InitWithFuncCallback(XInputPollTimerCallback,
-                                             this,
-                                             kXInputPollInterval,
-                                             nsITimer::TYPE_REPEATING_SLACK);
+      mIsXInputMonitoring = true;
+      mXInputTimer->InitWithFuncCallback(XInputMessageLoopOnceCallback, this,
+                                         kXInputPollInterval,
+                                         nsITimer::TYPE_ONE_SHOT);
+    } else {
+      mIsXInputMonitoring = false;
     }
   }
 
   // Look for devices that are no longer present and remove them.
   for (int i = mGamepads.Length() - 1; i >= 0; i--) {
     if (!mGamepads[i].present) {
-      RemoveGamepad(mGamepads[i].id);
+      service->RemoveGamepad(mGamepads[i].id);
       mGamepads.RemoveElementAt(i);
     }
   }
 }
 
-// static
-void
-WindowsGamepadService::XInputPollTimerCallback(nsITimer* aTimer,
-                                               void* aClosure)
-{
-  WindowsGamepadService* self =
-    reinterpret_cast<WindowsGamepadService*>(aClosure);
-  self->PollXInput();
-}
-
 void
 WindowsGamepadService::PollXInput()
 {
   for (unsigned int i = 0; i < mGamepads.Length(); i++) {
     if (mGamepads[i].type != kXInputGamepad) {
       continue;
     }
 
@@ -540,60 +517,65 @@ WindowsGamepadService::PollXInput()
         && state.dwPacketNumber != mGamepads[i].state.dwPacketNumber) {
         CheckXInputChanges(mGamepads[i], state);
     }
   }
 }
 
 void WindowsGamepadService::CheckXInputChanges(Gamepad& gamepad,
                                                XINPUT_STATE& state) {
+  RefPtr<GamepadPlatformService> service =
+    GamepadPlatformService::GetParentService();
+  if (!service) {
+    return;
+  }
   // Handle digital buttons first
   for (size_t b = 0; b < kNumMappings; b++) {
     if (state.Gamepad.wButtons & kXIButtonMap[b].button &&
         !(gamepad.state.Gamepad.wButtons & kXIButtonMap[b].button)) {
       // Button pressed
-      NewButtonEvent(gamepad.id, kXIButtonMap[b].mapped, true);
+      service->NewButtonEvent(gamepad.id, kXIButtonMap[b].mapped, true);
     } else if (!(state.Gamepad.wButtons & kXIButtonMap[b].button) &&
                gamepad.state.Gamepad.wButtons & kXIButtonMap[b].button) {
       // Button released
-      NewButtonEvent(gamepad.id, kXIButtonMap[b].mapped, false);
+      service->NewButtonEvent(gamepad.id, kXIButtonMap[b].mapped, false);
     }
   }
 
   // Then triggers
   if (state.Gamepad.bLeftTrigger != gamepad.state.Gamepad.bLeftTrigger) {
     bool pressed =
       state.Gamepad.bLeftTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
-    NewButtonEvent(gamepad.id, kButtonLeftTrigger,
-                   pressed, state.Gamepad.bLeftTrigger / 255.0);
+    service->NewButtonEvent(gamepad.id, kButtonLeftTrigger,
+                            pressed, state.Gamepad.bLeftTrigger / 255.0);
   }
   if (state.Gamepad.bRightTrigger != gamepad.state.Gamepad.bRightTrigger) {
     bool pressed =
       state.Gamepad.bRightTrigger >= XINPUT_GAMEPAD_TRIGGER_THRESHOLD;
-    NewButtonEvent(gamepad.id, kButtonRightTrigger,
-                   pressed, state.Gamepad.bRightTrigger / 255.0);
+    service->NewButtonEvent(gamepad.id, kButtonRightTrigger,
+                            pressed, state.Gamepad.bRightTrigger / 255.0);
   }
 
   // Finally deal with analog sticks
   // TODO: bug 1001955 - Support deadzones.
   if (state.Gamepad.sThumbLX != gamepad.state.Gamepad.sThumbLX) {
-    NewAxisMoveEvent(gamepad.id, kLeftStickXAxis,
-                     state.Gamepad.sThumbLX / 32767.0);
+    service->NewAxisMoveEvent(gamepad.id, kLeftStickXAxis,
+                              state.Gamepad.sThumbLX / 32767.0);
   }
   if (state.Gamepad.sThumbLY != gamepad.state.Gamepad.sThumbLY) {
-    NewAxisMoveEvent(gamepad.id, kLeftStickYAxis,
-                     -1.0 * state.Gamepad.sThumbLY / 32767.0);
+    service->NewAxisMoveEvent(gamepad.id, kLeftStickYAxis,
+                              -1.0 * state.Gamepad.sThumbLY / 32767.0);
   }
   if (state.Gamepad.sThumbRX != gamepad.state.Gamepad.sThumbRX) {
-    NewAxisMoveEvent(gamepad.id, kRightStickXAxis,
-                     state.Gamepad.sThumbRX / 32767.0);
+    service->NewAxisMoveEvent(gamepad.id, kRightStickXAxis,
+                              state.Gamepad.sThumbRX / 32767.0);
   }
   if (state.Gamepad.sThumbRY != gamepad.state.Gamepad.sThumbRY) {
-    NewAxisMoveEvent(gamepad.id, kRightStickYAxis,
-                     -1.0 * state.Gamepad.sThumbRY / 32767.0);
+    service->NewAxisMoveEvent(gamepad.id, kRightStickYAxis,
+                              -1.0 * state.Gamepad.sThumbRY / 32767.0);
   }
   gamepad.state = state;
 }
 
 // Used to sort a list of axes by HID usage.
 class HidValueComparator {
 public:
   bool Equals(const HIDP_VALUE_CAPS& c1, const HIDP_VALUE_CAPS& c2) const
@@ -607,16 +589,22 @@ public:
     }
     return c1.UsagePage < c2.UsagePage;
   }
 };
 
 bool
 WindowsGamepadService::GetRawGamepad(HANDLE handle)
 {
+  RefPtr<GamepadPlatformService> service =
+    GamepadPlatformService::GetParentService();
+  if (!service) {
+    return true;
+  }
+
   if (!mHID) {
     return false;
   }
 
   for (unsigned i = 0; i < mGamepads.Length(); i++) {
     if (mGamepads[i].type == kRawInputGamepad && mGamepads[i].handle == handle) {
       mGamepads[i].present = true;
       return true;
@@ -743,32 +731,37 @@ WindowsGamepadService::GetRawGamepad(HAN
     if (i >= kMaxAxes) {
       break;
     }
     gamepad.axes[i].caps = axes[i];
   }
   gamepad.type = kRawInputGamepad;
   gamepad.handle = handle;
   gamepad.present = true;
-
-  gamepad.id = GamepadFunctions::AddGamepad(gamepad_id,
-                                            GamepadMappingType::_empty,
-                                            gamepad.numButtons,
-                                            gamepad.numAxes);
+  gamepad.id = service->AddGamepad(gamepad_id,
+                                   GamepadMappingType::_empty,
+                                   gamepad.numButtons,
+                                   gamepad.numAxes);
   mGamepads.AppendElement(gamepad);
   return true;
 }
 
 bool
 WindowsGamepadService::HandleRawInput(HRAWINPUT handle)
 {
   if (!mHID) {
     return false;
   }
 
+  RefPtr<GamepadPlatformService> service =
+    GamepadPlatformService::GetParentService();
+  if (service) {
+    return true;
+  }
+
   // First, get data from the handle
   UINT size;
   GetRawInputData(handle, RID_INPUT, nullptr, &size, sizeof(RAWINPUTHEADER));
   nsTArray<uint8_t> data(size);
   data.SetLength(size);
   if (GetRawInputData(handle, RID_INPUT, data.Elements(), &size,
                       sizeof(RAWINPUTHEADER)) == kRawInputError) {
     return false;
@@ -816,17 +809,17 @@ WindowsGamepadService::HandleRawInput(HR
     ULONG value;
     if (mHID.mHidP_GetUsageValue(HidP_Input, gamepad->dpadCaps.UsagePage, 0, gamepad->dpadCaps.Range.UsageMin, &value, parsed, (PCHAR)raw->data.hid.bRawData, raw->data.hid.dwSizeHid) == HIDP_STATUS_SUCCESS) {
       UnpackDpad(static_cast<LONG>(value), gamepad, buttons);
     }
   }
 
   for (unsigned i = 0; i < gamepad->numButtons; i++) {
     if (gamepad->buttons[i] != buttons[i]) {
-      NewButtonEvent(gamepad->id, i, buttons[i]);
+      service->NewButtonEvent(gamepad->id, i, buttons[i]);
       gamepad->buttons[i] = buttons[i];
     }
   }
 
   // Get all axis values.
   for (unsigned i = 0; i < gamepad->numAxes; i++) {
     double new_value;
     if (gamepad->axes[i].caps.LogicalMin < 0) {
@@ -836,31 +829,30 @@ LONG value;
                                    &value, parsed,
                                    (PCHAR)raw->data.hid.bRawData,
                                    raw->data.hid.dwSizeHid)
           != HIDP_STATUS_SUCCESS) {
         continue;
       }
       new_value = ScaleAxis(value, gamepad->axes[i].caps.LogicalMin,
                             gamepad->axes[i].caps.LogicalMax);
-    }
-    else {
+    } else {
       ULONG value;
       if (mHID.mHidP_GetUsageValue(HidP_Input, gamepad->axes[i].caps.UsagePage, 0,
                              gamepad->axes[i].caps.Range.UsageMin, &value,
                              parsed, (PCHAR)raw->data.hid.bRawData,
                              raw->data.hid.dwSizeHid) != HIDP_STATUS_SUCCESS) {
         continue;
       }
 
       new_value = ScaleAxis(value, gamepad->axes[i].caps.LogicalMin,
                             gamepad->axes[i].caps.LogicalMax);
     }
     if (gamepad->axes[i].value != new_value) {
-      NewAxisMoveEvent(gamepad->id, i, new_value);
+      service->NewAxisMoveEvent(gamepad->id, i, new_value);
       gamepad->axes[i].value = new_value;
     }
   }
 
   return true;
 }
 
 void
@@ -873,51 +865,39 @@ void
 WindowsGamepadService::Shutdown()
 {
   Cleanup();
 }
 
 void
 WindowsGamepadService::Cleanup()
 {
-  if (mXInputPollTimer) {
-    mXInputPollTimer->Cancel();
+  mIsXInputMonitoring = false;
+  if (mXInputTimer) {
+    mXInputTimer->Cancel();
+  }
+  if (mDeviceChangeTimer) {
+    mDeviceChangeTimer->Cancel();
   }
   mGamepads.Clear();
-  if (mObserver) {
-    mObserver->Stop();
-    mObserver = nullptr;
-  }
 }
 
 void
-WindowsGamepadService::DevicesChanged(DeviceChangeType type)
+WindowsGamepadService::DevicesChanged(bool aIsStablizing)
 {
-  if (type == DeviceChangeNotification) {
-    if (mObserver) {
-      mObserver->SetDeviceChangeTimer();
-    }
-  } else if (type == DeviceChangeStable) {
+  if (aIsStablizing) {
+    mDeviceChangeTimer->Cancel();
+    mDeviceChangeTimer->InitWithFuncCallback(DevicesChangeCallback, this,
+                                             kDevicesChangedStableDelay,
+                                             nsITimer::TYPE_ONE_SHOT);
+  } else {
     ScanForDevices();
   }
 }
 
-NS_IMETHODIMP
-Observer::Observe(nsISupports* aSubject,
-                  const char* aTopic,
-                  const char16_t* aData)
-{
-  if (strcmp(aTopic, "timer-callback") == 0) {
-    mSvc.DevicesChanged(WindowsGamepadService::DeviceChangeStable);
-  } else if (strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID) == 0) {
-    Stop();
-  }
-  return NS_OK;
-}
-
 HWND sHWnd = nullptr;
 
 bool
 RegisterRawInput(HWND hwnd, bool enable)
 {
   nsTArray<RAWINPUTDEVICE> rid(ArrayLength(kUsagePages));
   rid.SetLength(ArrayLength(kUsagePages));
 
@@ -945,74 +925,141 @@ GamepadWindowProc(HWND hwnd, UINT msg, W
   const unsigned int DBT_DEVNODES_CHANGED     = 0x7;
 
   switch (msg) {
   case WM_DEVICECHANGE:
     if (wParam == DBT_DEVICEARRIVAL ||
         wParam == DBT_DEVICEREMOVECOMPLETE ||
         wParam == DBT_DEVNODES_CHANGED) {
       if (gService) {
-        gService->DevicesChanged(WindowsGamepadService::DeviceChangeNotification);
+        gService->DevicesChanged(true);
       }
     }
     break;
   case WM_INPUT:
     if (gService) {
       gService->HandleRawInput(reinterpret_cast<HRAWINPUT>(lParam));
     }
     break;
   }
   return DefWindowProc(hwnd, msg, wParam, lParam);
 }
 
+class WindowGamepadMessageLoopOnceRunnable final : public Runnable
+{
+public:
+  WindowGamepadMessageLoopOnceRunnable() {}
+  NS_IMETHOD Run() override
+  {
+    MOZ_ASSERT(NS_GetCurrentThread() == gMonitorThread);
+    MSG msg;
+    while (PeekMessageW(&msg, sHWnd, 0, 0, PM_REMOVE) > 0) {
+      TranslateMessage(&msg);
+      DispatchMessage(&msg);
+    }
+    if (!sIsShutdown) {
+      NS_DispatchToCurrentThread(new WindowGamepadMessageLoopOnceRunnable());
+    }
+    return NS_OK;
+  }
+private:
+  ~WindowGamepadMessageLoopOnceRunnable() {}
+};
+
+class StartWindowsGamepadServiceRunnable final : public Runnable
+{
+public:
+  StartWindowsGamepadServiceRunnable() {}
+
+  NS_IMETHOD Run() override
+  {
+    MOZ_ASSERT(NS_GetCurrentThread() == gMonitorThread);
+    gService = new WindowsGamepadService();
+    gService->Startup();
+
+    if (sHWnd == nullptr) {
+      WNDCLASSW wc;
+      HMODULE hSelf = GetModuleHandle(nullptr);
+
+      if (!GetClassInfoW(hSelf, L"MozillaGamepadClass", &wc)) {
+        ZeroMemory(&wc, sizeof(WNDCLASSW));
+        wc.hInstance = hSelf;
+        wc.lpfnWndProc = GamepadWindowProc;
+        wc.lpszClassName = L"MozillaGamepadClass";
+        RegisterClassW(&wc);
+      }
+
+      sHWnd = CreateWindowW(L"MozillaGamepadClass", L"Gamepad Watcher",
+        0, 0, 0, 0, 0,
+        nullptr, nullptr, hSelf, nullptr);
+      RegisterRawInput(sHWnd, true);
+    }
+
+    // Explicitly start the message loop
+    NS_DispatchToCurrentThread(new WindowGamepadMessageLoopOnceRunnable());
+
+    return NS_OK;
+  }
+private:
+  ~StartWindowsGamepadServiceRunnable() {}
+};
+
+class StopWindowsGamepadServiceRunnable final : public Runnable
+{
+ public:
+  StopWindowsGamepadServiceRunnable() {}
+
+  NS_IMETHOD Run() override
+  {
+    MOZ_ASSERT(NS_GetCurrentThread() == gMonitorThread);
+    if (sHWnd) {
+      RegisterRawInput(sHWnd, false);
+      DestroyWindow(sHWnd);
+      sHWnd = nullptr;
+    }
+
+    gService->Shutdown();
+    delete gService;
+    gService = nullptr;
+
+    return NS_OK;
+  }
+ private:
+  ~StopWindowsGamepadServiceRunnable() {}
+};
+
 } // namespace
 
 namespace mozilla {
 namespace dom {
 
-void StartGamepadMonitoring()
+using namespace mozilla::ipc;
+
+void
+StartGamepadMonitoring()
 {
-  if (gService) {
+  AssertIsOnBackgroundThread();
+
+  if (gMonitorThread || gService) {
     return;
   }
-
-  gService = new WindowsGamepadService();
-  gService->Startup();
-
-  if (sHWnd == nullptr) {
-    WNDCLASSW wc;
-    HMODULE hSelf = GetModuleHandle(nullptr);
-
-    if (!GetClassInfoW(hSelf, L"MozillaGamepadClass", &wc)) {
-      ZeroMemory(&wc, sizeof(WNDCLASSW));
-      wc.hInstance = hSelf;
-      wc.lpfnWndProc = GamepadWindowProc;
-      wc.lpszClassName = L"MozillaGamepadClass";
-      RegisterClassW(&wc);
-    }
-
-    sHWnd = CreateWindowW(L"MozillaGamepadClass", L"Gamepad Watcher",
-                          0, 0, 0, 0, 0,
-                          nullptr, nullptr, hSelf, nullptr);
-    RegisterRawInput(sHWnd, true);
-  }
+  sIsShutdown = false;
+  NS_NewThread(getter_AddRefs(gMonitorThread));
+  gMonitorThread->Dispatch(new StartWindowsGamepadServiceRunnable(),
+                           NS_DISPATCH_NORMAL);
 }
 
-void StopGamepadMonitoring()
+void
+StopGamepadMonitoring()
 {
-  if (!gService) {
+  AssertIsOnBackgroundThread();
+
+  if (sIsShutdown) {
     return;
   }
-
-  if (sHWnd) {
-    RegisterRawInput(sHWnd, false);
-    DestroyWindow(sHWnd);
-    sHWnd = nullptr;
-  }
-
-  gService->Shutdown();
-  delete gService;
-  gService = nullptr;
+  sIsShutdown = true;
+  gMonitorThread->Dispatch(new StopWindowsGamepadServiceRunnable(), NS_DISPATCH_NORMAL);
+  gMonitorThread->Shutdown();
+  gMonitorThread = nullptr;
 }
 
 } // namespace dom
 } // namespace mozilla
-
deleted file mode 100644
--- a/dom/interfaces/gamepad/moz.build
+++ /dev/null
@@ -1,11 +0,0 @@
-# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
-# vim: set filetype=python:
-# This Source Code Form is subject to the terms of the Mozilla Public
-# License, v. 2.0. If a copy of the MPL was not distributed with this
-# file, You can obtain one at http://mozilla.org/MPL/2.0/.
-
-XPIDL_MODULE = 'dom_gamepad'
-
-XPIDL_SOURCES = [
-  'nsIGamepadServiceTest.idl',
-  ]
deleted file mode 100644
--- a/dom/interfaces/gamepad/nsIGamepadServiceTest.idl
+++ /dev/null
@@ -1,29 +0,0 @@
-/* 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 "nsISupports.idl"
-
-interface nsIVariant;
-
-/*
- * This interface is intended only for use in tests.
- */
-[scriptable, uuid(c03ec4ed-8a7e-40e7-99da-c609f1760d0c)]
-interface nsIGamepadServiceTest : nsISupports
-{
-  const unsigned long NO_MAPPING = 0;
-  const unsigned long STANDARD_MAPPING = 1;
-
-  unsigned long addGamepad(in string id,
-                           in unsigned long mapping,
-                           in unsigned long numButtons,
-			   in unsigned long numAxes);
-  void removeGamepad(in unsigned long index);
-  void newButtonEvent(in unsigned long index, in unsigned long button,
-		      in boolean pressed);
-  void newButtonValueEvent(in unsigned long index, in unsigned long button,
-                           in boolean pressed, in double value);
-  void newAxisMoveEvent(in unsigned long index, in unsigned long axis,
-			in double value);
-};
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -168,20 +168,16 @@
 #include "nsIAccessibilityService.h"
 #endif
 
 #ifdef MOZ_NUWA_PROCESS
 #include "ipc/Nuwa.h"
 #endif
 #include "NuwaChild.h"
 
-#ifdef MOZ_GAMEPAD
-#include "mozilla/dom/GamepadService.h"
-#endif
-
 #ifndef MOZ_SIMPLEPUSH
 #include "mozilla/dom/PushNotifier.h"
 #endif
 
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/cellbroadcast/CellBroadcastIPCService.h"
 #include "mozilla/dom/icc/IccChild.h"
 #include "mozilla/dom/mobileconnection/MobileConnectionChild.h"
@@ -3225,28 +3221,16 @@ ContentChild::RecvPWebBrowserPersistDocu
 bool
 ContentChild::DeallocPWebBrowserPersistDocumentChild(PWebBrowserPersistDocumentChild* aActor)
 {
   delete aActor;
   return true;
 }
 
 bool
-ContentChild::RecvGamepadUpdate(const GamepadChangeEvent& aGamepadEvent)
-{
-#ifdef MOZ_GAMEPAD
-  RefPtr<GamepadService> svc(GamepadService::GetService());
-  if (svc) {
-    svc->Update(aGamepadEvent);
-  }
-#endif
-  return true;
-}
-
-bool
 ContentChild::RecvSetAudioSessionData(const nsID& aId,
                                       const nsString& aDisplayName,
                                       const nsString& aIconPath)
 {
 #if defined(XP_WIN)
     if (NS_FAILED(mozilla::widget::RecvAudioSessionData(aId, aDisplayName,
                                                         aIconPath))) {
       return true;
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -614,18 +614,16 @@ public:
 
   virtual PContentPermissionRequestChild*
   AllocPContentPermissionRequestChild(const InfallibleTArray<PermissionRequest>& aRequests,
                                       const IPC::Principal& aPrincipal,
                                       const TabId& aTabId) override;
   virtual bool
   DeallocPContentPermissionRequestChild(PContentPermissionRequestChild* actor) override;
 
-  virtual bool RecvGamepadUpdate(const GamepadChangeEvent& aGamepadEvent) override;
-
   // Windows specific - set up audio session
   virtual bool
   RecvSetAudioSessionData(const nsID& aId,
                           const nsString& aDisplayName,
                           const nsString& aIconPath) override;
 
 private:
   static void ForceKillTimerCallback(nsITimer* aTimer, void* aClosure);
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -256,20 +256,16 @@ using namespace mozilla::system;
 #include "nsIBrowserSearchService.h"
 #endif
 
 #ifdef MOZ_ENABLE_PROFILER_SPS
 #include "nsIProfiler.h"
 #include "nsIProfileSaveEvent.h"
 #endif
 
-#ifdef MOZ_GAMEPAD
-#include "mozilla/dom/GamepadMonitoring.h"
-#endif
-
 #ifndef MOZ_SIMPLEPUSH
 #include "mozilla/dom/PushNotifier.h"
 #endif
 
 #ifdef XP_WIN
 #include "mozilla/widget/AudioSession.h"
 #endif
 
@@ -2303,17 +2299,16 @@ ContentParent::InitializeMembers()
   mMetamorphosed = false;
   mSendPermissionUpdates = false;
   mCalledClose = false;
   mCalledKillHard = false;
   mCreatedPairedMinidumps = false;
   mShutdownPending = false;
   mIPCOpen = true;
   mHangMonitorActor = nullptr;
-  mHasGamepadListener = false;
 }
 
 bool
 ContentParent::LaunchSubprocess(ProcessPriority aInitialPriority /* = PROCESS_PRIORITY_FOREGROUND */)
 {
   PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER);
 
   std::vector<std::string> extraArgs;
@@ -5623,44 +5618,16 @@ ContentParent::GetBrowserConfiguration(c
     swr->GetRegistrations(aConfig.serviceWorkerRegistrations());
     return true;
   }
 
   return ContentChild::GetSingleton()->SendGetBrowserConfiguration(aURI, &aConfig);
 }
 
 bool
-ContentParent::RecvGamepadListenerAdded()
-{
-#ifdef MOZ_GAMEPAD
-  if (mHasGamepadListener) {
-    NS_WARNING("Gamepad listener already started, cannot start again!");
-    return false;
-  }
-  mHasGamepadListener = true;
-  StartGamepadMonitoring();
-#endif
-  return true;
-}
-
-bool
-ContentParent::RecvGamepadListenerRemoved()
-{
-#ifdef MOZ_GAMEPAD
-  if (!mHasGamepadListener) {
-    NS_WARNING("Gamepad listener already stopped, cannot stop again!");
-    return false;
-  }
-  mHasGamepadListener = false;
-  MaybeStopGamepadMonitoring();
-#endif
-  return true;
-}
-
-bool
 ContentParent::RecvProfile(const nsCString& aProfile)
 {
 #ifdef MOZ_ENABLE_PROFILER_SPS
   if (NS_WARN_IF(!mGatherer)) {
     return true;
   }
   mProfile = aProfile;
   mGatherer->GatheredOOPProfile();
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -541,18 +541,16 @@ public:
                                        const IPC::Principal& aPrincipal,
                                        const TabId& aTabId) override;
 
   virtual bool
   DeallocPContentPermissionRequestParent(PContentPermissionRequestParent* actor) override;
 
   virtual bool HandleWindowsMessages(const Message& aMsg) const override;
 
-  bool HasGamepadListener() const { return mHasGamepadListener; }
-
   void SetNuwaParent(NuwaParent* aNuwaParent) { mNuwaParent = aNuwaParent; }
 
   void ForkNewProcess(bool aBlocking);
 
   virtual bool RecvCreateWindow(PBrowserParent* aThisTabParent,
                                 PBrowserParent* aOpener,
                                 layout::PRenderFrameParent* aRenderFrame,
                                 const uint32_t& aChromeFlags,
@@ -1140,20 +1138,16 @@ private:
 
 
   virtual bool RecvUpdateDropEffect(const uint32_t& aDragAction,
                                     const uint32_t& aDropEffect) override;
 
   virtual bool RecvGetBrowserConfiguration(const nsCString& aURI,
                                            BrowserConfiguration* aConfig) override;
 
-  virtual bool RecvGamepadListenerAdded() override;
-
-  virtual bool RecvGamepadListenerRemoved() override;
-
   virtual bool RecvProfile(const nsCString& aProfile) override;
 
   virtual bool RecvGetGraphicsDeviceInitData(DeviceInitData* aOut) override;
 
   void StartProfiler(nsIProfilerStartParams* aParams);
 
   virtual bool RecvGetDeviceStorageLocation(const nsString& aType,
                                             nsString* aPath) override;
@@ -1220,17 +1214,16 @@ private:
 
   // True only the if process is already a browser or app or has
   // been transformed into one.
   bool mMetamorphosed;
 
   bool mSendPermissionUpdates;
   bool mIsForBrowser;
   bool mIsNuwaProcess;
-  bool mHasGamepadListener;
 
   // These variables track whether we've called Close() and KillHard() on our
   // channel.
   bool mCalledClose;
   bool mCalledKillHard;
   bool mCreatedPairedMinidumps;
   bool mShutdownPending;
   bool mIPCOpen;
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -334,47 +334,17 @@ struct DomainPolicyClone
 {
     bool        active;
     URIParams[] blacklist;
     URIParams[] whitelist;
     URIParams[] superBlacklist;
     URIParams[] superWhitelist;
 };
 
-struct GamepadAdded {
-    nsString id;
-    uint32_t index;
-    uint32_t mapping;
-    uint32_t num_buttons;
-    uint32_t num_axes;
-};
 
-struct GamepadRemoved {
-    uint32_t index;
-};
-
-struct GamepadAxisInformation {
-    uint32_t index;
-    uint32_t axis;
-    double value;
-};
-
-struct GamepadButtonInformation {
-    uint32_t index;
-    uint32_t button;
-    bool pressed;
-    double value;
-};
-
-union GamepadChangeEvent {
-    GamepadAdded;
-    GamepadRemoved;
-    GamepadAxisInformation;
-    GamepadButtonInformation;
-};
 
 struct FrameScriptInfo
 {
     nsString url;
     bool runInGlobalScope;
 };
 
 struct AndroidSystemInfo
@@ -632,21 +602,16 @@ child:
 
     /**
      * Requests a full native update of a native plugin child window. This is
      * a Windows specific call.
      */
     async UpdateWindow(uintptr_t aChildId);
 
     /**
-     * Send gamepad status update to child.
-     */
-    async GamepadUpdate(GamepadChangeEvent aGamepadEvent);
-
-    /**
      * Notify the child that presentation receiver has been launched with the
      * correspondent iframe.
      */
     async NotifyPresentationReceiverLaunched(PBrowser aIframe, nsString aSessionId);
 
     /**
      * Notify the child that the info about a presentation receiver needs to be
      * cleaned up.
@@ -1122,26 +1087,16 @@ parent:
                                     TabId tabId);
 
     /**
      * Send ServiceWorkerRegistrationData to child process.
      */
     sync GetBrowserConfiguration(nsCString aUri)
         returns (BrowserConfiguration aConfig);
 
-    /*
-     * Tells the parent to start the gamepad listening service if it hasn't already.
-     */
-    async GamepadListenerAdded();
-
-    /**
-     * Tells the parent to stop the gamepad listening service if it hasn't already.
-     */
-    async GamepadListenerRemoved();
-
     async Profile(nsCString aProfile);
 
     /**
      * Request graphics initialization information from the parent.
      */
     sync GetGraphicsDeviceInitData()
         returns (DeviceInitData aData);
 
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -229,8 +229,10 @@ RewriteYouTubeEmbedPathParams=Rewriting 
 # LOCALIZATION NOTE: Do not translate "ServiceWorker". %1$S is the ServiceWorker scope URL. %2$S is an error string.
 PushMessageDecryptionFailure=The ServiceWorker for scope ‘%1$S’ encountered an error decrypting a push message: ‘%2$S’. For help with encryption, please see https://developer.mozilla.org/en-US/docs/Web/API/Push_API/Using_the_Push_API#Encryption
 # LOCALIZATION NOTE: %1$S is the type of a DOM event. 'passive' is a literal parameter from the DOM spec.
 PreventDefaultFromPassiveListenerWarning=Ignoring ‘preventDefault()’ call on event of type ‘%1$S’ from a listener registered as ‘passive’.
 FileLastModifiedDateWarning=File.lastModifiedDate is deprecated. Use File.lastModified instead.
 ChromeScriptedDOMParserWithoutPrincipal=Creating DOMParser without a principal is deprecated.
 IIRFilterChannelCountChangeWarning=IIRFilterNode channel count changes may produce audio glitches.
 BiquadFilterChannelCountChangeWarning=BiquadFilterNode channel count changes may produce audio glitches.
+# LOCALIZATION NOTE: %1$S is the unanimatable paced property.
+UnanimatablePacedProperty=Paced property ‘%1$S’ is not an animatable property.
--- a/dom/media/Benchmark.cpp
+++ b/dom/media/Benchmark.cpp
@@ -202,18 +202,17 @@ BenchmarkPlayback::DemuxNextSample()
 }
 
 void
 BenchmarkPlayback::InitDecoder(TrackInfo&& aInfo)
 {
   MOZ_ASSERT(OnThread());
 
   RefPtr<PDMFactory> platform = new PDMFactory();
-  mDecoder = platform->CreateDecoder(aInfo, mDecoderTaskQueue, this,
-     /* DecoderDoctorDiagnostics* */ nullptr);
+  mDecoder = platform->CreateDecoder({ aInfo, mDecoderTaskQueue, reinterpret_cast<MediaDataDecoderCallback*>(this) });
   if (!mDecoder) {
     MainThreadShutdown();
     return;
   }
   RefPtr<Benchmark> ref(mMainThreadState);
   mDecoder->Init()->Then(
     Thread(), __func__,
     [this, ref](TrackInfo::TrackType aTrackType) {
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -402,38 +402,37 @@ MediaFormatReader::EnsureDecoderCreated(
     }
   }
 
   decoder.mDecoderInitialized = false;
 
   MonitorAutoLock mon(decoder.mMonitor);
 
   switch (aTrack) {
-    case TrackType::kAudioTrack:
-      decoder.mDecoder =
-        mPlatform->CreateDecoder(decoder.mInfo ?
-                                   *decoder.mInfo->GetAsAudioInfo() :
-                                   mInfo.mAudio,
-                                 decoder.mTaskQueue,
-                                 decoder.mCallback,
-                                 /* DecoderDoctorDiagnostics* */ nullptr);
+    case TrackType::kAudioTrack: {
+      decoder.mDecoder = mPlatform->CreateDecoder({
+        decoder.mInfo ? *decoder.mInfo->GetAsAudioInfo() : mInfo.mAudio,
+        decoder.mTaskQueue,
+        decoder.mCallback.get()
+      });
       break;
-    case TrackType::kVideoTrack:
+    }
+
+    case TrackType::kVideoTrack: {
       // Decoders use the layers backend to decide if they can use hardware decoding,
       // so specify LAYERS_NONE if we want to forcibly disable it.
-      decoder.mDecoder =
-        mPlatform->CreateDecoder(mVideo.mInfo ?
-                                   *mVideo.mInfo->GetAsVideoInfo() :
-                                   mInfo.mVideo,
-                                 decoder.mTaskQueue,
-                                 decoder.mCallback,
-                                 /* DecoderDoctorDiagnostics* */ nullptr,
-                                 mLayersBackendType,
-                                 GetImageContainer());
+      decoder.mDecoder = mPlatform->CreateDecoder({
+        mVideo.mInfo ? *mVideo.mInfo->GetAsVideoInfo() : mInfo.mVideo,
+        decoder.mTaskQueue,
+        decoder.mCallback.get(),
+        mLayersBackendType,
+        GetImageContainer(),
+      });
       break;
+    }
     default:
       break;
   }
   if (decoder.mDecoder ) {
     decoder.mDescription = decoder.mDecoder->GetDescriptionName();
   } else {
     decoder.mDescription = "error creating decoder";
   }
--- a/dom/media/MediaInfo.h
+++ b/dom/media/MediaInfo.h
@@ -183,26 +183,31 @@ public:
     kDegree_180 = 180,
     kDegree_270 = 270,
   };
   VideoInfo()
     : VideoInfo(-1, -1)
   {
   }
 
-  VideoInfo(int32_t aWidth, int32_t aHeight)
+  explicit VideoInfo(int32_t aWidth, int32_t aHeight)
+    : VideoInfo(nsIntSize(aWidth, aHeight))
+  {
+  }
+
+  explicit VideoInfo(const nsIntSize& aSize)
     : TrackInfo(kVideoTrack, NS_LITERAL_STRING("2"), NS_LITERAL_STRING("main"),
                 EmptyString(), EmptyString(), true, 2)
-    , mDisplay(nsIntSize(aWidth, aHeight))
+    , mDisplay(aSize)
     , mStereoMode(StereoMode::MONO)
-    , mImage(nsIntSize(aWidth, aHeight))
+    , mImage(aSize)
     , mCodecSpecificConfig(new MediaByteBuffer)
     , mExtraData(new MediaByteBuffer)
     , mRotation(kDegree_0)
-    , mImageRect(nsIntRect(0, 0, aWidth, aHeight))
+    , mImageRect(nsIntRect(nsIntPoint(), aSize))
   {
   }
 
   VideoInfo(const VideoInfo& aOther)
     : TrackInfo(aOther)
     , mDisplay(aOther.mDisplay)
     , mStereoMode(aOther.mStereoMode)
     , mImage(aOther.mImage)
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -1189,30 +1189,30 @@ public:
 
     nsresult rv;
 
     if (mAudioDevice) {
       rv = mAudioDevice->Allocate(GetInvariant(mConstraints.mAudio),
                                   mPrefs, mOrigin);
       if (NS_FAILED(rv)) {
         LOG(("Failed to allocate audiosource %d",rv));
-        Fail(NS_LITERAL_STRING("SourceUnavailableError"),
+        Fail(NS_LITERAL_STRING("NotReadableError"),
              NS_LITERAL_STRING("Failed to allocate audiosource"));
         return NS_OK;
       }
     }
     if (mVideoDevice) {
       rv = mVideoDevice->Allocate(GetInvariant(mConstraints.mVideo),
                                   mPrefs, mOrigin);
       if (NS_FAILED(rv)) {
         LOG(("Failed to allocate videosource %d\n",rv));
         if (mAudioDevice) {
           mAudioDevice->GetSource()->Deallocate();
         }
-        Fail(NS_LITERAL_STRING("SourceUnavailableError"),
+        Fail(NS_LITERAL_STRING("NotReadableError"),
              NS_LITERAL_STRING("Failed to allocate videosource"));
         return NS_OK;
       }
     }
     PeerIdentity* peerIdentity = nullptr;
     if (!mConstraints.mPeerIdentity.IsEmpty()) {
       peerIdentity = new PeerIdentity(mConstraints.mPeerIdentity);
     }
--- a/dom/media/MediaStreamError.cpp
+++ b/dom/media/MediaStreamError.cpp
@@ -20,19 +20,18 @@ BaseMediaMgrError::BaseMediaMgrError(con
   if (mMessage.IsEmpty()) {
     if (mName.EqualsLiteral("NotFoundError")) {
       mMessage.AssignLiteral("The object can not be found here.");
     } else if (mName.EqualsLiteral("NotAllowedError")) {
       mMessage.AssignLiteral("The request is not allowed by the user agent "
                              "or the platform in the current context.");
     } else if (mName.EqualsLiteral("SecurityError")) {
       mMessage.AssignLiteral("The operation is insecure.");
-    } else if (mName.EqualsLiteral("SourceUnavailableError")) {
-      mMessage.AssignLiteral("The source of the MediaStream could not be "
-          "accessed due to a hardware error (e.g. lock from another process).");
+    } else if (mName.EqualsLiteral("NotReadableError")) {
+      mMessage.AssignLiteral("The I/O read operation failed.");
     } else if (mName.EqualsLiteral("InternalError")) {
       mMessage.AssignLiteral("Internal error.");
     } else if (mName.EqualsLiteral("NotSupportedError")) {
       mMessage.AssignLiteral("The operation is not supported.");
     } else if (mName.EqualsLiteral("OverconstrainedError")) {
       mMessage.AssignLiteral("Constraints could be not satisfied.");
     }
   }
--- a/dom/media/VideoFrameContainer.cpp
+++ b/dom/media/VideoFrameContainer.cpp
@@ -131,16 +131,17 @@ void VideoFrameContainer::ClearCurrentFr
   MutexAutoLock lock(mMutex);
 
   // See comment in SetCurrentFrame for the reasoning behind
   // using a kungFuDeathGrip here.
   nsTArray<ImageContainer::OwningImage> kungFuDeathGrip;
   mImageContainer->GetCurrentImages(&kungFuDeathGrip);
 
   mImageContainer->ClearAllImages();
+  mImageContainer->ClearCachedResources();
 }
 
 void VideoFrameContainer::ClearFutureFrames()
 {
   MutexAutoLock lock(mMutex);
 
   // See comment in SetCurrentFrame for the reasoning behind
   // using a kungFuDeathGrip here.
--- a/dom/media/fmp4/MP4Decoder.cpp
+++ b/dom/media/fmp4/MP4Decoder.cpp
@@ -217,20 +217,17 @@ CreateTestH264Decoder(layers::LayersBack
   aConfig.mDuration = 40000;
   aConfig.mMediaTime = 0;
   aConfig.mImage = aConfig.mDisplay = nsIntSize(640, 360);
   aConfig.mExtraData = new MediaByteBuffer();
   aConfig.mExtraData->AppendElements(sTestH264ExtraData,
                                      MOZ_ARRAY_LENGTH(sTestH264ExtraData));
 
   RefPtr<PDMFactory> platform = new PDMFactory();
-  RefPtr<MediaDataDecoder> decoder(
-    platform->CreateDecoder(aConfig, aTaskQueue, nullptr,
-                            /* DecoderDoctorDiagnostics* */ nullptr,
-                            aBackend, nullptr));
+  RefPtr<MediaDataDecoder> decoder(platform->CreateDecoder({ aConfig, aTaskQueue, aBackend }));
 
   return decoder.forget();
 }
 
 /* static */ already_AddRefed<dom::Promise>
 MP4Decoder::IsVideoAccelerated(layers::LayersBackend aBackend, nsIGlobalObject* aParent)
 {
   MOZ_ASSERT(NS_IsMainThread());
--- a/dom/media/platforms/PDMFactory.cpp
+++ b/dom/media/platforms/PDMFactory.cpp
@@ -96,126 +96,94 @@ PDMFactory::EnsureInit() const
       nsCOMPtr<nsIRunnable> runnable =
         NS_NewRunnableFunction([]() { ClearOnShutdown(&sInstance); });
       NS_DispatchToMainThread(runnable);
     }
   }
 }
 
 already_AddRefed<MediaDataDecoder>
-PDMFactory::CreateDecoder(const TrackInfo& aConfig,
-                          TaskQueue* aTaskQueue,
-                          MediaDataDecoderCallback* aCallback,
-                          DecoderDoctorDiagnostics* aDiagnostics,
-                          layers::LayersBackend aLayersBackend,
-                          layers::ImageContainer* aImageContainer) const
+PDMFactory::CreateDecoder(const CreateDecoderParams& aParams)
 {
-  bool isEncrypted = mEMEPDM && aConfig.mCrypto.mValid;
+  const TrackInfo& config = aParams.mConfig;
+  bool isEncrypted = mEMEPDM && config.mCrypto.mValid;
 
   if (isEncrypted) {
-    return CreateDecoderWithPDM(mEMEPDM,
-                                aConfig,
-                                aTaskQueue,
-                                aCallback,
-                                aDiagnostics,
-                                aLayersBackend,
-                                aImageContainer);
+    return CreateDecoderWithPDM(mEMEPDM, aParams);
   }
 
-  if (aDiagnostics) {
+  DecoderDoctorDiagnostics* diagnostics = aParams.mDiagnostics;
+  if (diagnostics) {
     // If libraries failed to load, the following loop over mCurrentPDMs
     // will not even try to use them. So we record failures now.
     if (mWMFFailedToLoad) {
-      aDiagnostics->SetWMFFailedToLoad();
+      diagnostics->SetWMFFailedToLoad();
     }
     if (mFFmpegFailedToLoad) {
-      aDiagnostics->SetFFmpegFailedToLoad();
+      diagnostics->SetFFmpegFailedToLoad();
     }
     if (mGMPPDMFailedToStartup) {
-      aDiagnostics->SetGMPPDMFailedToStartup();
+      diagnostics->SetGMPPDMFailedToStartup();
     }
   }
 
   for (auto& current : mCurrentPDMs) {
-    if (!current->SupportsMimeType(aConfig.mMimeType, aDiagnostics)) {
+    if (!current->SupportsMimeType(config.mMimeType, diagnostics)) {
       continue;
     }
-    RefPtr<MediaDataDecoder> m =
-      CreateDecoderWithPDM(current,
-                           aConfig,
-                           aTaskQueue,
-                           aCallback,
-                           aDiagnostics,
-                           aLayersBackend,
-                           aImageContainer);
+    RefPtr<MediaDataDecoder> m = CreateDecoderWithPDM(current, aParams);
     if (m) {
       return m.forget();
     }
   }
   NS_WARNING("Unable to create a decoder, no platform found.");
   return nullptr;
 }
 
 already_AddRefed<MediaDataDecoder>
 PDMFactory::CreateDecoderWithPDM(PlatformDecoderModule* aPDM,
-                                 const TrackInfo& aConfig,
-                                 TaskQueue* aTaskQueue,
-                                 MediaDataDecoderCallback* aCallback,
-                                 DecoderDoctorDiagnostics* aDiagnostics,
-                                 layers::LayersBackend aLayersBackend,
-                                 layers::ImageContainer* aImageContainer) const
+                                 const CreateDecoderParams& aParams)
 {
   MOZ_ASSERT(aPDM);
   RefPtr<MediaDataDecoder> m;
 
-  if (aConfig.GetAsAudioInfo()) {
-    m = aPDM->CreateAudioDecoder(*aConfig.GetAsAudioInfo(),
-                                 aTaskQueue,
-                                 aCallback,
-                                 aDiagnostics);
+  const TrackInfo& config = aParams.mConfig;
+  if (config.IsAudio()) {
+    m = aPDM->CreateAudioDecoder(aParams);
     return m.forget();
   }
 
-  if (!aConfig.GetAsVideoInfo()) {
+  if (!config.IsVideo()) {
     return nullptr;
   }
 
-  MediaDataDecoderCallback* callback = aCallback;
+  MediaDataDecoderCallback* callback = aParams.mCallback;
   RefPtr<DecoderCallbackFuzzingWrapper> callbackWrapper;
   if (MediaPrefs::PDMFuzzingEnabled()) {
-    callbackWrapper = new DecoderCallbackFuzzingWrapper(aCallback);
+    callbackWrapper = new DecoderCallbackFuzzingWrapper(callback);
     callbackWrapper->SetVideoOutputMinimumInterval(
       TimeDuration::FromMilliseconds(MediaPrefs::PDMFuzzingInterval()));
     callbackWrapper->SetDontDelayInputExhausted(!MediaPrefs::PDMFuzzingDelayInputExhausted());
     callback = callbackWrapper.get();
   }
 
-  if (H264Converter::IsH264(aConfig)) {
-    RefPtr<H264Converter> h
-      = new H264Converter(aPDM,
-                          *aConfig.GetAsVideoInfo(),
-                          aLayersBackend,
-                          aImageContainer,
-                          aTaskQueue,
-                          callback,
-                          aDiagnostics);
+  CreateDecoderParams params = aParams;
+  params.mCallback = callback;
+
+  if (H264Converter::IsH264(config)) {
+    RefPtr<H264Converter> h = new H264Converter(aPDM, params);
     const nsresult rv = h->GetLastError();
     if (NS_SUCCEEDED(rv) || rv == NS_ERROR_NOT_INITIALIZED) {
       // The H264Converter either successfully created the wrapped decoder,
       // or there wasn't enough AVCC data to do so. Otherwise, there was some
       // problem, for example WMF DLLs were missing.
       m = h.forget();
     }
   } else {
-    m = aPDM->CreateVideoDecoder(*aConfig.GetAsVideoInfo(),
-                                 aLayersBackend,
-                                 aImageContainer,
-                                 aTaskQueue,
-                                 callback,
-                                 aDiagnostics);
+    m = aPDM->CreateVideoDecoder(params);
   }
 
   if (callbackWrapper && m) {
     m = new DecoderFuzzingWrapper(m.forget(), callbackWrapper.forget());
   }
 
   return m.forget();
 }
--- a/dom/media/platforms/PDMFactory.h
+++ b/dom/media/platforms/PDMFactory.h
@@ -25,22 +25,17 @@ public:
   PDMFactory();
 
   // Factory method that creates the appropriate PlatformDecoderModule for
   // the platform we're running on. Caller is responsible for deleting this
   // instance. It's expected that there will be multiple
   // PlatformDecoderModules alive at the same time.
   // This is called on the decode task queue.
   already_AddRefed<MediaDataDecoder>
-  CreateDecoder(const TrackInfo& aConfig,
-                TaskQueue* aTaskQueue,
-                MediaDataDecoderCallback* aCallback,
-                DecoderDoctorDiagnostics* aDiagnostics,
-                layers::LayersBackend aLayersBackend = layers::LayersBackend::LAYERS_NONE,
-                layers::ImageContainer* aImageContainer = nullptr) const;
+  CreateDecoder(const CreateDecoderParams& aParams);
 
   bool SupportsMimeType(const nsACString& aMimeType,
                         DecoderDoctorDiagnostics* aDiagnostics) const;
 
 #ifdef MOZ_EME
   // Creates a PlatformDecoderModule that uses a CDMProxy to decrypt or
   // decrypt-and-decode EME encrypted content. If the CDM only decrypts and
   // does not decode, we create a PDM and use that to create MediaDataDecoders
@@ -56,22 +51,17 @@ private:
   bool StartupPDM(PlatformDecoderModule* aPDM);
   // Returns the first PDM in our list supporting the mimetype.
   already_AddRefed<PlatformDecoderModule>
   GetDecoder(const nsACString& aMimeType,
              DecoderDoctorDiagnostics* aDiagnostics) const;
 
   already_AddRefed<MediaDataDecoder>
   CreateDecoderWithPDM(PlatformDecoderModule* aPDM,
-                       const TrackInfo& aConfig,
-                       TaskQueue* aTaskQueue,
-                       MediaDataDecoderCallback* aCallback,
-                       DecoderDoctorDiagnostics* aDiagnostics,
-                       layers::LayersBackend aLayersBackend,
-                       layers::ImageContainer* aImageContainer) const;
+                       const CreateDecoderParams& aParams);
 
   nsTArray<RefPtr<PlatformDecoderModule>> mCurrentPDMs;
   RefPtr<PlatformDecoderModule> mEMEPDM;
 
   bool mWMFFailedToLoad = false;
   bool mFFmpegFailedToLoad = false;
   bool mGMPPDMFailedToStartup = false;
 
--- a/dom/media/platforms/PlatformDecoderModule.h
+++ b/dom/media/platforms/PlatformDecoderModule.h
@@ -27,16 +27,64 @@ class ImageContainer;
 
 class MediaDataDecoder;
 class MediaDataDecoderCallback;
 class TaskQueue;
 class CDMProxy;
 
 static LazyLogModule sPDMLog("PlatformDecoderModule");
 
+struct CreateDecoderParams {
+  explicit CreateDecoderParams(const TrackInfo& aConfig)
+    : mConfig(aConfig)
+  {}
+
+  template <typename T1, typename... Ts>
+  CreateDecoderParams(const TrackInfo& aConfig, T1 a1, Ts... as)
+    : mConfig(aConfig)
+  {
+    Set(a1, as...);
+  }
+
+  const VideoInfo& VideoConfig() const
+  {
+    MOZ_ASSERT(mConfig.IsVideo());
+    return *mConfig.GetAsVideoInfo();
+  }
+
+  const AudioInfo& AudioConfig() const
+  {
+    MOZ_ASSERT(mConfig.IsAudio());
+    return *mConfig.GetAsAudioInfo();
+  }
+
+  const TrackInfo& mConfig;
+  TaskQueue* mTaskQueue = nullptr;
+  MediaDataDecoderCallback* mCallback = nullptr;
+  DecoderDoctorDiagnostics* mDiagnostics = nullptr;
+  layers::ImageContainer* mImageContainer = nullptr;
+  layers::LayersBackend mLayersBackend = layers::LayersBackend::LAYERS_NONE;
+
+private:
+  void Set(TaskQueue* aTaskQueue) { mTaskQueue = aTaskQueue; }
+  void Set(MediaDataDecoderCallback* aCallback) { mCallback = aCallback; }
+  void Set(DecoderDoctorDiagnostics* aDiagnostics) { mDiagnostics = aDiagnostics; }
+  void Set(layers::ImageContainer* aImageContainer) { mImageContainer = aImageContainer; }
+  void Set(layers::LayersBackend aLayersBackend) { mLayersBackend = aLayersBackend; }
+  template <typename T1, typename T2, typename... Ts>
+  void Set(T1 a1, T2 a2, Ts... as)
+  {
+    // Parameter pack expansion trick, to call Set() on each argument.
+    using expander = int[];
+    (void)expander {
+      (Set(a1), 0), (Set(a2), 0), (Set(as), 0)...
+    };
+  }
+};
+
 // The PlatformDecoderModule interface is used by the MediaFormatReader to
 // abstract access to decoders provided by various
 // platforms.
 // Each platform (Windows, MacOSX, Linux, B2G etc) must implement a
 // PlatformDecoderModule to provide access to its decoders in order to get
 // decompressed H.264/AAC from the MediaFormatReader.
 //
 // Decoding is asynchronous, and should be performed on the task queue
@@ -83,38 +131,30 @@ protected:
   // not hold a reference to it.
   // Output and errors should be returned to the reader via aCallback.
   // On Windows the task queue's threads in have MSCOM initialized with
   // COINIT_MULTITHREADED.
   // Returns nullptr if the decoder can't be created.
   // It is safe to store a reference to aConfig.
   // This is called on the decode task queue.
   virtual already_AddRefed<MediaDataDecoder>
-  CreateVideoDecoder(const VideoInfo& aConfig,
-                     layers::LayersBackend aLayersBackend,
-                     layers::ImageContainer* aImageContainer,
-                     TaskQueue* aTaskQueue,
-                     MediaDataDecoderCallback* aCallback,
-                     DecoderDoctorDiagnostics* aDiagnostics) = 0;
+  CreateVideoDecoder(const CreateDecoderParams& aParams) = 0;
 
   // Creates an Audio decoder with the specified properties.
   // Asynchronous decoding of audio should be done in runnables dispatched to
   // aAudioTaskQueue. If the task queue isn't needed, the decoder should
   // not hold a reference to it.
   // Output and errors should be returned to the reader via aCallback.
   // Returns nullptr if the decoder can't be created.
   // On Windows the task queue's threads in have MSCOM initialized with
   // COINIT_MULTITHREADED.
   // It is safe to store a reference to aConfig.
   // This is called on the decode task queue.
   virtual already_AddRefed<MediaDataDecoder>
-  CreateAudioDecoder(const AudioInfo& aConfig,
-                     TaskQueue* aTaskQueue,
-                     MediaDataDecoderCallback* aCallback,
-                     DecoderDoctorDiagnostics* aDiagnostics) = 0;
+  CreateAudioDecoder(const CreateDecoderParams& aParams) = 0;
 };
 
 enum MediaDataDecoderError {
   FATAL_ERROR,
   DECODE_ERROR
 };
 
 // A callback used by MediaDataDecoder to return output/errors to the
--- a/dom/media/platforms/agnostic/AgnosticDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/AgnosticDecoderModule.cpp
@@ -18,51 +18,37 @@ AgnosticDecoderModule::SupportsMimeType(
 {
   return VPXDecoder::IsVPX(aMimeType) ||
     OpusDataDecoder::IsOpus(aMimeType) ||
     VorbisDataDecoder::IsVorbis(aMimeType) ||
     WaveDataDecoder::IsWave(aMimeType);
 }
 
 already_AddRefed<MediaDataDecoder>
-AgnosticDecoderModule::CreateVideoDecoder(const VideoInfo& aConfig,
-                                          layers::LayersBackend aLayersBackend,
-                                          layers::ImageContainer* aImageContainer,
-                                          TaskQueue* aTaskQueue,
-                                          MediaDataDecoderCallback* aCallback,
-                                          DecoderDoctorDiagnostics* aDiagnostics)
+AgnosticDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
 {
   RefPtr<MediaDataDecoder> m;
 
-  if (VPXDecoder::IsVPX(aConfig.mMimeType)) {
-    m = new VPXDecoder(*aConfig.GetAsVideoInfo(),
-                       aImageContainer,
-                       aTaskQueue,
-                       aCallback);
+  if (VPXDecoder::IsVPX(aParams.mConfig.mMimeType)) {
+    m = new VPXDecoder(aParams);
   }
 
   return m.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
-AgnosticDecoderModule::CreateAudioDecoder(const AudioInfo& aConfig,
-                                          TaskQueue* aTaskQueue,
-                                          MediaDataDecoderCallback* aCallback,
-                                          DecoderDoctorDiagnostics* aDiagnostics)
+AgnosticDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams)
 {
   RefPtr<MediaDataDecoder> m;
 
-  if (VorbisDataDecoder::IsVorbis(aConfig.mMimeType)) {
-    m = new VorbisDataDecoder(*aConfig.GetAsAudioInfo(),
-                              aTaskQueue,
-                              aCallback);
-  } else if (OpusDataDecoder::IsOpus(aConfig.mMimeType)) {
-    m = new OpusDataDecoder(*aConfig.GetAsAudioInfo(),
-                            aTaskQueue,
-                            aCallback);
-  } else if (WaveDataDecoder::IsWave(aConfig.mMimeType)) {
-    m = new WaveDataDecoder(*aConfig.GetAsAudioInfo(), aCallback);
+  const TrackInfo& config = aParams.mConfig;
+  if (VorbisDataDecoder::IsVorbis(config.mMimeType)) {
+    m = new VorbisDataDecoder(aParams);
+  } else if (OpusDataDecoder::IsOpus(config.mMimeType)) {
+    m = new OpusDataDecoder(aParams);
+  } else if (WaveDataDecoder::IsWave(config.mMimeType)) {
+    m = new WaveDataDecoder(aParams);
   }
 
   return m.forget();
 }
 
 } // namespace mozilla
--- a/dom/media/platforms/agnostic/AgnosticDecoderModule.h
+++ b/dom/media/platforms/agnostic/AgnosticDecoderModule.h
@@ -17,26 +17,18 @@ public:
   DecoderNeedsConversion(const TrackInfo& aConfig) const override
   {
     return ConversionRequired::kNeedNone;
   }
 
 protected:
   // Decode thread.
   already_AddRefed<MediaDataDecoder>
-  CreateVideoDecoder(const VideoInfo& aConfig,
-                     layers::LayersBackend aLayersBackend,
-                     layers::ImageContainer* aImageContainer,
-                     TaskQueue* aTaskQueue,
-                     MediaDataDecoderCallback* aCallback,
-                     DecoderDoctorDiagnostics* aDiagnostics) override;
+  CreateVideoDecoder(const CreateDecoderParams& aParams) override;
 
   // Decode thread.
   already_AddRefed<MediaDataDecoder>
-  CreateAudioDecoder(const AudioInfo& aConfig,
-                     TaskQueue* aTaskQueue,
-                     MediaDataDecoderCallback* aCallback,
-                     DecoderDoctorDiagnostics* aDiagnostics) override;
+  CreateAudioDecoder(const CreateDecoderParams& aParams) override;
 };
 
 } // namespace mozilla
 
 #endif /* AgnosticDecoderModule_h_ */
--- a/dom/media/platforms/agnostic/BlankDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/BlankDecoderModule.cpp
@@ -21,21 +21,20 @@ namespace mozilla {
 
 // Decoder that uses a passed in object's Create function to create blank
 // MediaData objects.
 template<class BlankMediaDataCreator>
 class BlankMediaDataDecoder : public MediaDataDecoder {
 public:
 
   BlankMediaDataDecoder(BlankMediaDataCreator* aCreator,
-                        MediaDataDecoderCallback* aCallback,
-                        TrackInfo::TrackType aType)
+                        const CreateDecoderParams& aParams)
     : mCreator(aCreator)
-    , mCallback(aCallback)
-    , mType(aType)
+    , mCallback(aParams.mCallback)
+    , mType(aParams.mConfig.GetType())
   {
   }
 
   RefPtr<InitPromise> Init() override {
     return InitPromise::CreateAndResolve(mType, __func__);
   }
 
   nsresult Shutdown() override {
@@ -194,44 +193,34 @@ private:
   uint32_t mSampleRate;
 };
 
 class BlankDecoderModule : public PlatformDecoderModule {
 public:
 
   // Decode thread.
   already_AddRefed<MediaDataDecoder>
-  CreateVideoDecoder(const VideoInfo& aConfig,
-                     layers::LayersBackend aLayersBackend,
-                     layers::ImageContainer* aImageContainer,
-                     TaskQueue* aTaskQueue,
-                     MediaDataDecoderCallback* aCallback,
-                     DecoderDoctorDiagnostics* aDiagnostics) override {
+  CreateVideoDecoder(const CreateDecoderParams& aParams) override {
+    const VideoInfo& config = aParams.VideoConfig();
     BlankVideoDataCreator* creator = new BlankVideoDataCreator(
-      aConfig.mDisplay.width, aConfig.mDisplay.height, aImageContainer);
+      config.mDisplay.width, config.mDisplay.height, aParams.mImageContainer);
     RefPtr<MediaDataDecoder> decoder =
-      new BlankMediaDataDecoder<BlankVideoDataCreator>(creator,
-                                                       aCallback,
-                                                       TrackInfo::kVideoTrack);
+      new BlankMediaDataDecoder<BlankVideoDataCreator>(creator, aParams);
     return decoder.forget();
   }
 
   // Decode thread.
   already_AddRefed<MediaDataDecoder>
-  CreateAudioDecoder(const AudioInfo& aConfig,
-                     TaskQueue* aTaskQueue,
-                     MediaDataDecoderCallback* aCallback,
-                     DecoderDoctorDiagnostics* aDiagnostics) override {
+  CreateAudioDecoder(const CreateDecoderParams& aParams) override {
+    const AudioInfo& config = aParams.AudioConfig();
     BlankAudioDataCreator* creator = new BlankAudioDataCreator(
-      aConfig.mChannels, aConfig.mRate);
+      config.mChannels, config.mRate);
 
     RefPtr<MediaDataDecoder> decoder =
-      new BlankMediaDataDecoder<BlankAudioDataCreator>(creator,
-                                                       aCallback,
-                                                       TrackInfo::kAudioTrack);
+      new BlankMediaDataDecoder<BlankAudioDataCreator>(creator, aParams);
     return decoder.forget();
   }
 
   bool
   SupportsMimeType(const nsACString& aMimeType,
                    DecoderDoctorDiagnostics* aDiagnostics) const override
   {
     return true;
--- a/dom/media/platforms/agnostic/OpusDecoder.cpp
+++ b/dom/media/platforms/agnostic/OpusDecoder.cpp
@@ -15,22 +15,20 @@
 #include <stdint.h>
 #include <inttypes.h>  // For PRId64
 
 #define OPUS_DEBUG(arg, ...) MOZ_LOG(sPDMLog, mozilla::LogLevel::Debug, \
     ("OpusDataDecoder(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
 
 namespace mozilla {
 
-OpusDataDecoder::OpusDataDecoder(const AudioInfo& aConfig,
-                                 TaskQueue* aTaskQueue,
-                                 MediaDataDecoderCallback* aCallback)
-  : mInfo(aConfig)
-  , mTaskQueue(aTaskQueue)
-  , mCallback(aCallback)
+OpusDataDecoder::OpusDataDecoder(const CreateDecoderParams& aParams)
+  : mInfo(aParams.AudioConfig())
+  , mTaskQueue(aParams.mTaskQueue)
+  , mCallback(aParams.mCallback)
   , mOpusDecoder(nullptr)
   , mSkip(0)
   , mDecodedHeader(false)
   , mPaddingDiscarded(false)
   , mFrames(0)
   , mIsFlushing(false)
 {
 }
--- a/dom/media/platforms/agnostic/OpusDecoder.h
+++ b/dom/media/platforms/agnostic/OpusDecoder.h
@@ -12,19 +12,17 @@
 #include "mozilla/Maybe.h"
 #include "nsAutoPtr.h"
 
 namespace mozilla {
 
 class OpusDataDecoder : public MediaDataDecoder
 {
 public:
-  OpusDataDecoder(const AudioInfo& aConfig,
-                  TaskQueue* aTaskQueue,
-                  MediaDataDecoderCallback* aCallback);
+  explicit OpusDataDecoder(const CreateDecoderParams& aParams);
   ~OpusDataDecoder();
 
   RefPtr<InitPromise> Init() override;
   nsresult Input(MediaRawData* aSample) override;
   nsresult Flush() override;
   nsresult Drain() override;
   nsresult Shutdown() override;
   const char* GetDescriptionName() const override
--- a/dom/media/platforms/agnostic/VPXDecoder.cpp
+++ b/dom/media/platforms/agnostic/VPXDecoder.cpp
@@ -27,26 +27,23 @@ static int MimeTypeToCodec(const nsACStr
   if (aMimeType.EqualsLiteral("video/webm; codecs=vp8")) {
     return VPXDecoder::Codec::VP8;
   } else if (aMimeType.EqualsLiteral("video/webm; codecs=vp9")) {
     return VPXDecoder::Codec::VP9;
   }
   return -1;
 }
 
-VPXDecoder::VPXDecoder(const VideoInfo& aConfig,
-                       ImageContainer* aImageContainer,
-                       TaskQueue* aTaskQueue,
-                       MediaDataDecoderCallback* aCallback)
-  : mImageContainer(aImageContainer)
-  , mTaskQueue(aTaskQueue)
-  , mCallback(aCallback)
+VPXDecoder::VPXDecoder(const CreateDecoderParams& aParams)
+  : mImageContainer(aParams.mImageContainer)
+  , mTaskQueue(aParams.mTaskQueue)
+  , mCallback(aParams.mCallback)
   , mIsFlushing(false)
-  , mInfo(aConfig)
-  , mCodec(MimeTypeToCodec(aConfig.mMimeType))
+  , mInfo(aParams.VideoConfig())
+  , mCodec(MimeTypeToCodec(aParams.VideoConfig().mMimeType))
 {
   MOZ_COUNT_CTOR(VPXDecoder);
   PodZero(&mVPX);
 }
 
 VPXDecoder::~VPXDecoder()
 {
   MOZ_COUNT_DTOR(VPXDecoder);
--- a/dom/media/platforms/agnostic/VPXDecoder.h
+++ b/dom/media/platforms/agnostic/VPXDecoder.h
@@ -16,21 +16,17 @@
 
 namespace mozilla {
 
 using namespace layers;
 
 class VPXDecoder : public MediaDataDecoder
 {
 public:
-  VPXDecoder(const VideoInfo& aConfig,
-             ImageContainer* aImageContainer,
-             TaskQueue* aTaskQueue,
-             MediaDataDecoderCallback* aCallback);
-
+  explicit VPXDecoder(const CreateDecoderParams& aParams);
   ~VPXDecoder();
 
   RefPtr<InitPromise> Init() override;
   nsresult Input(MediaRawData* aSample) override;
   nsresult Flush() override;
   nsresult Drain() override;
   nsresult Shutdown() override;
   const char* GetDescriptionName() const override
--- a/dom/media/platforms/agnostic/VorbisDecoder.cpp
+++ b/dom/media/platforms/agnostic/VorbisDecoder.cpp
@@ -25,22 +25,20 @@ ogg_packet InitVorbisPacket(const unsign
   packet.bytes = aLength;
   packet.b_o_s = aBOS;
   packet.e_o_s = aEOS;
   packet.granulepos = aGranulepos;
   packet.packetno = aPacketNo;
   return packet;
 }
 
-VorbisDataDecoder::VorbisDataDecoder(const AudioInfo& aConfig,
-                                     TaskQueue* aTaskQueue,
-                                     MediaDataDecoderCallback* aCallback)
-  : mInfo(aConfig)
-  , mTaskQueue(aTaskQueue)
-  , mCallback(aCallback)
+VorbisDataDecoder::VorbisDataDecoder(const CreateDecoderParams& aParams)
+  : mInfo(aParams.AudioConfig())
+  , mTaskQueue(aParams.mTaskQueue)
+  , mCallback(aParams.mCallback)
   , mPacketCount(0)
   , mFrames(0)
   , mIsFlushing(false)
 {
   // Zero these member vars to avoid crashes in Vorbis clear functions when
   // destructor is called before |Init|.
   PodZero(&mVorbisBlock);
   PodZero(&mVorbisDsp);
--- a/dom/media/platforms/agnostic/VorbisDecoder.h
+++ b/dom/media/platforms/agnostic/VorbisDecoder.h
@@ -16,19 +16,17 @@
 #include "vorbis/codec.h"
 #endif
 
 namespace mozilla {
 
 class VorbisDataDecoder : public MediaDataDecoder
 {
 public:
-  VorbisDataDecoder(const AudioInfo& aConfig,
-                TaskQueue* aTaskQueue,
-                MediaDataDecoderCallback* aCallback);
+  explicit VorbisDataDecoder(const CreateDecoderParams& aParams);
   ~VorbisDataDecoder();
 
   RefPtr<InitPromise> Init() override;
   nsresult Input(MediaRawData* aSample) override;
   nsresult Flush() override;
   nsresult Drain() override;
   nsresult Shutdown() override;
   const char* GetDescriptionName() const override
--- a/dom/media/platforms/agnostic/WAVDecoder.cpp
+++ b/dom/media/platforms/agnostic/WAVDecoder.cpp
@@ -40,20 +40,19 @@ DecodeULawSample(uint8_t aValue)
   aValue = aValue ^ 0xFF;
   int8_t sign = (aValue & 0x80) ? -1 : 1;
   uint8_t exponent = (aValue & 0x70) >> 4;
   uint8_t mantissa = aValue & 0x0F;
   int16_t sample = (33 + 2 * mantissa) * (2 << (exponent + 1)) - 33;
   return sign * sample;
 }
 
-WaveDataDecoder::WaveDataDecoder(const AudioInfo& aConfig,
-                                 MediaDataDecoderCallback* aCallback)
-  : mInfo(aConfig)
-  , mCallback(aCallback)
+WaveDataDecoder::WaveDataDecoder(const CreateDecoderParams& aParams)
+  : mInfo(aParams.AudioConfig())
+  , mCallback(aParams.mCallback)
 {
 }
 
 nsresult
 WaveDataDecoder::Shutdown()
 {
   return NS_OK;
 }
--- a/dom/media/platforms/agnostic/WAVDecoder.h
+++ b/dom/media/platforms/agnostic/WAVDecoder.h
@@ -10,18 +10,17 @@
 #include "PlatformDecoderModule.h"
 #include "mp4_demuxer/ByteReader.h"
 
 namespace mozilla {
 
 class WaveDataDecoder : public MediaDataDecoder
 {
 public:
-  WaveDataDecoder(const AudioInfo& aConfig,
-                  MediaDataDecoderCallback* aCallback);
+  explicit WaveDataDecoder(const CreateDecoderParams& aParams);
 
   // Return true if mimetype is Wave
   static bool IsWave(const nsACString& aMimeType);
 
   RefPtr<InitPromise> Init() override;
   nsresult Input(MediaRawData* aSample) override;
   nsresult Flush() override;
   nsresult Drain() override;
--- a/dom/media/platforms/agnostic/eme/EMEAudioDecoder.cpp
+++ b/dom/media/platforms/agnostic/eme/EMEAudioDecoder.cpp
@@ -16,16 +16,23 @@ EMEAudioCallbackAdapter::Error(GMPErr aE
     // The GMP failed to decrypt a frame due to not having a key. This can
     // happen if a key expires or a session is closed during playback.
     NS_WARNING("GMP failed to decrypt due to lack of key");
     return;
   }
   AudioCallbackAdapter::Error(aErr);
 }
 
+EMEAudioDecoder::EMEAudioDecoder(CDMProxy* aProxy,
+                                 const GMPAudioDecoderParams& aParams)
+  : GMPAudioDecoder(GMPAudioDecoderParams(aParams).WithAdapter(
+                    new EMEAudioCallbackAdapter(aParams.mCallback)))
+  , mProxy(aProxy)
+{}
+
 void
 EMEAudioDecoder::InitTags(nsTArray<nsCString>& aTags)
 {
   aTags.AppendElement(NS_LITERAL_CSTRING("aac"));
   aTags.AppendElement(NS_ConvertUTF16toUTF8(mProxy->KeySystem()));
 }
 
 nsCString
--- a/dom/media/platforms/agnostic/eme/EMEAudioDecoder.h
+++ b/dom/media/platforms/agnostic/eme/EMEAudioDecoder.h
@@ -18,24 +18,17 @@ public:
    : AudioCallbackAdapter(aCallback)
   {}
 
   void Error(GMPErr aErr) override;
 };
 
 class EMEAudioDecoder : public GMPAudioDecoder {
 public:
-  EMEAudioDecoder(CDMProxy* aProxy,
-                  const AudioInfo& aConfig,
-                  TaskQueue* aTaskQueue,
-                  MediaDataDecoderCallbackProxy* aCallback)
-   : GMPAudioDecoder(aConfig, aTaskQueue, aCallback, new EMEAudioCallbackAdapter(aCallback))
-   , mProxy(aProxy)
-  {
-  }
+  EMEAudioDecoder(CDMProxy* aProxy, const GMPAudioDecoderParams& aParams);
 
 private:
   void InitTags(nsTArray<nsCString>& aTags) override;
   nsCString GetNodeId() override;
 
   RefPtr<CDMProxy> mProxy;
 };
 
--- a/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
@@ -231,85 +231,66 @@ CreateDecoderWrapper(MediaDataDecoderCal
     return nullptr;
   }
   RefPtr<MediaDataDecoderProxy> decoder(
     new EMEMediaDataDecoderProxy(thread.forget(), aCallback, aProxy, aTaskQueue));
   return decoder.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
-EMEDecoderModule::CreateVideoDecoder(const VideoInfo& aConfig,
-                                     layers::LayersBackend aLayersBackend,
-                                     layers::ImageContainer* aImageContainer,
-                                     TaskQueue* aTaskQueue,
-                                     MediaDataDecoderCallback* aCallback,
-                                     DecoderDoctorDiagnostics* aDiagnostics)
+EMEDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
 {
-  MOZ_ASSERT(aConfig.mCrypto.mValid);
+  MOZ_ASSERT(aParams.mConfig.mCrypto.mValid);
 
-  if (SupportsMimeType(aConfig.mMimeType, nullptr)) {
+  if (SupportsMimeType(aParams.mConfig.mMimeType, nullptr)) {
     // GMP decodes. Assume that means it can decrypt too.
-    RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aCallback, mProxy, aTaskQueue);
-    wrapper->SetProxyTarget(new EMEVideoDecoder(mProxy,
-                                                aConfig,
-                                                aLayersBackend,
-                                                aImageContainer,
-                                                aTaskQueue,
-                                                wrapper->Callback()));
+    RefPtr<MediaDataDecoderProxy> wrapper =
+      CreateDecoderWrapper(aParams.mCallback, mProxy, aParams.mTaskQueue);
+    auto params = GMPVideoDecoderParams(aParams).WithCallback(wrapper);
+    wrapper->SetProxyTarget(new EMEVideoDecoder(mProxy, params));
     return wrapper.forget();
   }
 
   MOZ_ASSERT(mPDM);
-  RefPtr<MediaDataDecoder> decoder(
-    mPDM->CreateDecoder(aConfig,
-                        aTaskQueue,
-                        aCallback,
-                        aDiagnostics,
-                        aLayersBackend,
-                        aImageContainer));
+  RefPtr<MediaDataDecoder> decoder(mPDM->CreateDecoder(aParams));
   if (!decoder) {
     return nullptr;
   }
 
   RefPtr<MediaDataDecoder> emeDecoder(new EMEDecryptor(decoder,
-                                                         aCallback,
-                                                         mProxy,
-                                                         AbstractThread::GetCurrent()->AsTaskQueue()));
+                                                       aParams.mCallback,
+                                                       mProxy,
+                                                       AbstractThread::GetCurrent()->AsTaskQueue()));
   return emeDecoder.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
-EMEDecoderModule::CreateAudioDecoder(const AudioInfo& aConfig,
-                                     TaskQueue* aTaskQueue,
-                                     MediaDataDecoderCallback* aCallback,
-                                     DecoderDoctorDiagnostics* aDiagnostics)
+EMEDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams)
 {
-  MOZ_ASSERT(aConfig.mCrypto.mValid);
+  MOZ_ASSERT(aParams.mConfig.mCrypto.mValid);
 
-  if (SupportsMimeType(aConfig.mMimeType, nullptr)) {
+  if (SupportsMimeType(aParams.mConfig.mMimeType, nullptr)) {
     // GMP decodes. Assume that means it can decrypt too.
-    RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aCallback, mProxy, aTaskQueue);
-    wrapper->SetProxyTarget(new EMEAudioDecoder(mProxy,
-                                                aConfig,
-                                                aTaskQueue,
-                                                wrapper->Callback()));
+    RefPtr<MediaDataDecoderProxy> wrapper =
+      CreateDecoderWrapper(aParams.mCallback, mProxy, aParams.mTaskQueue);
+    auto gmpParams = GMPAudioDecoderParams(aParams).WithCallback(wrapper);
+    wrapper->SetProxyTarget(new EMEAudioDecoder(mProxy, gmpParams));
     return wrapper.forget();
   }
 
   MOZ_ASSERT(mPDM);
-  RefPtr<MediaDataDecoder> decoder(
-    mPDM->CreateDecoder(aConfig, aTaskQueue, aCallback, aDiagnostics));
+  RefPtr<MediaDataDecoder> decoder(mPDM->CreateDecoder(aParams));
   if (!decoder) {
     return nullptr;
   }
 
   RefPtr<MediaDataDecoder> emeDecoder(new EMEDecryptor(decoder,
-                                                         aCallback,
-                                                         mProxy,
-                                                         AbstractThread::GetCurrent()->AsTaskQueue()));
+                                                       aParams.mCallback,
+                                                       mProxy,
+                                                       AbstractThread::GetCurrent()->AsTaskQueue()));
   return emeDecoder.forget();
 }
 
 PlatformDecoderModule::ConversionRequired
 EMEDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const
 {
   if (aConfig.IsVideo()) {
     return kNeedAVCC;
--- a/dom/media/platforms/agnostic/eme/EMEDecoderModule.h
+++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.h
@@ -21,29 +21,21 @@ private:
 public:
   EMEDecoderModule(CDMProxy* aProxy, PDMFactory* aPDM);
 
   virtual ~EMEDecoderModule();
 
 protected:
   // Decode thread.
   already_AddRefed<MediaDataDecoder>
-  CreateVideoDecoder(const VideoInfo& aConfig,
-                    layers::LayersBackend aLayersBackend,
-                    layers::ImageContainer* aImageContainer,
-                    TaskQueue* aTaskQueue,
-                    MediaDataDecoderCallback* aCallback,
-                    DecoderDoctorDiagnostics* aDiagnostics) override;
+  CreateVideoDecoder(const CreateDecoderParams& aParams) override;
 
   // Decode thread.
   already_AddRefed<MediaDataDecoder>
-  CreateAudioDecoder(const AudioInfo& aConfig,
-                     TaskQueue* aTaskQueue,
-                     MediaDataDecoderCallback* aCallback,
-                     DecoderDoctorDiagnostics* aDiagnostics) override;
+  CreateAudioDecoder(const CreateDecoderParams& aParams) override;
 
   ConversionRequired
   DecoderNeedsConversion(const TrackInfo& aConfig) const override;
 
   bool
   SupportsMimeType(const nsACString& aMimeType,
                    DecoderDoctorDiagnostics* aDiagnostics) const override;
 
--- a/dom/media/platforms/agnostic/eme/EMEVideoDecoder.cpp
+++ b/dom/media/platforms/agnostic/eme/EMEVideoDecoder.cpp
@@ -18,16 +18,25 @@ EMEVideoCallbackAdapter::Error(GMPErr aE
     // The GMP failed to decrypt a frame due to not having a key. This can
     // happen if a key expires or a session is closed during playback.
     NS_WARNING("GMP failed to decrypt due to lack of key");
     return;
   }
   VideoCallbackAdapter::Error(aErr);
 }
 
+EMEVideoDecoder::EMEVideoDecoder(CDMProxy* aProxy,
+                                 const GMPVideoDecoderParams& aParams)
+  : GMPVideoDecoder(GMPVideoDecoderParams(aParams).WithAdapter(
+                    new EMEVideoCallbackAdapter(aParams.mCallback,
+                                                VideoInfo(aParams.mConfig.mDisplay),
+                                                aParams.mImageContainer)))
+  , mProxy(aProxy)
+{}
+
 void
 EMEVideoDecoder::InitTags(nsTArray<nsCString>& aTags)
 {
   aTags.AppendElement(NS_LITERAL_CSTRING("h264"));
   aTags.AppendElement(NS_ConvertUTF16toUTF8(mProxy->KeySystem()));
 }
 
 nsCString
--- a/dom/media/platforms/agnostic/eme/EMEVideoDecoder.h
+++ b/dom/media/platforms/agnostic/eme/EMEVideoDecoder.h
@@ -23,34 +23,17 @@ public:
    : VideoCallbackAdapter(aCallback, aVideoInfo, aImageContainer)
   {}
 
   void Error(GMPErr aErr) override;
 };
 
 class EMEVideoDecoder : public GMPVideoDecoder {
 public:
-  EMEVideoDecoder(CDMProxy* aProxy,
-                  const VideoInfo& aConfig,
-                  layers::LayersBackend aLayersBackend,
-                  layers::ImageContainer* aImageContainer,
-                  TaskQueue* aTaskQueue,
-                  MediaDataDecoderCallbackProxy* aCallback)
-   : GMPVideoDecoder(aConfig,
-                     aLayersBackend,
-                     aImageContainer,
-                     aTaskQueue,
-                     aCallback,
-                     new EMEVideoCallbackAdapter(aCallback,
-                                                 VideoInfo(aConfig.mDisplay.width,
-                                                           aConfig.mDisplay.height),
-                                                 aImageContainer))
-   , mProxy(aProxy)
-  {
-  }
+  EMEVideoDecoder(CDMProxy* aProxy, const GMPVideoDecoderParams& aParams);
 
 private:
   void InitTags(nsTArray<nsCString>& aTags) override;
   nsCString GetNodeId() override;
   GMPUniquePtr<GMPVideoEncodedFrame> CreateFrame(MediaRawData* aSample) override;
 
   RefPtr<CDMProxy> mProxy;
 };
--- a/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp
+++ b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.cpp
@@ -121,16 +121,55 @@ AudioCallbackAdapter::Error(GMPErr aErr)
 
 void
 AudioCallbackAdapter::Terminated()
 {
   NS_WARNING("AAC GMP decoder terminated.");
   mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
 }
 
+GMPAudioDecoderParams::GMPAudioDecoderParams(const CreateDecoderParams& aParams)
+  : mConfig(aParams.AudioConfig())
+  , mTaskQueue(aParams.mTaskQueue)
+  , mCallback(nullptr)
+  , mAdapter(nullptr)
+{}
+
+GMPAudioDecoderParams&
+GMPAudioDecoderParams::WithCallback(MediaDataDecoderProxy* aWrapper)
+{
+  MOZ_ASSERT(aWrapper);
+  MOZ_ASSERT(!mCallback); // Should only be called once per instance.
+  mCallback = aWrapper->Callback();
+  mAdapter = nullptr;
+  return *this;
+}
+
+GMPAudioDecoderParams&
+GMPAudioDecoderParams::WithAdapter(AudioCallbackAdapter* aAdapter)
+{
+  MOZ_ASSERT(aAdapter);
+  MOZ_ASSERT(!mAdapter); // Should only be called once per instance.
+  mCallback = aAdapter->Callback();
+  mAdapter = aAdapter;
+  return *this;
+}
+
+GMPAudioDecoder::GMPAudioDecoder(const GMPAudioDecoderParams& aParams)
+  : mConfig(aParams.mConfig)
+  , mCallback(aParams.mCallback)
+  , mGMP(nullptr)
+  , mAdapter(aParams.mAdapter)
+{
+  MOZ_ASSERT(!mAdapter || mCallback == mAdapter->Callback());
+  if (!mAdapter) {
+    mAdapter = new AudioCallbackAdapter(mCallback);
+  }
+}
+
 void
 GMPAudioDecoder::InitTags(nsTArray<nsCString>& aTags)
 {
   aTags.AppendElement(NS_LITERAL_CSTRING("aac"));
   const Maybe<nsCString> gmp(
     GMPDecoderModule::PreferredGMP(NS_LITERAL_CSTRING("audio/mp4a-latm")));
   if (gmp.isSome()) {
     aTags.AppendElement(gmp.value());
--- a/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.h
+++ b/dom/media/platforms/agnostic/gmp/GMPAudioDecoder.h
@@ -20,16 +20,18 @@ public:
   explicit AudioCallbackAdapter(MediaDataDecoderCallbackProxy* aCallback)
    : mCallback(aCallback)
    , mLastStreamOffset(0)
    , mAudioFrameSum(0)
    , mAudioFrameOffset(0)
    , mMustRecaptureAudioPosition(true)
   {}
 
+  MediaDataDecoderCallbackProxy* Callback() const { return mCallback; }
+
   // GMPAudioDecoderCallbackProxy
   void Decoded(const nsTArray<int16_t>& aPCM, uint64_t aTimeStamp, uint32_t aChannels, uint32_t aRate) override;
   void InputDataExhausted() override;
   void DrainComplete() override;
   void ResetComplete() override;
   void Error(GMPErr aErr) override;
   void Terminated() override;
 
@@ -41,39 +43,30 @@ private:
   MediaDataDecoderCallbackProxy* mCallback;
   int64_t mLastStreamOffset;
 
   int64_t mAudioFrameSum;
   int64_t mAudioFrameOffset;
   bool mMustRecaptureAudioPosition;
 };
 
+struct GMPAudioDecoderParams {
+  explicit GMPAudioDecoderParams(const CreateDecoderParams& aParams);
+  GMPAudioDecoderParams& WithCallback(MediaDataDecoderProxy* aWrapper);
+  GMPAudioDecoderParams& WithAdapter(AudioCallbackAdapter* aAdapter);
+
+  const AudioInfo& mConfig;
+  TaskQueue* mTaskQueue;
+  MediaDataDecoderCallbackProxy* mCallback;
+  AudioCallbackAdapter* mAdapter;
+};
+
 class GMPAudioDecoder : public MediaDataDecoder {
-protected:
-  GMPAudioDecoder(const AudioInfo& aConfig,
-                  TaskQueue* aTaskQueue,
-                  MediaDataDecoderCallbackProxy* aCallback,
-                  AudioCallbackAdapter* aAdapter)
-   : mConfig(aConfig)
-   , mCallback(aCallback)
-   , mGMP(nullptr)
-   , mAdapter(aAdapter)
-  {
-  }
-
 public:
-  GMPAudioDecoder(const AudioInfo& aConfig,
-                  TaskQueue* aTaskQueue,
-                  MediaDataDecoderCallbackProxy* aCallback)
-   : mConfig(aConfig)
-   , mCallback(aCallback)
-   , mGMP(nullptr)
-   , mAdapter(new AudioCallbackAdapter(aCallback))
-  {
-  }
+  explicit GMPAudioDecoder(const GMPAudioDecoderParams& aParams);
 
   RefPtr<InitPromise> Init() override;
   nsresult Input(MediaRawData* aSample) override;
   nsresult Flush() override;
   nsresult Drain() override;
   nsresult Shutdown() override;
   const char* GetDescriptionName() const override
   {
--- a/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.cpp
@@ -41,64 +41,52 @@ CreateDecoderWrapper(MediaDataDecoderCal
   if (!thread) {
     return nullptr;
   }
   RefPtr<MediaDataDecoderProxy> decoder(new MediaDataDecoderProxy(thread.forget(), aCallback));
   return decoder.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
-GMPDecoderModule::CreateVideoDecoder(const VideoInfo& aConfig,
-                                     layers::LayersBackend aLayersBackend,
-                                     layers::ImageContainer* aImageContainer,
-                                     TaskQueue* aTaskQueue,
-                                     MediaDataDecoderCallback* aCallback,
-                                     DecoderDoctorDiagnostics* aDiagnostics)
+GMPDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
 {
-  if (!aConfig.mMimeType.EqualsLiteral("video/avc")) {
+  if (!aParams.mConfig.mMimeType.EqualsLiteral("video/avc")) {
     return nullptr;
   }
 
-  if (aDiagnostics) {
-    const Maybe<nsCString> preferredGMP = PreferredGMP(aConfig.mMimeType);
+  if (aParams.mDiagnostics) {
+    const Maybe<nsCString> preferredGMP = PreferredGMP(aParams.mConfig.mMimeType);
     if (preferredGMP.isSome()) {
-      aDiagnostics->SetGMP(preferredGMP.value());
+      aParams.mDiagnostics->SetGMP(preferredGMP.value());
     }
   }
 
-  RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aCallback);
-  wrapper->SetProxyTarget(new GMPVideoDecoder(aConfig,
-                                              aLayersBackend,
-                                              aImageContainer,
-                                              aTaskQueue,
-                                              wrapper->Callback()));
+  RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aParams.mCallback);
+  auto params = GMPVideoDecoderParams(aParams).WithCallback(wrapper);
+  wrapper->SetProxyTarget(new GMPVideoDecoder(params));
   return wrapper.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
-GMPDecoderModule::CreateAudioDecoder(const AudioInfo& aConfig,
-                                     TaskQueue* aTaskQueue,
-                                     MediaDataDecoderCallback* aCallback,
-                                     DecoderDoctorDiagnostics* aDiagnostics)
+GMPDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams)
 {
-  if (!aConfig.mMimeType.EqualsLiteral("audio/mp4a-latm")) {
+  if (!aParams.mConfig.mMimeType.EqualsLiteral("audio/mp4a-latm")) {
     return nullptr;
   }
 
-  if (aDiagnostics) {
-    const Maybe<nsCString> preferredGMP = PreferredGMP(aConfig.mMimeType);
+  if (aParams.mDiagnostics) {
+    const Maybe<nsCString> preferredGMP = PreferredGMP(aParams.mConfig.mMimeType);
     if (preferredGMP.isSome()) {
-      aDiagnostics->SetGMP(preferredGMP.value());
+      aParams.mDiagnostics->SetGMP(preferredGMP.value());
     }
   }
 
-  RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aCallback);
-  wrapper->SetProxyTarget(new GMPAudioDecoder(aConfig,
-                                              aTaskQueue,
-                                              wrapper->Callback()));
+  RefPtr<MediaDataDecoderProxy> wrapper = CreateDecoderWrapper(aParams.mCallback);
+  auto params = GMPAudioDecoderParams(aParams).WithCallback(wrapper);
+  wrapper->SetProxyTarget(new GMPAudioDecoder(params));
   return wrapper.forget();
 }
 
 PlatformDecoderModule::ConversionRequired
 GMPDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const
 {
   // GMPVideoCodecType::kGMPVideoCodecH264 specifies that encoded frames must be in AVCC format.
   if (aConfig.IsVideo()) {
--- a/dom/media/platforms/agnostic/gmp/GMPDecoderModule.h
+++ b/dom/media/platforms/agnostic/gmp/GMPDecoderModule.h
@@ -28,29 +28,21 @@ namespace mozilla {
 class GMPDecoderModule : public PlatformDecoderModule {
 public:
   GMPDecoderModule();
 
   virtual ~GMPDecoderModule();
 
   // Decode thread.
   already_AddRefed<MediaDataDecoder>
-  CreateVideoDecoder(const VideoInfo& aConfig,
-                     layers::LayersBackend aLayersBackend,
-                     layers::ImageContainer* aImageContainer,
-                     TaskQueue* aTaskQueue,
-                     MediaDataDecoderCallback* aCallback,
-                     DecoderDoctorDiagnostics* aDiagnostics) override;
+  CreateVideoDecoder(const CreateDecoderParams& aParams) override;
 
   // Decode thread.
   already_AddRefed<MediaDataDecoder>
-  CreateAudioDecoder(const AudioInfo& aConfig,
-                     TaskQueue* aTaskQueue,
-                     MediaDataDecoderCallback* aCallback,
-                     DecoderDoctorDiagnostics* aDiagnostics) override;
+  CreateAudioDecoder(const CreateDecoderParams& aParams) override;
 
   ConversionRequired
   DecoderNeedsConversion(const TrackInfo& aConfig) const override;
 
   bool
   SupportsMimeType(const nsACString& aMimeType,
                    DecoderDoctorDiagnostics* aDiagnostics) const override;
 
--- a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp
+++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.cpp
@@ -99,16 +99,62 @@ VideoCallbackAdapter::Error(GMPErr aErr)
 void
 VideoCallbackAdapter::Terminated()
 {
   // Note that this *may* be called from the proxy thread also.
   NS_WARNING("H.264 GMP decoder terminated.");
   mCallback->Error(MediaDataDecoderError::FATAL_ERROR);
 }
 
+GMPVideoDecoderParams::GMPVideoDecoderParams(const CreateDecoderParams& aParams)
+  : mConfig(aParams.VideoConfig())
+  , mTaskQueue(aParams.mTaskQueue)
+  , mCallback(nullptr)
+  , mAdapter(nullptr)
+  , mImageContainer(aParams.mImageContainer)
+  , mLayersBackend(aParams.mLayersBackend)
+{}
+
+GMPVideoDecoderParams&
+GMPVideoDecoderParams::WithCallback(MediaDataDecoderProxy* aWrapper)
+{
+  MOZ_ASSERT(aWrapper);
+  MOZ_ASSERT(!mCallback); // Should only be called once per instance.
+  mCallback = aWrapper->Callback();
+  mAdapter = nullptr;
+  return *this;
+}
+
+GMPVideoDecoderParams&
+GMPVideoDecoderParams::WithAdapter(VideoCallbackAdapter* aAdapter)
+{
+  MOZ_ASSERT(aAdapter);
+  MOZ_ASSERT(!mAdapter); // Should only be called once per instance.
+  mCallback = aAdapter->Callback();
+  mAdapter = aAdapter;
+  return *this;
+}
+
+GMPVideoDecoder::GMPVideoDecoder(const GMPVideoDecoderParams& aParams)
+  : mConfig(aParams.mConfig)
+  , mCallback(aParams.mCallback)
+  , mGMP(nullptr)
+  , mHost(nullptr)
+  , mAdapter(aParams.mAdapter)
+  , mConvertNALUnitLengths(false)
+{
+  MOZ_ASSERT(!mAdapter || mCallback == mAdapter->Callback());
+  if (!mAdapter) {
+    mAdapter = new VideoCallbackAdapter(mCallback,
+                                        VideoInfo(mConfig.mDisplay.width,
+                                                  mConfig.mDisplay.height),
+                                        aParams.mImageContainer);
+  }
+}
+
 void
 GMPVideoDecoder::InitTags(nsTArray<nsCString>& aTags)
 {
   aTags.AppendElement(NS_LITERAL_CSTRING("h264"));
   const Maybe<nsCString> gmp(
     GMPDecoderModule::PreferredGMP(NS_LITERAL_CSTRING("video/avc")));
   if (gmp.isSome()) {
     aTags.AppendElement(gmp.value());
--- a/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h
+++ b/dom/media/platforms/agnostic/gmp/GMPVideoDecoder.h
@@ -22,16 +22,18 @@ public:
                        VideoInfo aVideoInfo,
                        layers::ImageContainer* aImageContainer)
    : mCallback(aCallback)
    , mLastStreamOffset(0)
    , mVideoInfo(aVideoInfo)
    , mImageContainer(aImageContainer)
   {}
 
+  MediaDataDecoderCallbackProxy* Callback() const { return mCallback; }
+
   // GMPVideoDecoderCallbackProxy
   void Decoded(GMPVideoi420Frame* aDecodedFrame) override;
   void ReceivedDecodedReferenceFrame(const uint64_t aPictureId) override;
   void ReceivedDecodedFrame(const uint64_t aPictureId) override;
   void InputDataExhausted() override;
   void DrainComplete() override;
   void ResetComplete() override;
   void Error(GMPErr aErr) override;
@@ -44,50 +46,32 @@ public:
 private:
   MediaDataDecoderCallbackProxy* mCallback;
   int64_t mLastStreamOffset;
 
   VideoInfo mVideoInfo;
   RefPtr<layers::ImageContainer> mImageContainer;
 };
 
-class GMPVideoDecoder : public MediaDataDecoder {
-protected:
-  GMPVideoDecoder(const VideoInfo& aConfig,
-                  layers::LayersBackend aLayersBackend,
-                  layers::ImageContainer* aImageContainer,
-                  TaskQueue* aTaskQueue,
-                  MediaDataDecoderCallbackProxy* aCallback,
-                  VideoCallbackAdapter* aAdapter)
-   : mConfig(aConfig)
-   , mCallback(aCallback)
-   , mGMP(nullptr)
-   , mHost(nullptr)
-   , mAdapter(aAdapter)
-   , mConvertNALUnitLengths(false)
-  {
-  }
+struct GMPVideoDecoderParams {
+  explicit GMPVideoDecoderParams(const CreateDecoderParams& aParams);
+  GMPVideoDecoderParams& WithCallback(MediaDataDecoderProxy* aWrapper);
+  GMPVideoDecoderParams& WithAdapter(VideoCallbackAdapter* aAdapter);
 
+  const VideoInfo& mConfig;
+  TaskQueue* mTaskQueue;
+  MediaDataDecoderCallbackProxy* mCallback;
+  VideoCallbackAdapter* mAdapter;
+  layers::ImageContainer* mImageContainer;
+  layers::LayersBackend mLayersBackend;
+};
+
+class GMPVideoDecoder : public MediaDataDecoder {
 public:
-  GMPVideoDecoder(const VideoInfo& aConfig,
-                  layers::LayersBackend aLayersBackend,
-                  layers::ImageContainer* aImageContainer,
-                  TaskQueue* aTaskQueue,
-                  MediaDataDecoderCallbackProxy* aCallback)
-   : mConfig(aConfig)
-   , mCallback(aCallback)
-   , mGMP(nullptr)
-   , mHost(nullptr)
-   , mAdapter(new VideoCallbackAdapter(aCallback,
-                                       VideoInfo(aConfig.mDisplay.width,
-                                                 aConfig.mDisplay.height),
-                                       aImageContainer))
-   , mConvertNALUnitLengths(false)
-  {
-  }
+  explicit GMPVideoDecoder(const GMPVideoDecoderParams& aParams);
 
   RefPtr<InitPromise> Init() override;
   nsresult Input(MediaRawData* aSample) override;
   nsresult Flush() override;
   nsresult Drain() override;
   nsresult Shutdown() override;
   const char* GetDescriptionName() const override
   {
--- a/dom/media/platforms/android/AndroidDecoderModule.cpp
+++ b/dom/media/platforms/android/AndroidDecoderModule.cpp
@@ -277,57 +277,55 @@ AndroidDecoderModule::SupportsMimeType(c
     return false;
   }
 
   return widget::HardwareCodecCapabilityUtils::FindDecoderCodecInfoForMimeType(
       nsCString(TranslateMimeType(aMimeType)));
 }
 
 already_AddRefed<MediaDataDecoder>
-AndroidDecoderModule::CreateVideoDecoder(
-    const VideoInfo& aConfig, layers::LayersBackend aLayersBackend,
-    layers::ImageContainer* aImageContainer, TaskQueue* aTaskQueue,
-    MediaDataDecoderCallback* aCallback,
-    DecoderDoctorDiagnostics* aDiagnostics)
+AndroidDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
 {
   MediaFormat::LocalRef format;
 
+  const VideoInfo& config = aParams.VideoConfig();
   NS_ENSURE_SUCCESS(MediaFormat::CreateVideoFormat(
-      TranslateMimeType(aConfig.mMimeType),
-      aConfig.mDisplay.width,
-      aConfig.mDisplay.height,
+      TranslateMimeType(config.mMimeType),
+      config.mDisplay.width,
+      config.mDisplay.height,
       &format), nullptr);
 
   RefPtr<MediaDataDecoder> decoder =
-    new VideoDataDecoder(aConfig, format, aCallback, aImageContainer);
+    new VideoDataDecoder(config,
+                         format,
+                         aParams.mCallback,
+                         aParams.mImageContainer);
 
   return decoder.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
-AndroidDecoderModule::CreateAudioDecoder(
-    const AudioInfo& aConfig, TaskQueue* aTaskQueue,
-    MediaDataDecoderCallback* aCallback,
-    DecoderDoctorDiagnostics* aDiagnostics)
+AndroidDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams)
 {
-  MOZ_ASSERT(aConfig.mBitDepth == 16, "We only handle 16-bit audio!");
+  const AudioInfo& config = aParams.AudioConfig();
+  MOZ_ASSERT(config.mBitDepth == 16, "We only handle 16-bit audio!");
 
   MediaFormat::LocalRef format;
 
   LOG("CreateAudioFormat with mimeType=%s, mRate=%d, channels=%d",
-      aConfig.mMimeType.Data(), aConfig.mRate, aConfig.mChannels);
+      config.mMimeType.Data(), config.mRate, config.mChannels);
 
   NS_ENSURE_SUCCESS(MediaFormat::CreateAudioFormat(
-      aConfig.mMimeType,
-      aConfig.mRate,
-      aConfig.mChannels,
+      config.mMimeType,
+      config.mRate,
+      config.mChannels,
       &format), nullptr);
 
   RefPtr<MediaDataDecoder> decoder =
-    new AudioDataDecoder(aConfig, format, aCallback);
+    new AudioDataDecoder(config, format, aParams.mCallback);
 
   return decoder.forget();
 }
 
 PlatformDecoderModule::ConversionRequired
 AndroidDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const
 {
   if (aConfig.IsVideo()) {
--- a/dom/media/platforms/android/AndroidDecoderModule.h
+++ b/dom/media/platforms/android/AndroidDecoderModule.h
@@ -17,28 +17,20 @@
 
 namespace mozilla {
 
 typedef std::deque<RefPtr<MediaRawData>> SampleQueue;
 
 class AndroidDecoderModule : public PlatformDecoderModule {
 public:
   already_AddRefed<MediaDataDecoder>
-  CreateVideoDecoder(const VideoInfo& aConfig,
-                     layers::LayersBackend aLayersBackend,
-                     layers::ImageContainer* aImageContainer,
-                     TaskQueue* aTaskQueue,
-                     MediaDataDecoderCallback* aCallback,
-                     DecoderDoctorDiagnostics* aDiagnostics) override;
+  CreateVideoDecoder(const CreateDecoderParams& aParams) override;
 
   already_AddRefed<MediaDataDecoder>
-  CreateAudioDecoder(const AudioInfo& aConfig,
-                     TaskQueue* aTaskQueue,
-                     MediaDataDecoderCallback* aCallback,
-                     DecoderDoctorDiagnostics* aDiagnostics) override;
+  CreateAudioDecoder(const CreateDecoderParams& aParams) override;
 
 
   AndroidDecoderModule() {}
   virtual ~AndroidDecoderModule() {}
 
   bool SupportsMimeType(const nsACString& aMimeType,
                         DecoderDoctorDiagnostics* aDiagnostics) const override;
 
--- a/dom/media/platforms/apple/AppleDecoderModule.cpp
+++ b/dom/media/platforms/apple/AppleDecoderModule.cpp
@@ -69,52 +69,49 @@ AppleDecoderModule::Startup()
 {
   if (!sInitialized || (!sIsVDAAvailable && !sIsVTAvailable)) {
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 already_AddRefed<MediaDataDecoder>
-AppleDecoderModule::CreateVideoDecoder(const VideoInfo& aConfig,
-                                       layers::LayersBackend aLayersBackend,
-                                       layers::ImageContainer* aImageContainer,
-                                       TaskQueue* aTaskQueue,
-                                       MediaDataDecoderCallback* aCallback,
-                                       DecoderDoctorDiagnostics* aDiagnostics)
+AppleDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
 {
   RefPtr<MediaDataDecoder> decoder;
 
   if (sIsVDAAvailable && (!sIsVTHWAvailable || MediaPrefs::AppleForceVDA())) {
     decoder =
-      AppleVDADecoder::CreateVDADecoder(aConfig,
-                                        aTaskQueue,
-                                        aCallback,
-                                        aImageContainer);
+      AppleVDADecoder::CreateVDADecoder(aParams.VideoConfig(),
+                                        aParams.mTaskQueue,
+                                        aParams.mCallback,
+                                        aParams.mImageContainer);
     if (decoder) {
       return decoder.forget();
     }
   }
   // We fallback here if VDA isn't available, or is available but isn't
   // supported by the current platform.
   if (sIsVTAvailable) {
     decoder =
-      new AppleVTDecoder(aConfig, aTaskQueue, aCallback, aImageContainer);
+      new AppleVTDecoder(aParams.VideoConfig(),
+                         aParams.mTaskQueue,
+                         aParams.mCallback,
+                         aParams.mImageContainer);
   }
   return decoder.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
-AppleDecoderModule::CreateAudioDecoder(const AudioInfo& aConfig,
-                                       TaskQueue* aTaskQueue,
-                                       MediaDataDecoderCallback* aCallback,
-                                       DecoderDoctorDiagnostics* aDiagnostics)
+AppleDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams)
 {
   RefPtr<MediaDataDecoder> decoder =
-    new AppleATDecoder(aConfig, aTaskQueue, aCallback);
+    new AppleATDecoder(aParams.AudioConfig(),
+                       aParams.mTaskQueue,
+                       aParams.mCallback);
   return decoder.forget();
 }
 
 bool
 AppleDecoderModule::SupportsMimeType(const nsACString& aMimeType,
                                      DecoderDoctorDiagnostics* aDiagnostics) const
 {
   return (sIsCoreMediaAvailable &&
--- a/dom/media/platforms/apple/AppleDecoderModule.h
+++ b/dom/media/platforms/apple/AppleDecoderModule.h
@@ -15,29 +15,21 @@ class AppleDecoderModule : public Platfo
 public:
   AppleDecoderModule();
   virtual ~AppleDecoderModule();
 
   nsresult Startup() override;
 
   // Decode thread.
   already_AddRefed<MediaDataDecoder>
-  CreateVideoDecoder(const VideoInfo& aConfig,
-                     layers::LayersBackend aLayersBackend,
-                     layers::ImageContainer* aImageContainer,
-                     TaskQueue* aTaskQueue,
-                     MediaDataDecoderCallback* aCallback,
-                     DecoderDoctorDiagnostics* aDiagnostics) override;
+  CreateVideoDecoder(const CreateDecoderParams& aParams) override;
 
   // Decode thread.
   already_AddRefed<MediaDataDecoder>
-  CreateAudioDecoder(const AudioInfo& aConfig,
-                     TaskQueue* aTaskQueue,
-                     MediaDataDecoderCallback* aCallback,
-                     DecoderDoctorDiagnostics* aDiagnostics) override;
+  CreateAudioDecoder(const CreateDecoderParams& aParams) override;
 
   bool SupportsMimeType(const nsACString& aMimeType,
                         DecoderDoctorDiagnostics* aDiagnostics) const override;
 
   ConversionRequired
   DecoderNeedsConversion(const TrackInfo& aConfig) const override;
 
   static void Init();
--- a/dom/media/platforms/ffmpeg/FFmpegDecoderModule.h
+++ b/dom/media/platforms/ffmpeg/FFmpegDecoderModule.h
@@ -26,40 +26,38 @@ public:
 
     return pdm.forget();
   }
 
   explicit FFmpegDecoderModule(FFmpegLibWrapper* aLib) : mLib(aLib) {}
   virtual ~FFmpegDecoderModule() {}
 
   already_AddRefed<MediaDataDecoder>
-  CreateVideoDecoder(const VideoInfo& aConfig,
-                     layers::LayersBackend aLayersBackend,
-                     layers::ImageContainer* aImageContainer,
-                     TaskQueue* aTaskQueue,
-                     MediaDataDecoderCallback* aCallback,
-                     DecoderDoctorDiagnostics* aDiagnostics) override
+  CreateVideoDecoder(const CreateDecoderParams& aParams) override
   {
     RefPtr<MediaDataDecoder> decoder =
-      new FFmpegVideoDecoder<V>(mLib, aTaskQueue, aCallback, aConfig,
-                                aImageContainer);
+      new FFmpegVideoDecoder<V>(mLib,
+                                aParams.mTaskQueue,
+                                aParams.mCallback,
+                                aParams.VideoConfig(),
+                                aParams.mImageContainer);
     return decoder.forget();
   }
 
   already_AddRefed<MediaDataDecoder>
-  CreateAudioDecoder(const AudioInfo& aConfig,
-                     TaskQueue* aTaskQueue,
-                     MediaDataDecoderCallback* aCallback,
-                     DecoderDoctorDiagnostics* aDiagnostics) override
+  CreateAudioDecoder(const CreateDecoderParams& aParams) override
   {
 #ifdef USING_MOZFFVPX
     return nullptr;
 #else
     RefPtr<MediaDataDecoder> decoder =
-      new FFmpegAudioDecoder<V>(mLib, aTaskQueue, aCallback, aConfig);
+      new FFmpegAudioDecoder<V>(mLib,
+                                aParams.mTaskQueue,
+                                aParams.mCallback,
+                                aParams.AudioConfig());
     return decoder.forget();
 #endif
   }
 
   bool SupportsMimeType(const nsACString& aMimeType,
                         DecoderDoctorDiagnostics* aDiagnostics) const override
   {
     AVCodecID videoCodec = FFmpegVideoDecoder<V>::GetCodecId(aMimeType);
--- a/dom/media/platforms/gonk/GonkDecoderModule.cpp
+++ b/dom/media/platforms/gonk/GonkDecoderModule.cpp
@@ -14,34 +14,26 @@ GonkDecoderModule::GonkDecoderModule()
 {
 }
 
 GonkDecoderModule::~GonkDecoderModule()
 {
 }
 
 already_AddRefed<MediaDataDecoder>
-GonkDecoderModule::CreateVideoDecoder(const VideoInfo& aConfig,
-                                     mozilla::layers::LayersBackend aLayersBackend,
-                                     mozilla::layers::ImageContainer* aImageContainer,
-                                     TaskQueue* aTaskQueue,
-                                     MediaDataDecoderCallback* aCallback,
-                                     DecoderDoctorDiagnostics* aDiagnostics)
+GonkDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
 {
   RefPtr<MediaDataDecoder> decoder =
   new GonkMediaDataDecoder(new GonkVideoDecoderManager(aImageContainer, aConfig),
                            aCallback);
   return decoder.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
-GonkDecoderModule::CreateAudioDecoder(const AudioInfo& aConfig,
-                                      TaskQueue* aTaskQueue,
-                                      MediaDataDecoderCallback* aCallback,
-                                      DecoderDoctorDiagnostics* aDiagnostics)
+GonkDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams)
 {
   RefPtr<MediaDataDecoder> decoder =
   new GonkMediaDataDecoder(new GonkAudioDecoderManager(aConfig),
                            aCallback);
   return decoder.forget();
 }
 
 PlatformDecoderModule::ConversionRequired
--- a/dom/media/platforms/gonk/GonkDecoderModule.h
+++ b/dom/media/platforms/gonk/GonkDecoderModule.h
@@ -13,29 +13,21 @@ namespace mozilla {
 
 class GonkDecoderModule : public PlatformDecoderModule {
 public:
   GonkDecoderModule();
   virtual ~GonkDecoderModule();
 
   // Decode thread.
   already_AddRefed<MediaDataDecoder>
-  CreateVideoDecoder(const VideoInfo& aConfig,
-                     mozilla::layers::LayersBackend aLayersBackend,
-                     mozilla::layers::ImageContainer* aImageContainer,
-                     TaskQueue* aTaskQueue,
-                     MediaDataDecoderCallback* aCallback,
-                     DecoderDoctorDiagnostics* aDiagnostics) override;
+  CreateVideoDecoder(const CreateDecoderParams& aParams) override;
 
   // Decode thread.
   already_AddRefed<MediaDataDecoder>
-  CreateAudioDecoder(const AudioInfo& aConfig,
-                     TaskQueue* aTaskQueue,
-                     MediaDataDecoderCallback* aCallback,
-                     DecoderDoctorDiagnostics* aDiagnostics) override;
+  CreateAudioDecoder(const CreateDecoderParams& aParams) override;
 
   ConversionRequired
   DecoderNeedsConversion(const TrackInfo& aConfig) const override;
 
   bool SupportsMimeType(const nsACString& aMimeType,
                         DecoderDoctorDiagnostics* aDiagnostics) const override;
 
 };
--- a/dom/media/platforms/omx/OmxDecoderModule.cpp
+++ b/dom/media/platforms/omx/OmxDecoderModule.cpp
@@ -7,34 +7,30 @@
 #include "OmxDecoderModule.h"
 
 #include "OmxDataDecoder.h"
 #include "OmxPlatformLayer.h"
 
 namespace mozilla {
 
 already_AddRefed<MediaDataDecoder>
-OmxDecoderModule::CreateVideoDecoder(const VideoInfo& aConfig,
-                                     mozilla::layers::LayersBackend aLayersBackend,
-                                     mozilla::layers::ImageContainer* aImageContainer,
-                                     TaskQueue* aTaskQueue,
-                                     MediaDataDecoderCallback* aCallback,
-                                     DecoderDoctorDiagnostics* aDiagnostics)
+OmxDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
 {
-  RefPtr<OmxDataDecoder> decoder = new OmxDataDecoder(aConfig, aCallback, aImageContainer);
+  RefPtr<OmxDataDecoder> decoder = new OmxDataDecoder(aParams.mConfig,
+                                                      aParams.mCallback,
+                                                      aParams.mImageContainer);
   return decoder.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
-OmxDecoderModule::CreateAudioDecoder(const AudioInfo& aConfig,
-                                     TaskQueue* aTaskQueue,
-                                     MediaDataDecoderCallback* aCallback,
-                                     DecoderDoctorDiagnostics* aDiagnostics)
+OmxDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams)
 {
-  RefPtr<OmxDataDecoder> decoder = new OmxDataDecoder(aConfig, aCallback, nullptr);
+  RefPtr<OmxDataDecoder> decoder = new OmxDataDecoder(aParams.mConfig,
+                                                      aParams.mCallback,
+                                                      nullptr);
   return decoder.forget();
 }
 
 PlatformDecoderModule::ConversionRequired
 OmxDecoderModule::DecoderNeedsConversion(const TrackInfo& aConfig) const
 {
   return kNeedNone;
 }
--- a/dom/media/platforms/omx/OmxDecoderModule.h
+++ b/dom/media/platforms/omx/OmxDecoderModule.h
@@ -9,28 +9,20 @@
 
 #include "PlatformDecoderModule.h"
 
 namespace mozilla {
 
 class OmxDecoderModule : public PlatformDecoderModule {
 public:
   already_AddRefed<MediaDataDecoder>
-  CreateVideoDecoder(const VideoInfo& aConfig,
-                     mozilla::layers::LayersBackend aLayersBackend,
-                     mozilla::layers::ImageContainer* aImageContainer,
-                     TaskQueue* aTaskQueue,
-                     MediaDataDecoderCallback* aCallback,
-                     DecoderDoctorDiagnostics* aDiagnostics) override;
+  CreateVideoDecoder(const CreateDecoderParams& aParams) override;
 
   already_AddRefed<MediaDataDecoder>
-  CreateAudioDecoder(const AudioInfo& aConfig,
-                     TaskQueue* aTaskQueue,
-                     MediaDataDecoderCallback* aCallback,
-                     DecoderDoctorDiagnostics* aDiagnostics) override;
+  CreateAudioDecoder(const CreateDecoderParams& aParams) override;
 
   bool SupportsMimeType(const nsACString& aMimeType,
                         DecoderDoctorDiagnostics* aDiagnostics) const override;
 
   ConversionRequired DecoderNeedsConversion(const TrackInfo& aConfig) const override;
 };
 
 } // namespace mozilla
--- a/dom/media/platforms/wmf/WMFDecoderModule.cpp
+++ b/dom/media/platforms/wmf/WMFDecoderModule.cpp
@@ -71,53 +71,45 @@ WMFDecoderModule::GetNumDecoderThreads()
 nsresult
 WMFDecoderModule::Startup()
 {
   mWMFInitialized = SUCCEEDED(wmf::MFStartup());
   return mWMFInitialized ? NS_OK : NS_ERROR_FAILURE;
 }
 
 already_AddRefed<MediaDataDecoder>
-WMFDecoderModule::CreateVideoDecoder(const VideoInfo& aConfig,
-                                     layers::LayersBackend aLayersBackend,
-                                     layers::ImageContainer* aImageContainer,
-                                     TaskQueue* aTaskQueue,
-                                     MediaDataDecoderCallback* aCallback,
-                                     DecoderDoctorDiagnostics* aDiagnostics)
+WMFDecoderModule::CreateVideoDecoder(const CreateDecoderParams& aParams)
 {
   nsAutoPtr<WMFVideoMFTManager> manager(
-    new WMFVideoMFTManager(aConfig,
-                           aLayersBackend,
-                           aImageContainer,
+    new WMFVideoMFTManager(aParams.VideoConfig(),
+                           aParams.mLayersBackend,
+                           aParams.mImageContainer,
                            sDXVAEnabled));
 
   if (!manager->Init()) {
     return nullptr;
   }
 
   RefPtr<MediaDataDecoder> decoder =
-    new WMFMediaDataDecoder(manager.forget(), aTaskQueue, aCallback);
+    new WMFMediaDataDecoder(manager.forget(), aParams.mTaskQueue, aParams.mCallback);
 
   return decoder.forget();
 }
 
 already_AddRefed<MediaDataDecoder>
-WMFDecoderModule::CreateAudioDecoder(const AudioInfo& aConfig,
-                                     TaskQueue* aTaskQueue,
-                                     MediaDataDecoderCallback* aCallback,
-                                     DecoderDoctorDiagnostics* aDiagnostics)
+WMFDecoderModule::CreateAudioDecoder(const CreateDecoderParams& aParams)
 {
-  nsAutoPtr<WMFAudioMFTManager> manager(new WMFAudioMFTManager(aConfig));
+  nsAutoPtr<WMFAudioMFTManager> manager(new WMFAudioMFTManager(aParams.AudioConfig()));
 
   if (!manager->Init()) {
     return nullptr;
   }
 
   RefPtr<MediaDataDecoder> decoder =
-    new WMFMediaDataDecoder(manager.forget(), aTaskQueue, aCallback);
+    new WMFMediaDataDecoder(manager.forget(), aParams.mTaskQueue, aParams.mCallback);
   return decoder.forget();
 }
 
 static bool
 CanCreateMFTDecoder(const GUID& aGuid)
 {
   if (FAILED(wmf::MFStartup())) {
     return false;
--- a/dom/media/platforms/wmf/WMFDecoderModule.h
+++ b/dom/media/platforms/wmf/WMFDecoderModule.h
@@ -15,28 +15,20 @@ class WMFDecoderModule : public Platform
 public:
   WMFDecoderModule();
   virtual ~WMFDecoderModule();
 
   // Initializes the module, loads required dynamic libraries, etc.
   nsresult Startup() override;
 
   already_AddRefed<MediaDataDecoder>
-  CreateVideoDecoder(const VideoInfo& aConfig,
-                     layers::LayersBackend aLayersBackend,
-                     layers::ImageContainer* aImageContainer,
-                     TaskQueue* aTaskQueue,
-                     MediaDataDecoderCallback* aCallback,
-                     DecoderDoctorDiagnostics* aDiagnostics) override;
+  CreateVideoDecoder(const CreateDecoderParams& aParams) override;
 
   already_AddRefed<MediaDataDecoder>
-  CreateAudioDecoder(const AudioInfo& aConfig,
-                     TaskQueue* aTaskQueue,
-                     MediaDataDecoderCallback* aCallback,
-                     DecoderDoctorDiagnostics* aDiagnostics) override;
+  CreateAudioDecoder(const CreateDecoderParams& aParams) override;
 
   bool SupportsMimeType(const nsACString& aMimeType,
                         DecoderDoctorDiagnostics* aDiagnostics) const override;
 
   ConversionRequired
   DecoderNeedsConversion(const TrackInfo& aConfig) const override;
 
   // Called on main thread.
--- a/dom/media/platforms/wrappers/H264Converter.cpp
+++ b/dom/media/platforms/wrappers/H264Converter.cpp
@@ -11,34 +11,29 @@
 #include "MediaInfo.h"
 #include "mp4_demuxer/AnnexB.h"
 #include "mp4_demuxer/H264.h"
 
 namespace mozilla
 {
 
 H264Converter::H264Converter(PlatformDecoderModule* aPDM,
-                             const VideoInfo& aConfig,
-                             layers::LayersBackend aLayersBackend,
-                             layers::ImageContainer* aImageContainer,
-                             TaskQueue* aTaskQueue,
-                             MediaDataDecoderCallback* aCallback,
-                             DecoderDoctorDiagnostics* aDiagnostics)
+                             const CreateDecoderParams& aParams)
   : mPDM(aPDM)
-  , mOriginalConfig(aConfig)
-  , mCurrentConfig(aConfig)
-  , mLayersBackend(aLayersBackend)
-  , mImageContainer(aImageContainer)
-  , mTaskQueue(aTaskQueue)
-  , mCallback(aCallback)
+  , mOriginalConfig(aParams.VideoConfig())
+  , mCurrentConfig(aParams.VideoConfig())
+  , mLayersBackend(aParams.mLayersBackend)
+  , mImageContainer(aParams.mImageContainer)
+  , mTaskQueue(aParams.mTaskQueue)
+  , mCallback(aParams.mCallback)
   , mDecoder(nullptr)
-  , mNeedAVCC(aPDM->DecoderNeedsConversion(aConfig) == PlatformDecoderModule::kNeedAVCC)
+  , mNeedAVCC(aPDM->DecoderNeedsConversion(aParams.mConfig) == PlatformDecoderModule::kNeedAVCC)
   , mLastError(NS_OK)
 {
-  CreateDecoder(aDiagnostics);
+  CreateDecoder(aParams.mDiagnostics);
 }
 
 H264Converter::~H264Converter()
 {
 }
 
 RefPtr<MediaDataDecoder::InitPromise>
 H264Converter::Init()
@@ -141,22 +136,25 @@ H264Converter::CreateDecoder(DecoderDoct
   }
   UpdateConfigFromExtraData(mCurrentConfig.mExtraData);
   if (!mNeedAVCC) {
     // When using a decoder handling AnnexB, we get here only once from the
     // constructor. We do want to get the dimensions extracted from the SPS.
     mOriginalConfig = mCurrentConfig;
   }
 
-  mDecoder = mPDM->CreateVideoDecoder(mNeedAVCC ? mCurrentConfig : mOriginalConfig,
-                                      mLayersBackend,
-                                      mImageContainer,
-                                      mTaskQueue,
-                                      mCallback,
-                                      aDiagnostics);
+  mDecoder = mPDM->CreateVideoDecoder({
+    mNeedAVCC ? mCurrentConfig : mOriginalConfig,
+    mTaskQueue,
+    mCallback,
+    aDiagnostics,
+    mImageContainer,
+    mLayersBackend
+  });
+
   if (!mDecoder) {
     mLastError = NS_ERROR_FAILURE;
     return NS_ERROR_FAILURE;
   }
   return NS_OK;
 }
 
 nsresult
--- a/dom/media/platforms/wrappers/H264Converter.h
+++ b/dom/media/platforms/wrappers/H264Converter.h
@@ -17,22 +17,17 @@ namespace mozilla {
 // provided in the init segment (e.g. AVC3 or Annex B)
 // H264Converter will monitor the input data, and will delay creation of the
 // MediaDataDecoder until a SPS and PPS NALs have been extracted.
 
 class H264Converter : public MediaDataDecoder {
 public:
 
   H264Converter(PlatformDecoderModule* aPDM,
-                const VideoInfo& aConfig,
-                layers::LayersBackend aLayersBackend,
-                layers::ImageContainer* aImageContainer,
-                TaskQueue* aTaskQueue,
-                MediaDataDecoderCallback* aCallback,
-                DecoderDoctorDiagnostics* aDiagnostics);
+                const CreateDecoderParams& aParams);
   virtual ~H264Converter();
 
   RefPtr<InitPromise> Init() override;
   nsresult Input(MediaRawData* aSample) override;
   nsresult Flush() override;
   nsresult Drain() override;
   nsresult Shutdown() override;
   bool IsHardwareAccelerated(nsACString& aFailureReason) const override;
--- a/dom/media/tests/mochitest/test_getUserMedia_constraints.html
+++ b/dom/media/tests/mochitest/test_getUserMedia_constraints.html
@@ -42,16 +42,22 @@ var tests = [
     constraint: "mediaSource" },
   { message: "unknown mediaSource in audio fails",
     constraints: { audio: { mediaSource: 'uncle' } },
     error: "OverconstrainedError",
     constraint: "mediaSource" },
   { message: "emtpy constraint fails",
     constraints: { },
     error: "NotSupportedError" },
+  { message: "Triggering mock failure in default video device fails",
+    constraints: { video: { deviceId: 'bad device' }, fake: true },
+    error: "NotReadableError" },
+  { message: "Triggering mock failure in default audio device fails",
+    constraints: { audio: { deviceId: 'bad device' }, fake: true },
+    error: "NotReadableError" },
   { message: "Success-path: optional video facingMode + audio ignoring facingMode",
     constraints: { audio: { mediaSource: 'microphone',
                             facingMode: 'left',
                             foo: 0,
                             advanced: [{ facingMode: 'environment' },
                                        { facingMode: 'user' },
                                        { bar: 0 }] },
                    video: { mediaSource: 'camera',
--- a/dom/media/webrtc/MediaEngineDefault.cpp
+++ b/dom/media/webrtc/MediaEngineDefault.cpp
@@ -90,16 +90,22 @@ MediaEngineDefaultVideoSource::Allocate(
                                         const MediaEnginePrefs &aPrefs,
                                         const nsString& aDeviceId,
                                         const nsACString& aOrigin)
 {
   if (mState != kReleased) {
     return NS_ERROR_FAILURE;
   }
 
+  // Mock failure for automated tests.
+  if (aConstraints.mDeviceId.IsString() &&
+      aConstraints.mDeviceId.GetAsString().EqualsASCII("bad device")) {
+    return NS_ERROR_FAILURE;
+  }
+
   mOpts = aPrefs;
   mOpts.mWidth = mOpts.mWidth ? mOpts.mWidth : MediaEngine::DEFAULT_43_VIDEO_WIDTH;
   mOpts.mHeight = mOpts.mHeight ? mOpts.mHeight : MediaEngine::DEFAULT_43_VIDEO_HEIGHT;
   mState = kAllocated;
   return NS_OK;
 }
 
 nsresult
@@ -403,16 +409,22 @@ MediaEngineDefaultAudioSource::Allocate(
                                         const MediaEnginePrefs &aPrefs,
                                         const nsString& aDeviceId,
                                         const nsACString& aOrigin)
 {
   if (mState != kReleased) {
     return NS_ERROR_FAILURE;
   }
 
+  // Mock failure for automated tests.
+  if (aConstraints.mDeviceId.IsString() &&
+      aConstraints.mDeviceId.GetAsString().EqualsASCII("bad device")) {
+    return NS_ERROR_FAILURE;
+  }
+
   mState = kAllocated;
   // generate sine wave (default 1KHz)
   mSineGenerator = new SineWaveGenerator(AUDIO_RATE,
                                          static_cast<uint32_t>(aPrefs.mFreq ? aPrefs.mFreq : 1000));
   return NS_OK;
 }
 
 nsresult
--- a/dom/moz.build
+++ b/dom/moz.build
@@ -26,17 +26,16 @@ interfaces = [
     'json',
     'offline',
     'geolocation',
     'notification',
     'permission',
     'svg',
     'smil',
     'apps',
-    'gamepad',
     'push',
 ]
 
 DIRS += ['interfaces/' + i for i in interfaces]
 
 DIRS += [
     'animation',
     'apps',
@@ -109,16 +108,17 @@ DIRS += [
     'xul',
     'resourcestats',
     'manifest',
     'vr',
     'newapps',
     'u2f',
     'console',
     'performance',
+    'xhr',
 ]
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     DIRS += ['plugins/ipc/hangui']
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
     DIRS += [
         'speakermanager',
--- a/dom/plugins/ipc/PluginInstanceChild.cpp
+++ b/dom/plugins/ipc/PluginInstanceChild.cpp
@@ -4628,10 +4628,16 @@ PluginInstanceChild::AnswerNPP_Destroy(N
     Destroy();
 
     return true;
 }
 
 void
 PluginInstanceChild::ActorDestroy(ActorDestroyReason why)
 {
+#ifdef XP_WIN
+    // ClearAllSurfaces() should not try to send anything after ActorDestroy.
+    mCurrentSurfaceActor = nullptr;
+    mBackSurfaceActor = nullptr;
+#endif
+
     Destroy();
 }
--- a/dom/push/PushDB.jsm
+++ b/dom/push/PushDB.jsm
@@ -120,20 +120,20 @@ this.PushDB.prototype = {
    */
   delete: function(aKeyID) {
     console.debug("delete()");
 
     return new Promise((resolve, reject) =>
       this.newTxn(
         "readwrite",
         this._dbStoreName,
-        function txnCb(aTxn, aStore) {
+        (aTxn, aStore) => {
           console.debug("delete: Removing record", aKeyID);
           aStore.get(aKeyID).onsuccess = event => {
-            aTxn.result = event.target.result;
+            aTxn.result = this.toPushRecord(event.target.result);
             aStore.delete(aKeyID);
           };
         },
         resolve,
         reject
       )
     );
   },
--- a/dom/push/PushNotifier.cpp
+++ b/dom/push/PushNotifier.cpp
@@ -42,55 +42,60 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(PushNoti
 NS_IMPL_CYCLE_COLLECTING_RELEASE(PushNotifier)
 
 NS_IMETHODIMP
 PushNotifier::NotifyPushWithData(const nsACString& aScope,
                                  nsIPrincipal* aPrincipal,
                                  const nsAString& aMessageId,
                                  uint32_t aDataLen, uint8_t* aData)
 {
+  NS_ENSURE_ARG(aPrincipal);
   nsTArray<uint8_t> data;
   if (!data.SetCapacity(aDataLen, fallible)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   if (!data.InsertElementsAt(0, aData, aDataLen, fallible)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   PushMessageDispatcher dispatcher(aScope, aPrincipal, aMessageId, Some(data));
   return Dispatch(dispatcher);
 }
 
 NS_IMETHODIMP
 PushNotifier::NotifyPush(const nsACString& aScope, nsIPrincipal* aPrincipal,
                          const nsAString& aMessageId)
 {
+  NS_ENSURE_ARG(aPrincipal);
   PushMessageDispatcher dispatcher(aScope, aPrincipal, aMessageId, Nothing());
   return Dispatch(dispatcher);
 }
 
 NS_IMETHODIMP
 PushNotifier::NotifySubscriptionChange(const nsACString& aScope,
                                        nsIPrincipal* aPrincipal)
 {
+  NS_ENSURE_ARG(aPrincipal);
   PushSubscriptionChangeDispatcher dispatcher(aScope, aPrincipal);
   return Dispatch(dispatcher);
 }
 
 NS_IMETHODIMP
 PushNotifier::NotifySubscriptionModified(const nsACString& aScope,
                                          nsIPrincipal* aPrincipal)
 {
+  NS_ENSURE_ARG(aPrincipal);
   PushSubscriptionModifiedDispatcher dispatcher(aScope, aPrincipal);
   return Dispatch(dispatcher);
 }
 
 NS_IMETHODIMP
 PushNotifier::NotifyError(const nsACString& aScope, nsIPrincipal* aPrincipal,
                           const nsAString& aMessage, uint32_t aFlags)
 {
+  NS_ENSURE_ARG(aPrincipal);
   PushErrorDispatcher dispatcher(aScope, aPrincipal, aMessage, aFlags);
   return Dispatch(dispatcher);
 }
 
 nsresult
 PushNotifier::Dispatch(PushDispatcher& aDispatcher)
 {
   if (XRE_IsParentProcess()) {
@@ -271,16 +276,19 @@ PushDispatcher::NotifyObserversAndWorker
 {
   Unused << NS_WARN_IF(NS_FAILED(NotifyObservers()));
   return NotifyWorkers();
 }
 
 bool
 PushDispatcher::ShouldNotifyWorkers()
 {
+  if (NS_WARN_IF(!mPrincipal)) {
+    return false;
+  }
   // System subscriptions use observer notifications instead of service worker
   // events. The `testing.notifyWorkers` pref disables worker events for
   // non-system subscriptions.
   return !nsContentUtils::IsSystemPrincipal(mPrincipal) &&
          Preferences::GetBool("dom.push.testing.notifyWorkers", true);
 }
 
 nsresult
--- a/dom/push/PushService.jsm
+++ b/dom/push/PushService.jsm
@@ -1018,20 +1018,18 @@ this.PushService = {
 
   getAllUnexpired: function() {
     return this._db.getAllUnexpired();
   },
 
   _sendRequest(action, ...params) {
     if (this._state == PUSH_SERVICE_CONNECTION_DISABLE) {
       return Promise.reject(new Error("Push service disabled"));
-    } else if (this._state == PUSH_SERVICE_ACTIVE_OFFLINE) {
-      if (this._service.serviceType() == "WebSocket" && action == "unregister") {
-        return Promise.resolve();
-      }
+    }
+    if (this._state == PUSH_SERVICE_ACTIVE_OFFLINE) {
       return Promise.reject(new Error("Push service offline"));
     }
     // Ensure the backend is ready. `getByPageRecord` already checks this, but
     // we need to check again here in case the service was restarted in the
     // meantime.
     return this._checkActivated().then(_ => {
       switch (action) {
         case "register":
@@ -1198,22 +1196,23 @@ this.PushService = {
       .then(record => {
         if (record === undefined) {
           return false;
         }
 
         let reason = Ci.nsIPushErrorReporter.UNSUBSCRIBE_MANUAL;
         return Promise.all([
           this._sendUnregister(record, reason),
-          this._db.delete(record.keyID),
-        ]).then(() => {
-          gPushNotifier.notifySubscriptionModified(record.scope,
-                                                   record.principal);
-          return true;
-        });
+          this._db.delete(record.keyID).then(record => {
+            if (record) {
+              gPushNotifier.notifySubscriptionModified(record.scope,
+                                                       record.principal);
+            }
+          }),
+        ]).then(([success]) => success);
       });
   },
 
   clear: function(info) {
     return this._checkActivated()
       .then(_ => {
         return this._dropRegistrationsIf(record =>
           info.domain == "*" ||
--- a/dom/push/PushServiceAndroidGCM.jsm
+++ b/dom/push/PushServiceAndroidGCM.jsm
@@ -54,20 +54,16 @@ this.PushServiceAndroidGCM = {
   newPushDB: function() {
     return new PushDB(kPUSHANDROIDGCMDB_DB_NAME,
                       kPUSHANDROIDGCMDB_DB_VERSION,
                       kPUSHANDROIDGCMDB_STORE_NAME,
                       "channelID",
                       PushRecordAndroidGCM);
   },
 
-  serviceType: function() {
-    return "AndroidGCM";
-  },
-
   validServerURI: function(serverURI) {
     if (!serverURI) {
       return false;
     }
 
     if (serverURI.scheme == "https") {
       return true;
     }
--- a/dom/push/PushServiceHttp2.jsm
+++ b/dom/push/PushServiceHttp2.jsm
@@ -425,20 +425,16 @@ this.PushServiceHttp2 = {
   newPushDB: function() {
     return new PushDB(kPUSHHTTP2DB_DB_NAME,
                       kPUSHHTTP2DB_DB_VERSION,
                       kPUSHHTTP2DB_STORE_NAME,
                       "subscriptionUri",
                       PushRecordHttp2);
   },
 
-  serviceType: function() {
-    return "http2";
-  },
-
   hasmainPushService: function() {
     return this._mainPushService !== null;
   },
 
   validServerURI: function(serverURI) {
     if (serverURI.scheme == "http") {
       return !!prefs.get("testing.allowInsecureServerURL");
     }
--- a/dom/push/PushServiceWebSocket.jsm
+++ b/dom/push/PushServiceWebSocket.jsm
@@ -138,20 +138,16 @@ this.PushServiceWebSocket = {
   newPushDB: function() {
     return new PushDB(kPUSHWSDB_DB_NAME,
                       kPUSHWSDB_DB_VERSION,
                       kPUSHWSDB_STORE_NAME,
                       "channelID",
                       PushRecordWebSocket);
   },
 
-  serviceType: function() {
-    return "WebSocket";
-  },
-
   disconnect: function() {
     this._shutdownWS();
   },
 
   observe: function(aSubject, aTopic, aData) {
     if (aTopic == "nsPref:changed" && aData == "dom.push.userAgentID") {
       this._onUAIDChanged();
     } else if (aTopic == "timer-callback") {
@@ -229,27 +225,25 @@ this.PushServiceWebSocket = {
 
     if (this._lastPingTime > 0 &&
         now - this._lastPingTime > this._requestTimeout) {
 
       console.debug("timeOutRequests: Did not receive pong in time");
       requestTimedOut = true;
 
     } else {
-      for (let [channelID, request] of this._registerRequests) {
+      for (let [key, request] of this._pendingRequests) {
         let duration = now - request.ctime;
         // If any of the registration requests time out, all the ones after it
         // also made to fail, since we are going to be disconnecting the
         // socket.
         requestTimedOut |= duration > this._requestTimeout;
         if (requestTimedOut) {
-          request.reject(new Error(
-            "Register request timed out for channel ID " + channelID));
-
-          this._registerRequests.delete(channelID);
+          request.reject(new Error("Request timed out: " + key));
+          this._pendingRequests.delete(key);
         }
       }
     }
 
     // The most likely reason for a pong or registration request timing out is
     // that the socket has disconnected. Best to reconnect.
     if (requestTimedOut) {
       this._reconnect();
@@ -273,17 +267,17 @@ this.PushServiceWebSocket = {
         "Not updating userAgentID");
       return;
     }
     console.debug("New _UAID", newID);
     prefs.set("userAgentID", newID);
   },
 
   _ws: null,
-  _registerRequests: new Map(),
+  _pendingRequests: new Map(),
   _currentState: STATE_SHUT_DOWN,
   _requestTimeout: 0,
   _requestTimeoutTimer: null,
   _retryFailCount: 0,
 
   /**
    * According to the WS spec, servers should immediately close the underlying
    * TCP connection after they close a WebSocket. This causes wsOnStop to be
@@ -371,17 +365,17 @@ this.PushServiceWebSocket = {
 
     this._lastPingTime = 0;
 
     if (this._pingTimer) {
       this._pingTimer.cancel();
     }
 
     if (shouldCancelPending) {
-      this._cancelRegisterRequests();
+      this._cancelPendingRequests();
     }
 
     if (this._notifyRequestQueue) {
       this._notifyRequestQueue();
       this._notifyRequestQueue = null;
     }
   },
 
@@ -432,17 +426,17 @@ this.PushServiceWebSocket = {
       this._backoffTimer = Cc["@mozilla.org/timer;1"]
                                .createInstance(Ci.nsITimer);
     }
     this._backoffTimer.init(this, retryTimeout, Ci.nsITimer.TYPE_ONE_SHOT);
   },
 
   /** Indicates whether we're waiting for pongs or requests. */
   _hasPendingRequests() {
-    return this._lastPingTime > 0 || this._registerRequests.size > 0;
+    return this._lastPingTime > 0 || this._pendingRequests.size > 0;
   },
 
   /**
    * Starts the request timeout timer unless we're already waiting for a pong
    * or register request.
    */
   _startRequestTimeoutTimer() {
     if (this._hasPendingRequests()) {
@@ -617,17 +611,17 @@ this.PushServiceWebSocket = {
       return;
     }
 
     let sendRequests = () => {
       if (this._notifyRequestQueue) {
         this._notifyRequestQueue();
         this._notifyRequestQueue = null;
       }
-      this._sendRegisterRequests();
+      this._sendPendingRequests();
     };
 
     function finishHandshake() {
       this._UAID = reply.uaid;
       this._currentState = STATE_READY;
       prefs.observe("userAgentID", this);
 
       this._dataEnabled = !!reply.use_webpush;
@@ -664,27 +658,22 @@ this.PushServiceWebSocket = {
     finishHandshake.bind(this)();
   },
 
   /**
    * Protocol handler invoked by server message.
    */
   _handleRegisterReply: function(reply) {
     console.debug("handleRegisterReply()");
-    if (typeof reply.channelID !== "string" ||
-        !this._registerRequests.has(reply.channelID)) {
+
+    let tmp = this._takeRequestForReply(reply);
+    if (!tmp) {
       return;
     }
 
-    let tmp = this._registerRequests.get(reply.channelID);
-    this._registerRequests.delete(reply.channelID);
-    if (!this._hasPendingRequests()) {
-      this._requestTimeoutTimer.cancel();
-    }
-
     if (reply.status == 200) {
       try {
         Services.io.newURI(reply.pushEndpoint, null, null);
       }
       catch (e) {
         tmp.reject(new Error("Invalid push endpoint: " + reply.pushEndpoint));
         return;
       }
@@ -703,16 +692,28 @@ this.PushServiceWebSocket = {
       tmp.resolve(record);
     } else {
       console.error("handleRegisterReply: Unexpected server response", reply);
       tmp.reject(new Error("Wrong status code for register reply: " +
         reply.status));
     }
   },
 
+  _handleUnregisterReply(reply) {
+    console.debug("handleUnregisterReply()");
+
+    let request = this._takeRequestForReply(reply);
+    if (!request) {
+      return;
+    }
+
+    let success = reply.status === 200;
+    request.resolve(success);
+  },
+
   _handleDataUpdate: function(update) {
     let promise;
     if (typeof update.channelID != "string") {
       console.warn("handleDataUpdate: Discarding update without channel ID",
         update);
       return;
     }
     function updateRecord(record) {
@@ -840,112 +841,131 @@ this.PushServiceWebSocket = {
                           .getService(Ci.nsIUUIDGenerator);
     // generateUUID() gives a UUID surrounded by {...}, slice them off.
     return uuidGenerator.generateUUID().toString().slice(1, -1);
   },
 
   register(record) {
     console.debug("register() ", record);
 
-    // start the timer since we now have at least one request
-    this._startRequestTimeoutTimer();
-
     let data = {channelID: this._generateID(),
                 messageType: "register"};
 
     if (record.appServerKey) {
       data.key = ChromeUtils.base64URLEncode(record.appServerKey, {
         // The Push server requires padding.
         pad: true,
       });
     }
 
-    return new Promise((resolve, reject) => {
-      this._registerRequests.set(data.channelID, {
-        record: record,
-        resolve: resolve,
-        reject: reject,
-        ctime: Date.now(),
-      });
-      this._queueRequest(data);
-    }).then(record => {
+    return this._sendRequestForReply(record, data).then(record => {
       if (!this._dataEnabled) {
         return record;
       }
       return PushCrypto.generateKeys()
         .then(([publicKey, privateKey]) => {
           record.p256dhPublicKey = publicKey;
           record.p256dhPrivateKey = privateKey;
           record.authenticationSecret = PushCrypto.generateAuthenticationSecret();
           return record;
         });
     });
   },
 
   unregister(record, reason) {
     console.debug("unregister() ", record, reason);
 
-    let code = kUNREGISTER_REASON_TO_CODE[reason];
-    if (!code) {
-      return Promise.reject(new Error('Invalid unregister reason'));
-    }
-    let data = {channelID: record.channelID,
-                messageType: "unregister",
-                code: code};
-    this._queueRequest(data);
-    return Promise.resolve();
+    return Promise.resolve().then(_ => {
+      let code = kUNREGISTER_REASON_TO_CODE[reason];
+      if (!code) {
+        throw new Error('Invalid unregister reason');
+      }
+      let data = {channelID: record.channelID,
+                  messageType: "unregister",
+                  code: code};
+
+      return this._sendRequestForReply(record, data);
+    });
   },
 
   _queueStart: Promise.resolve(),
   _notifyRequestQueue: null,
   _queue: null,
   _enqueue: function(op) {
     console.debug("enqueue()");
     if (!this._queue) {
       this._queue = this._queueStart;
     }
     this._queue = this._queue
                     .then(op)
                     .catch(_ => {});
   },
 
+  /** Sends a request to the server. */
   _send(data) {
-    if (this._currentState == STATE_READY) {
-      if (data.messageType != "register" ||
-          this._registerRequests.has(data.channelID)) {
-
-        // check if request has not been cancelled
-        this._wsSendMessage(data);
-      }
+    if (this._currentState != STATE_READY) {
+      console.warn("send