Merge inbound to m-c a=merge
authorWes Kocher <wkocher@mozilla.com>
Thu, 11 Dec 2014 16:50:45 -0800
changeset 219326 5288b15d22de803f6405b37c57cc2b08296d115e
parent 219189 190b2d00739f0f061622c169285f181560e6f30a (current diff)
parent 219325 8eea64db9552bd83d73668e2182654145eb69209 (diff)
child 219327 3e110e9583543c78756fcfa435dce858219f9aa5
child 219352 d17f15f8539733b6f63646d4ff12fa660c4ab4af
child 219368 76b604c4556cef4c0fdc475e19a7f938390822b2
push id10368
push userkwierso@gmail.com
push dateFri, 12 Dec 2014 01:38:39 +0000
treeherderfx-team@5288b15d22de [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone37.0a1
Merge inbound to m-c a=merge
browser/base/content/browser-social.js
browser/base/content/urlbarBindings.xml
browser/modules/UITour.jsm
dom/media/omx/RtspMediaCodecReader.h
dom/media/tests/mochitest/test_dataChannel_noOffer.html
js/src/jit-test/tests/basic/bug764289.js
js/src/jit-test/tests/basic/string-contains.js
js/src/jsapi.cpp
js/src/jscompartment.h
layout/base/nsLayoutUtils.cpp
layout/generic/nsPluginUtilsOSX.h
layout/generic/nsPluginUtilsOSX.mm
mobile/android/chrome/content/browser.js
modules/libpref/init/all.js
security/sandbox/linux/LICENSE
security/sandbox/linux/android_arm_ucontext.h
security/sandbox/linux/android_i386_ucontext.h
security/sandbox/linux/android_ucontext.h
security/sandbox/linux/arm_linux_syscalls.h
security/sandbox/linux/linux_seccomp.h
security/sandbox/linux/linux_syscalls.h
security/sandbox/linux/x86_32_linux_syscalls.h
security/sandbox/linux/x86_64_linux_syscalls.h
testing/mochitest/tests/SimpleTest/Makefile.in
testing/xpcshell/head.js
toolkit/devtools/server/actors/script.js
--- a/browser/devtools/netmonitor/netmonitor-view.js
+++ b/browser/devtools/netmonitor/netmonitor-view.js
@@ -2848,21 +2848,22 @@ nsIURL.store = new Map();
 function parseQueryString(aQueryString) {
   // Make sure there's at least one param available.
   // Be careful here, params don't necessarily need to have values, so
   // no need to verify the existence of a "=".
   if (!aQueryString) {
     return;
   }
   // Turn the params string into an array containing { name: value } tuples.
-  let paramsArray = aQueryString.replace(/^[?&]/, "").split("&").map(e =>
-    let (param = e.split("=")) {
+  let paramsArray = aQueryString.replace(/^[?&]/, "").split("&").map(e => {
+    let param = e.split("=");
+    return {
       name: param[0] ? NetworkHelper.convertToUnicode(unescape(param[0])) : "",
       value: param[1] ? NetworkHelper.convertToUnicode(unescape(param[1])) : ""
-    });
+    }});
   return paramsArray;
 }
 
 /**
  * Parse text representation of multiple HTTP headers.
  *
  * @param string aText
  *        Text of headers
--- a/build/automation.py.in
+++ b/build/automation.py.in
@@ -482,17 +482,16 @@ class Automation(object):
         ldLibraryPath = ldLibraryPath + ":" + env[envVar]
       env[envVar] = ldLibraryPath
     elif self.IS_WIN32:
       env["PATH"] = env["PATH"] + ";" + str(ldLibraryPath)
       dmdLibrary = "dmd.dll"
       preloadEnvVar = "MOZ_REPLACE_MALLOC_LIB"
 
     if dmdPath and dmdLibrary and preloadEnvVar:
-      env['DMD'] = '1'
       env[preloadEnvVar] = os.path.join(dmdPath, dmdLibrary)
 
     if crashreporter and not debugger:
       env['MOZ_CRASHREPORTER_NO_REPORT'] = '1'
       env['MOZ_CRASHREPORTER'] = '1'
     else:
       env['MOZ_CRASHREPORTER_DISABLE'] = '1'
 
--- a/build/automationutils.py
+++ b/build/automationutils.py
@@ -384,17 +384,16 @@ def environment(xrePath, env=None, crash
     preloadEnvVar = "MOZ_REPLACE_MALLOC_LIB"
   if envVar:
     envValue = ((env.get(envVar), str(ldLibraryPath))
                 if mozinfo.isWin
                 else (ldLibraryPath, dmdPath, env.get(envVar)))
     env[envVar] = os.path.pathsep.join([path for path in envValue if path])
 
   if dmdPath and dmdLibrary and preloadEnvVar:
-    env['DMD'] = '1'
     env[preloadEnvVar] = os.path.join(dmdPath, dmdLibrary)
 
   # crashreporter
   env['GNOME_DISABLE_CRASH_DIALOG'] = '1'
   env['XRE_NO_WINDOWS_CRASH_DIALOG'] = '1'
   env['NS_TRACE_MALLOC_DISABLE_STACKS'] = '1'
 
   if crashreporter and not debugger:
--- a/build/mobile/remoteautomation.py
+++ b/build/mobile/remoteautomation.py
@@ -55,17 +55,16 @@ class RemoteAutomation(Automation):
     # Set up what we need for the remote environment
     def environment(self, env=None, xrePath=None, crashreporter=True, debugger=False, dmdPath=None, lsanPath=None):
         # Because we are running remote, we don't want to mimic the local env
         # so no copying of os.environ
         if env is None:
             env = {}
 
         if dmdPath:
-            env['DMD'] = '1'
             env['MOZ_REPLACE_MALLOC_LIB'] = os.path.join(dmdPath, 'libdmd.so')
 
         # Except for the mochitest results table hiding option, which isn't
         # passed to runtestsremote.py as an actual option, but through the
         # MOZ_HIDE_RESULTS_TABLE environment variable.
         if 'MOZ_HIDE_RESULTS_TABLE' in os.environ:
             env['MOZ_HIDE_RESULTS_TABLE'] = os.environ['MOZ_HIDE_RESULTS_TABLE']
 
--- a/docshell/shistory/src/nsSHEntryShared.cpp
+++ b/docshell/shistory/src/nsSHEntryShared.cpp
@@ -29,17 +29,17 @@ uint64_t gSHEntrySharedID = 0;
 
 #define CONTENT_VIEWER_TIMEOUT_SECONDS "browser.sessionhistory.contentViewerTimeout"
 // Default this to time out unused content viewers after 30 minutes
 #define CONTENT_VIEWER_TIMEOUT_SECONDS_DEFAULT (30*60)
 
 typedef nsExpirationTracker<nsSHEntryShared, 3> HistoryTrackerBase;
 class HistoryTracker MOZ_FINAL : public HistoryTrackerBase {
 public:
-  HistoryTracker(uint32_t aTimeout)
+  explicit HistoryTracker(uint32_t aTimeout)
     : HistoryTrackerBase(1000 * aTimeout / 2)
   {
   }
 
 protected:
   virtual void NotifyExpired(nsSHEntryShared *aObj) {
     RemoveObject(aObj);
     aObj->Expire();
--- a/dom/base/ImportManager.cpp
+++ b/dom/base/ImportManager.cpp
@@ -621,16 +621,19 @@ ImportLoader::OnStartRequest(nsIRequest*
                                   DocumentFlavorHTML);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_DOM_ABORT_ERR);
 
   // The imported document must know which master document it belongs to.
   mDocument = do_QueryInterface(importDoc);
   nsCOMPtr<nsIDocument> master = mImportParent->MasterDocument();
   mDocument->SetMasterDocument(master);
 
+  // We want to inherit the sandbox flags from the master document.
+  mDocument->SetSandboxFlags(master->GetSandboxFlags());
+
   // We have to connect the blank document we created with the channel we opened,
   // and create its own LoadGroup for it.
   nsCOMPtr<nsIStreamListener> listener;
   nsCOMPtr<nsILoadGroup> loadGroup;
   channel->GetLoadGroup(getter_AddRefs(loadGroup));
   nsCOMPtr<nsILoadGroup> newLoadGroup = do_CreateInstance(NS_LOADGROUP_CONTRACTID);
   NS_ENSURE_TRUE(newLoadGroup, NS_ERROR_OUT_OF_MEMORY);
   newLoadGroup->SetLoadGroup(loadGroup);
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -59,16 +59,17 @@ EXPORTS += [
     'nsContentPolicyUtils.h',
     'nsContentSink.h',
     'nsContentTypeParser.h',
     'nsContentUtils.h',
     'nsCopySupport.h',
     'nsCrossSiteListenerProxy.h',
     'nsDeprecatedOperationList.h',
     'nsDocElementCreatedNotificationRunner.h',
+    'nsDocumentWarningList.h',
     'nsDOMAttributeMap.h',
     'nsDOMCID.h',
     'nsDOMClassInfoClasses.h',
     'nsDOMClassInfoID.h',
     'nsDOMJSUtils.h',
     'nsDOMNavigationTiming.h',
     'nsDOMString.h',
     'nsElementFrameLoaderOwner.h',
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -425,18 +425,17 @@ NS_INTERFACE_MAP_BEGIN(nsDOMClassInfo)
   else
   NS_INTERFACE_MAP_ENTRY(nsIXPCScriptable)
   NS_INTERFACE_MAP_ENTRY(nsIClassInfo)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIClassInfo)
 NS_INTERFACE_MAP_END
 
 
 static const JSClass sDOMConstructorProtoClass = {
-  "DOM Constructor.prototype", 0,
-  nullptr, nullptr, JS_PropertyStub, JS_StrictPropertyStub
+  "DOM Constructor.prototype", 0
 };
 
 
 static const char *
 CutPrefix(const char *aName) {
   static const char prefix_nsIDOM[] = "nsIDOM";
   static const char prefix_nsI[]    = "nsI";
 
@@ -999,19 +998,19 @@ nsDOMClassInfo::SetProperty(nsIXPConnect
 NS_IMETHODIMP
 nsDOMClassInfo::Enumerate(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
                           JSObject *obj, bool *_retval)
 {
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsDOMClassInfo::NewEnumerate(nsIXPConnectWrappedNative *wrapper,
-                             JSContext *cx, JSObject *obj, uint32_t enum_op,
-                             jsval *statep, jsid *idp, bool *_retval)
+nsDOMClassInfo::NewEnumerate(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
+                             JSObject *obj, JS::AutoIdVector &properties,
+                             bool *_retval)
 {
   NS_WARNING("nsDOMClassInfo::NewEnumerate Don't call me!");
 
   return NS_ERROR_UNEXPECTED;
 }
 
 NS_IMETHODIMP
 nsDOMClassInfo::Resolve(nsIXPConnectWrappedNative *wrapper, JSContext *cx,
@@ -2046,18 +2045,17 @@ nsWindowSH::NameStructEnabled(JSContext*
 }
 
 #ifdef RELEASE_BUILD
 #define USE_CONTROLLERS_SHIM
 #endif
 
 #ifdef USE_CONTROLLERS_SHIM
 static const JSClass ControllersShimClass = {
-    "XULControllers", 0,
-    nullptr, nullptr, JS_PropertyStub, JS_StrictPropertyStub
+    "XULControllers", 0
 };
 #endif
 
 // static
 nsresult
 nsWindowSH::GlobalResolve(nsGlobalWindow *aWin, JSContext *cx,
                           JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
                           JS::MutableHandle<JSPropertyDescriptor> desc)
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -3560,17 +3560,22 @@ nsDocument::SetBaseURI(nsIURI* aURI)
   }
 
   // Check if CSP allows this base-uri
   nsCOMPtr<nsIContentSecurityPolicy> csp;
   nsresult rv = NodePrincipal()->GetCsp(getter_AddRefs(csp));
   NS_ENSURE_SUCCESS(rv, rv);
   if (csp) {
     bool permitsBaseURI = false;
-    rv = csp->PermitsBaseURI(aURI, &permitsBaseURI);
+
+    // base-uri is only enforced if explicitly defined in the
+    // policy - do *not* consult default-src, see:
+    // http://www.w3.org/TR/CSP2/#directive-default-src
+    rv = csp->Permits(aURI, nsIContentSecurityPolicy::BASE_URI_DIRECTIVE,
+                      true, &permitsBaseURI);
     NS_ENSURE_SUCCESS(rv, rv);
     if (!permitsBaseURI) {
       return NS_OK;
     }
   }
 
   if (aURI) {
     mDocumentBaseURI = NS_TryToMakeImmutable(aURI);
@@ -10170,44 +10175,79 @@ nsDocument::FindImageMap(const nsAString
       return map->AsElement();
     }
   }
 
   return nullptr;
 }
 
 #define DEPRECATED_OPERATION(_op) #_op "Warning",
-static const char* kWarnings[] = {
+static const char* kDeprecationWarnings[] = {
 #include "nsDeprecatedOperationList.h"
   nullptr
 };
 #undef DEPRECATED_OPERATION
 
+#define DOCUMENT_WARNING(_op) #_op "Warning",
+static const char* kDocumentWarnings[] = {
+#include "nsDocumentWarningList.h"
+  nullptr
+};
+#undef DOCUMENT_WARNING
+
 bool
 nsIDocument::HasWarnedAbout(DeprecatedOperations aOperation)
 {
   static_assert(eDeprecatedOperationCount <= 64,
                 "Too many deprecated operations");
-  return mWarnedAbout & (1ull << aOperation);
+  return mDeprecationWarnedAbout & (1ull << aOperation);
 }
 
 void
 nsIDocument::WarnOnceAbout(DeprecatedOperations aOperation,
                            bool asError /* = false */)
 {
   if (HasWarnedAbout(aOperation)) {
     return;
   }
-  mWarnedAbout |= (1ull << aOperation);
+  mDeprecationWarnedAbout |= (1ull << aOperation);
   uint32_t flags = asError ? nsIScriptError::errorFlag
                            : nsIScriptError::warningFlag;
   nsContentUtils::ReportToConsole(flags,
                                   NS_LITERAL_CSTRING("DOM Core"), this,
                                   nsContentUtils::eDOM_PROPERTIES,
-                                  kWarnings[aOperation]);
+                                  kDeprecationWarnings[aOperation]);
+}
+
+bool
+nsIDocument::HasWarnedAbout(DocumentWarnings aWarning)
+{
+  static_assert(eDocumentWarningCount <= 64,
+                "Too many document warnings");
+  return mDocWarningWarnedAbout & (1ull << aWarning);
+}
+
+void
+nsIDocument::WarnOnceAbout(DocumentWarnings aWarning,
+                           bool asError /* = false */,
+                           const char16_t **aParams /* = nullptr */,
+                           uint32_t aParamsLength /* = 0 */)
+{
+  if (HasWarnedAbout(aWarning)) {
+    return;
+  }
+  mDocWarningWarnedAbout |= (1ull << aWarning);
+  uint32_t flags = asError ? nsIScriptError::errorFlag
+                           : nsIScriptError::warningFlag;
+  nsContentUtils::ReportToConsole(flags,
+                                  NS_LITERAL_CSTRING("DOM Core"), this,
+                                  nsContentUtils::eDOM_PROPERTIES,
+                                  kDocumentWarnings[aWarning],
+                                  aParams,
+                                  aParamsLength);
 }
 
 nsresult
 nsDocument::AddImage(imgIRequest* aImage)
 {
   NS_ENSURE_ARG_POINTER(aImage);
 
   // See if the image is already in the hashtable. If it is, get the old count.
new file mode 100644
--- /dev/null
+++ b/dom/base/nsDocumentWarningList.h
@@ -0,0 +1,12 @@
+
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+// IWYU pragma: private, include "nsIDocument.h"
+
+/*
+ * This file contains the list of document DOM operations warnings.  It is
+ * designed to be used as input to the C preprocessor *only*.
+ */
+
+DOCUMENT_WARNING(WillChangeBudget)
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -905,17 +905,18 @@ nsFrameLoader::ShowRemoteFrame(const nsI
       mRemoteBrowserInitialized = true;
     }
   } else {
     nsIntRect dimensions;
     NS_ENSURE_SUCCESS(GetWindowDimensions(dimensions), false);
 
     // Don't show remote iframe if we are waiting for the completion of reflow.
     if (!aFrame || !(aFrame->GetStateBits() & NS_FRAME_FIRST_REFLOW)) {
-      mRemoteBrowser->UpdateDimensions(dimensions, size);
+      nsIntPoint chromeDisp = aFrame->GetChromeDisplacement();
+      mRemoteBrowser->UpdateDimensions(dimensions, size, chromeDisp);
     }
   }
 
   return true;
 }
 
 void
 nsFrameLoader::Hide()
@@ -1937,17 +1938,18 @@ nsFrameLoader::GetWindowDimensions(nsInt
 NS_IMETHODIMP
 nsFrameLoader::UpdatePositionAndSize(nsSubDocumentFrame *aIFrame)
 {
   if (mRemoteFrame) {
     if (mRemoteBrowser) {
       nsIntSize size = aIFrame->GetSubdocumentSize();
       nsIntRect dimensions;
       NS_ENSURE_SUCCESS(GetWindowDimensions(dimensions), NS_ERROR_FAILURE);
-      mRemoteBrowser->UpdateDimensions(dimensions, size);
+      nsIntPoint chromeDisp = aIFrame->GetChromeDisplacement();
+      mRemoteBrowser->UpdateDimensions(dimensions, size, chromeDisp);
     }
     return NS_OK;
   }
   return UpdateBaseWindowPositionAndSize(aIFrame);
 }
 
 nsresult
 nsFrameLoader::UpdateBaseWindowPositionAndSize(nsSubDocumentFrame *aIFrame)
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2098,16 +2098,28 @@ public:
   enum DeprecatedOperations {
 #include "nsDeprecatedOperationList.h"
     eDeprecatedOperationCount
   };
 #undef DEPRECATED_OPERATION
   bool HasWarnedAbout(DeprecatedOperations aOperation);
   void WarnOnceAbout(DeprecatedOperations aOperation, bool asError = false);
 
+#define DOCUMENT_WARNING(_op) e##_op,
+  enum DocumentWarnings {
+#include "nsDocumentWarningList.h"
+    eDocumentWarningCount
+  };
+#undef DOCUMENT_WARNING
+  bool HasWarnedAbout(DocumentWarnings aWarning);
+  void WarnOnceAbout(DocumentWarnings aWarning,
+                     bool asError = false,
+                     const char16_t **aParams = nullptr,
+                     uint32_t aParamsLength = 0);
+
   virtual void PostVisibilityUpdateEvent() = 0;
   
   bool IsSyntheticDocument() const { return mIsSyntheticDocument; }
 
   void SetNeedLayoutFlush() {
     mNeedLayoutFlush = true;
     if (mDisplayDocument) {
       mDisplayDocument->SetNeedLayoutFlush();
@@ -2454,17 +2466,18 @@ public:
   }
 
   // FontFaceSource
   mozilla::dom::FontFaceSet* GetFonts(mozilla::ErrorResult& aRv);
 
   bool DidFireDOMContentLoaded() const { return mDidFireDOMContentLoaded; }
 
 private:
-  uint64_t mWarnedAbout;
+  uint64_t mDeprecationWarnedAbout;
+  uint64_t mDocWarningWarnedAbout;
   SelectorCache mSelectorCache;
 
 protected:
   ~nsIDocument();
   nsPropertyTable* GetExtraPropertyTable(uint16_t aCategory);
 
   // Never ever call this. Only call GetWindow!
   virtual nsPIDOMWindow *GetWindowInternal() const = 0;
--- a/dom/base/nsScriptLoader.cpp
+++ b/dom/base/nsScriptLoader.cpp
@@ -433,16 +433,18 @@ ParseTypeAttribute(const nsAString& aTyp
 
   return true;
 }
 
 static bool
 CSPAllowsInlineScript(nsIScriptElement *aElement, nsIDocument *aDocument)
 {
   nsCOMPtr<nsIContentSecurityPolicy> csp;
+  // Note: For imports NodePrincipal and the principal of the master are
+  // the same.
   nsresult rv = aDocument->NodePrincipal()->GetCsp(getter_AddRefs(csp));
   NS_ENSURE_SUCCESS(rv, false);
 
   if (!csp) {
     // no CSP --> allow
     return true;
   }
 
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -1193,18 +1193,18 @@ ResolvePrototypeOrConstructor(JSContext*
     if (!protoOrIface) {
       return false;
     }
 
     cacheOnHolder = true;
 
     desc.object().set(wrapper);
     desc.setAttributes(attrs);
-    desc.setGetter(JS_PropertyStub);
-    desc.setSetter(JS_StrictPropertyStub);
+    desc.setGetter(nullptr);
+    desc.setSetter(nullptr);
     desc.value().set(JS::ObjectValue(*protoOrIface));
   }
   return JS_WrapPropertyDescriptor(cx, desc);
 }
 
 #ifdef DEBUG
 
 static void
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -581,17 +581,16 @@ DOMInterfaces = {
 },
 
 'IDBMutableFile': {
     'nativeType': 'mozilla::dom::indexedDB::IDBMutableFile',
 },
 
 'IDBObjectStore': {
     'nativeType': 'mozilla::dom::indexedDB::IDBObjectStore',
-    'implicitJSContext': [ 'createIndex' ],
     'binaryNames': {
         'mozGetAll': 'getAll'
     }
 },
 
 'IDBOpenDBRequest': {
     'nativeType': 'mozilla::dom::indexedDB::IDBOpenDBRequest',
     'headerFile': 'IDBRequest.h'
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -430,18 +430,18 @@ class CGDOMJSClass(CGThing):
 
         return fill(
             """
             static const DOMJSClass Class = {
               { "${name}",
                 ${flags},
                 ${addProperty}, /* addProperty */
                 nullptr,               /* delProperty */
-                JS_PropertyStub,       /* getProperty */
-                JS_StrictPropertyStub, /* setProperty */
+                nullptr,               /* getProperty */
+                nullptr,               /* setProperty */
                 ${enumerate}, /* enumerate */
                 ${resolve}, /* resolve */
                 nullptr,               /* convert */
                 ${finalize}, /* finalize */
                 ${call}, /* call */
                 nullptr,               /* hasInstance */
                 nullptr,               /* construct */
                 ${trace}, /* trace */
@@ -646,18 +646,18 @@ class CGPrototypeJSClass(CGThing):
         return fill(
             """
             static const DOMIfaceAndProtoJSClass PrototypeClass = {
               {
                 "${name}Prototype",
                 JSCLASS_IS_DOMIFACEANDPROTOJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(${slotCount}),
                 nullptr,               /* addProperty */
                 nullptr,               /* delProperty */
-                JS_PropertyStub,       /* getProperty */
-                JS_StrictPropertyStub, /* setProperty */
+                nullptr,               /* getProperty */
+                nullptr,               /* setProperty */
                 nullptr,               /* enumerate */
                 nullptr,               /* resolve */
                 nullptr,               /* convert */
                 nullptr,               /* finalize */
                 nullptr,               /* call */
                 nullptr,               /* hasInstance */
                 nullptr,               /* construct */
                 nullptr,               /* trace */
@@ -742,18 +742,18 @@ class CGInterfaceObjectJSClass(CGThing):
         return fill(
             """
             static const DOMIfaceAndProtoJSClass InterfaceObjectClass = {
               {
                 "Function",
                 JSCLASS_IS_DOMIFACEANDPROTOJSCLASS | JSCLASS_HAS_RESERVED_SLOTS(${slotCount}),
                 nullptr,               /* addProperty */
                 nullptr,               /* delProperty */
-                JS_PropertyStub,       /* getProperty */
-                JS_StrictPropertyStub, /* setProperty */
+                nullptr,               /* getProperty */
+                nullptr,               /* setProperty */
                 nullptr,               /* enumerate */
                 nullptr,               /* resolve */
                 nullptr,               /* convert */
                 nullptr,               /* finalize */
                 ${ctorname}, /* call */
                 ${hasInstance}, /* hasInstance */
                 ${ctorname}, /* construct */
                 nullptr,               /* trace */
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -690,17 +690,17 @@ NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(Ca
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(CanvasPattern, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(CanvasPattern, Release)
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(CanvasPattern, mContext)
 
 class CanvasDrawObserver
 {
 public:
-  CanvasDrawObserver(CanvasRenderingContext2D* aCanvasContext);
+  explicit CanvasDrawObserver(CanvasRenderingContext2D* aCanvasContext);
 
   // Only enumerate draw calls that could affect the heuristic
   enum DrawCallType {
     PutImageData,
     GetImageData,
     DrawImage
   };
 
--- a/dom/canvas/WebGLBindableName.h
+++ b/dom/canvas/WebGLBindableName.h
@@ -49,19 +49,19 @@ protected:
 /** Represents a GL name that can be bound to a target.
  */
 template<typename T>
 class WebGLBindableName
     : public WebGLBindable<T>
 {
 public:
 
-    WebGLBindableName(GLuint name)
+    explicit WebGLBindableName(GLuint aName)
         : WebGLBindable<T>()
-        , mGLName(name)
+        , mGLName(aName)
     { }
     GLuint GLName() const { return mGLName; }
 
 protected:
     const GLuint mGLName;
 };
 
 
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -3318,17 +3318,18 @@ WebGLContext::CompileShader(WebGLShader*
     compiler = ShConstructCompiler(shader->ShaderType(),
                                    SH_WEBGL_SPEC,
                                    targetShaderSourceLanguage,
                                    &resources);
 
     int compileOptions = SH_VARIABLES |
                          SH_ENFORCE_PACKING_RESTRICTIONS |
                          SH_INIT_VARYINGS_WITHOUT_STATIC_USE |
-                         SH_OBJECT_CODE;
+                         SH_OBJECT_CODE |
+                         SH_LIMIT_CALL_STACK_DEPTH;
 
     if (resources.MaxExpressionComplexity > 0) {
         compileOptions |= SH_LIMIT_EXPRESSION_COMPLEXITY;
     }
 
 #ifndef XP_MACOSX
     // We want to do this everywhere, but to do this on Mac, we need
     // to do it only on Mac OSX > 10.6 as this causes the shader
--- a/dom/datastore/DataStoreDB.cpp
+++ b/dom/datastore/DataStoreDB.cpp
@@ -242,45 +242,45 @@ DataStoreDB::UpgradeSchema(nsIDOMEvent* 
   IDBDatabase* database = nullptr;
   nsresult rv = UNWRAP_OBJECT(IDBDatabase, &result.toObject(), database);
   if (NS_FAILED(rv)) {
     NS_WARNING("Didn't get the object we expected!");
     return rv;
   }
 
   {
-    RootedDictionary<IDBObjectStoreParameters> params(cx);
+    IDBObjectStoreParameters params;
     params.Init(NS_LITERAL_STRING("{ \"autoIncrement\": true }"));
     nsRefPtr<IDBObjectStore> store =
-      database->CreateObjectStore(cx, NS_LITERAL_STRING(DATASTOREDB_NAME),
+      database->CreateObjectStore(NS_LITERAL_STRING(DATASTOREDB_NAME),
                                   params, error);
     if (NS_WARN_IF(error.Failed())) {
       return error.ErrorCode();
     }
   }
 
   nsRefPtr<IDBObjectStore> store;
 
   {
-    RootedDictionary<IDBObjectStoreParameters> params(cx);
+    IDBObjectStoreParameters params;
     params.Init(NS_LITERAL_STRING("{ \"autoIncrement\": true, \"keyPath\": \"internalRevisionId\" }"));
 
     store =
-      database->CreateObjectStore(cx, NS_LITERAL_STRING(DATASTOREDB_REVISION),
+      database->CreateObjectStore(NS_LITERAL_STRING(DATASTOREDB_REVISION),
                                   params, error);
     if (NS_WARN_IF(error.Failed())) {
       return error.ErrorCode();
     }
   }
 
   {
-    RootedDictionary<IDBIndexParameters> params(cx);
+    IDBIndexParameters params;
     params.Init(NS_LITERAL_STRING("{ \"unique\": true }"));
     nsRefPtr<IDBIndex> index =
-      store->CreateIndex(cx, NS_LITERAL_STRING(DATASTOREDB_REVISION_INDEX),
+      store->CreateIndex(NS_LITERAL_STRING(DATASTOREDB_REVISION_INDEX),
                          NS_LITERAL_STRING("revisionId"), params, error);
     if (NS_WARN_IF(error.Failed())) {
       return error.ErrorCode();
     }
   }
 
   return NS_OK;
 }
--- a/dom/geolocation/nsGeolocationSettings.h
+++ b/dom/geolocation/nsGeolocationSettings.h
@@ -52,17 +52,17 @@ enum GeolocationFuzzMethod {
 #endif
 
 /**
  * Simple class for holding the geolocation settings values.
  */
 
 class GeolocationSetting MOZ_FINAL {
 public:
-  GeolocationSetting(const nsString& aOrigin) :
+  explicit GeolocationSetting(const nsString& aOrigin) :
     mFuzzMethod(GEO_ALA_TYPE_DEFAULT),
 #ifdef MOZ_APPROX_LOCATION
     mDistance(0),
 #endif
     mLatitude(0.0),
     mLongitude(0.0),
     mOrigin(aOrigin) {}
 
--- a/dom/html/HTMLFormElement.cpp
+++ b/dom/html/HTMLFormElement.cpp
@@ -1628,17 +1628,22 @@ HTMLFormElement::GetActionURL(nsIURI** a
   NS_ENSURE_SUCCESS(rv, rv);
 
   // Check if CSP allows this form-action
   nsCOMPtr<nsIContentSecurityPolicy> csp;
   rv = NodePrincipal()->GetCsp(getter_AddRefs(csp));
   NS_ENSURE_SUCCESS(rv, rv);
   if (csp) {
     bool permitsFormAction = true;
-    rv = csp->PermitsFormAction(actionURL, &permitsFormAction);
+
+    // form-action is only enforced if explicitly defined in the
+    // policy - do *not* consult default-src, see:
+    // http://www.w3.org/TR/CSP2/#directive-default-src
+    rv = csp->Permits(actionURL, nsIContentSecurityPolicy::FORM_ACTION_DIRECTIVE,
+                      true, &permitsFormAction);
     NS_ENSURE_SUCCESS(rv, rv);
     if (!permitsFormAction) {
       rv = NS_ERROR_CSP_FORM_ACTION_VIOLATION;
     }
   }
 
   //
   // Assign to the output
--- a/dom/html/TimeRanges.cpp
+++ b/dom/html/TimeRanges.cpp
@@ -93,49 +93,49 @@ TimeRanges::GetEndTime()
 {
   if (mRanges.IsEmpty()) {
     return -1.0;
   }
   return mRanges[mRanges.Length() - 1].mEnd;
 }
 
 void
-TimeRanges::Normalize()
+TimeRanges::Normalize(double aError)
 {
   if (mRanges.Length() >= 2) {
     nsAutoTArray<TimeRange,4> normalized;
 
     mRanges.Sort(CompareTimeRanges());
 
     // This merges the intervals.
     TimeRange current(mRanges[0]);
     for (uint32_t i = 1; i < mRanges.Length(); i++) {
       if (current.mStart <= mRanges[i].mStart &&
           current.mEnd >= mRanges[i].mEnd) {
         continue;
       }
-      if (current.mEnd >= mRanges[i].mStart) {
+      if (current.mEnd + aError >= mRanges[i].mStart) {
         current.mEnd = mRanges[i].mEnd;
       } else {
         normalized.AppendElement(current);
         current = mRanges[i];
       }
     }
 
     normalized.AppendElement(current);
 
     mRanges = normalized;
   }
 }
 
 void
-TimeRanges::Union(const TimeRanges* aOtherRanges)
+TimeRanges::Union(const TimeRanges* aOtherRanges, double aError)
 {
   mRanges.AppendElements(aOtherRanges->mRanges);
-  Normalize();
+  Normalize(aError);
 }
 
 void
 TimeRanges::Intersection(const TimeRanges* aOtherRanges)
 {
   nsAutoTArray<TimeRange,4> intersection;
 
   const nsTArray<TimeRange>& otherRanges = aOtherRanges->mRanges;
--- a/dom/html/TimeRanges.h
+++ b/dom/html/TimeRanges.h
@@ -37,20 +37,20 @@ public:
 
   // Returns the start time of the first range, or -1 if no ranges exist.
   double GetStartTime();
 
   // Returns the end time of the last range, or -1 if no ranges exist.
   double GetEndTime();
 
   // See http://www.whatwg.org/html/#normalized-timeranges-object
-  void Normalize();
+  void Normalize(double aError = 0.0);
 
   // Mutate this TimeRange to be the union of this and aOtherRanges.
-  void Union(const TimeRanges* aOtherRanges);
+  void Union(const TimeRanges* aOtherRanges, double aError);
 
   // Mutate this TimeRange to be the intersection of this and aOtherRanges.
   void Intersection(const TimeRanges* aOtherRanges);
 
   JSObject* WrapObject(JSContext* aCx);
 
   uint32_t Length() const
   {
--- a/dom/html/nsBrowserElement.cpp
+++ b/dom/html/nsBrowserElement.cpp
@@ -30,17 +30,17 @@ namespace mozilla {
 
 static const char kRemoteBrowserPending[] = "remote-browser-pending";
 static const char kInprocessBrowserShown[] = "inprocess-browser-shown";
 
 class nsBrowserElement::BrowserShownObserver : public nsIObserver
                                              , public nsSupportsWeakReference
 {
 public:
-  BrowserShownObserver(nsBrowserElement* aBrowserElement);
+  explicit BrowserShownObserver(nsBrowserElement* aBrowserElement);
   NS_DECL_ISUPPORTS
   NS_DECL_NSIOBSERVER
   void AddObserver();
   void RemoveObserver();
 private:
   virtual ~BrowserShownObserver();
 
   // Weak reference to the browser element. nsBrowserElement has a
new file mode 100644
--- /dev/null
+++ b/dom/html/test/imports/file_CSP_sandbox.html
@@ -0,0 +1,7 @@
+<!DOCTYPE html>
+<head>
+</head>
+<body>
+  <link rel="import" href="file_CSP_sandbox_import.html" id="import"></link>
+</body>
+
new file mode 100644
--- /dev/null
+++ b/dom/html/test/imports/file_CSP_sandbox_import.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<head>
+</head>
+<body>
+<script>
+  var scriptExecuted = true;
+</script>
+</body>
+
--- a/dom/html/test/imports/mochitest.ini
+++ b/dom/html/test/imports/mochitest.ini
@@ -33,23 +33,25 @@ support-files =
   file_cycle_4_E.html
   file_encoding.html
   file_simple_import.html
   file_blocking_DOMContentLoaded_A.html
   file_blocking_DOMContentLoaded_B.html
   file_blocking_DOMContentLoaded_C.html
   file_blocking_DOMContentLoaded_D.html
   file_element_upgrade.html
-
+  file_CSP_sandbox.html
+  file_CSP_sandbox_import.html
 
 [test_cycle_1.html]
 skip-if = toolkit == 'gonk' # nested imports fail on b2g emulator
 [test_cycle_2.html]
 skip-if = toolkit == 'gonk' # nested imports fail on b2g emulator
 [test_cycle_3.html]
 skip-if = toolkit == 'gonk' # nested imports fail on b2g emulator
 [test_cycle_4.html]
 skip-if = toolkit == 'gonk' # nested imports fail on b2g emulator
 [test_blocking_DOMContentLoaded.html]
 skip-if = toolkit == 'gonk' # nested imports fail on b2g emulator
 [test_encoding.html]
 [test_defaultView.html]
 [test_element_upgrade.html]
+[test_CSP_sandbox.html]
new file mode 100644
--- /dev/null
+++ b/dom/html/test/imports/test_CSP_sandbox.html
@@ -0,0 +1,26 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1106713
+-->
+<head>
+  <title>Test for Bug 1106713</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <meta http-equiv="Content-Type" content="text/html;charset=utf-8">
+</head>
+<body>
+  <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1106713">Mozilla Bug 1106713</a>
+  <script type="text/javascript">
+    SimpleTest.waitForExplicitFinish();
+    function go() {
+      var ifr = document.getElementById('iframe1').contentWindow;
+      ok(!ifr.scriptExecuted, "script is not allowed to run");
+      SimpleTest.finish();
+    }
+
+  </script>
+  <iframe src='file_CSP_sandbox.html' sandbox="allow-same-origin" onload="go()" id="iframe1"></iframe>
+</body>
+</html>
+
--- a/dom/indexedDB/ActorsParent.cpp
+++ b/dom/indexedDB/ActorsParent.cpp
@@ -14070,18 +14070,18 @@ CreateIndexOp::DoDatabaseWork(Transactio
   return NS_OK;
 }
 
 const JSClass CreateIndexOp::ThreadLocalJSRuntime::kGlobalClass = {
   "IndexedDBTransactionThreadGlobal",
   JSCLASS_GLOBAL_FLAGS,
   /* addProperty */ nullptr,
   /* delProperty */ nullptr,
-  /* getProperty */ JS_PropertyStub,
-  /* setProperty */ JS_StrictPropertyStub,
+  /* getProperty */ nullptr,
+  /* setProperty */ nullptr,
   /* enumerate */ nullptr,
   /* resolve */ nullptr,
   /* convert */ nullptr,
   /* finalize */ nullptr,
   /* call */ nullptr,
   /* hasInstance */ nullptr,
   /* construct */ nullptr,
   /* trace */ JS_GlobalObjectTraceHook
--- a/dom/indexedDB/IDBDatabase.cpp
+++ b/dom/indexedDB/IDBDatabase.cpp
@@ -443,17 +443,16 @@ IDBDatabase::GetOwnerDocument() const
     nsCOMPtr<nsIDocument> doc = window->GetExtantDoc();
     return doc.forget();
   }
   return nullptr;
 }
 
 already_AddRefed<IDBObjectStore>
 IDBDatabase::CreateObjectStore(
-                            JSContext* aCx,
                             const nsAString& aName,
                             const IDBObjectStoreParameters& aOptionalParameters,
                             ErrorResult& aRv)
 {
   AssertIsOnOwningThread();
 
   IDBTransaction* transaction = IDBTransaction::GetCurrent();
 
@@ -461,17 +460,17 @@ IDBDatabase::CreateObjectStore(
       transaction->GetMode() != IDBTransaction::VERSION_CHANGE) {
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
     return nullptr;
   }
 
   MOZ_ASSERT(transaction->IsOpen());
 
   KeyPath keyPath(0);
-  if (NS_FAILED(KeyPath::Parse(aCx, aOptionalParameters.mKeyPath, &keyPath))) {
+  if (NS_FAILED(KeyPath::Parse(aOptionalParameters.mKeyPath, &keyPath))) {
     aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
     return nullptr;
   }
 
   nsTArray<ObjectStoreSpec>& objectStores = mSpec->objectStores();
   for (uint32_t count = objectStores.Length(), index = 0;
        index < count;
        index++) {
--- a/dom/indexedDB/IDBDatabase.h
+++ b/dom/indexedDB/IDBDatabase.h
@@ -198,18 +198,17 @@ public:
 
   nsPIDOMWindow*
   GetParentObject() const;
 
   already_AddRefed<DOMStringList>
   ObjectStoreNames() const;
 
   already_AddRefed<IDBObjectStore>
-  CreateObjectStore(JSContext* aCx,
-                    const nsAString& aName,
+  CreateObjectStore(const nsAString& aName,
                     const IDBObjectStoreParameters& aOptionalParameters,
                     ErrorResult& aRv);
 
   void
   DeleteObjectStore(const nsAString& name, ErrorResult& aRv);
 
   already_AddRefed<IDBTransaction>
   Transaction(const nsAString& aStoreName,
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -837,21 +837,17 @@ ClearStructuredCloneBuffer(JSAutoStructu
     aBuffer.clear();
   }
 }
 
 } // anonymous namespace
 
 const JSClass IDBObjectStore::sDummyPropJSClass = {
   "IDBObjectStore Dummy",
-  0 /* flags */,
-  nullptr /* addProperty */,
-  nullptr /* delProperty */,
-  JS_PropertyStub /* getProperty */,
-  JS_StrictPropertyStub /* setProperty */
+  0 /* flags */
 };
 
 IDBObjectStore::IDBObjectStore(IDBTransaction* aTransaction,
                                const ObjectStoreSpec* aSpec)
   : mTransaction(aTransaction)
   , mCachedKeyPath(JSVAL_VOID)
   , mSpec(aSpec)
   , mId(aSpec->metadata().id())
@@ -1645,57 +1641,54 @@ IDBObjectStore::Delete(JSContext* aCx,
                     IDB_PROFILER_STRING(Transaction()->Database()),
                     IDB_PROFILER_STRING(Transaction()),
                     IDB_PROFILER_STRING(this), IDB_PROFILER_STRING(aKeyRange));
 
   return request.forget();
 }
 
 already_AddRefed<IDBIndex>
-IDBObjectStore::CreateIndex(JSContext* aCx,
-                            const nsAString& aName,
+IDBObjectStore::CreateIndex(const nsAString& aName,
                             const nsAString& aKeyPath,
                             const IDBIndexParameters& aOptionalParameters,
                             ErrorResult& aRv)
 {
   AssertIsOnOwningThread();
 
   KeyPath keyPath(0);
-  if (NS_FAILED(KeyPath::Parse(aCx, aKeyPath, &keyPath)) ||
+  if (NS_FAILED(KeyPath::Parse(aKeyPath, &keyPath)) ||
       !keyPath.IsValid()) {
     aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
     return nullptr;
   }
 
-  return CreateIndexInternal(aCx, aName, keyPath, aOptionalParameters, aRv);
+  return CreateIndexInternal(aName, keyPath, aOptionalParameters, aRv);
 }
 
 already_AddRefed<IDBIndex>
-IDBObjectStore::CreateIndex(JSContext* aCx,
-                            const nsAString& aName,
+IDBObjectStore::CreateIndex(const nsAString& aName,
                             const Sequence<nsString >& aKeyPath,
                             const IDBIndexParameters& aOptionalParameters,
                             ErrorResult& aRv)
 {
   AssertIsOnOwningThread();
 
   KeyPath keyPath(0);
   if (aKeyPath.IsEmpty() ||
-      NS_FAILED(KeyPath::Parse(aCx, aKeyPath, &keyPath)) ||
+      NS_FAILED(KeyPath::Parse(aKeyPath, &keyPath)) ||
       !keyPath.IsValid()) {
     aRv.Throw(NS_ERROR_DOM_SYNTAX_ERR);
     return nullptr;
   }
 
-  return CreateIndexInternal(aCx, aName, keyPath, aOptionalParameters, aRv);
+  return CreateIndexInternal(aName, keyPath, aOptionalParameters, aRv);
 }
 
 already_AddRefed<IDBIndex>
 IDBObjectStore::CreateIndexInternal(
-                                  JSContext* aCx,
                                   const nsAString& aName,
                                   const KeyPath& aKeyPath,
                                   const IDBIndexParameters& aOptionalParameters,
                                   ErrorResult& aRv)
 {
   AssertIsOnOwningThread();
 
   IDBTransaction* transaction = IDBTransaction::GetCurrent();
--- a/dom/indexedDB/IDBObjectStore.h
+++ b/dom/indexedDB/IDBObjectStore.h
@@ -179,25 +179,23 @@ public:
 
   already_AddRefed<IDBRequest>
   Get(JSContext* aCx, JS::Handle<JS::Value> aKey, ErrorResult& aRv);
 
   already_AddRefed<IDBRequest>
   Clear(ErrorResult& aRv);
 
   already_AddRefed<IDBIndex>
-  CreateIndex(JSContext* aCx,
-              const nsAString& aName,
+  CreateIndex(const nsAString& aName,
               const nsAString& aKeyPath,
               const IDBIndexParameters& aOptionalParameters,
               ErrorResult& aRv);
 
   already_AddRefed<IDBIndex>
-  CreateIndex(JSContext* aCx,
-              const nsAString& aName,
+  CreateIndex(const nsAString& aName,
               const Sequence<nsString>& aKeyPath,
               const IDBIndexParameters& aOptionalParameters,
               ErrorResult& aRv);
 
   already_AddRefed<IDBIndex>
   Index(const nsAString& aName, ErrorResult &aRv);
 
   void
@@ -293,18 +291,17 @@ private:
   already_AddRefed<IDBRequest>
   GetAllInternal(bool aKeysOnly,
                  JSContext* aCx,
                  JS::Handle<JS::Value> aKey,
                  const Optional<uint32_t>& aLimit,
                  ErrorResult& aRv);
 
   already_AddRefed<IDBIndex>
-  CreateIndexInternal(JSContext* aCx,
-                      const nsAString& aName,
+  CreateIndexInternal(const nsAString& aName,
                       const KeyPath& aKeyPath,
                       const IDBIndexParameters& aOptionalParameters,
                       ErrorResult& aRv);
 
   already_AddRefed<IDBRequest>
   OpenCursorInternal(bool aKeysOnly,
                      JSContext* aCx,
                      JS::Handle<JS::Value> aRange,
--- a/dom/indexedDB/KeyPath.cpp
+++ b/dom/indexedDB/KeyPath.cpp
@@ -9,16 +9,17 @@
 #include "Key.h"
 #include "ReportInternalError.h"
 
 #include "nsCharSeparatedTokenizer.h"
 #include "nsJSUtils.h"
 #include "xpcpublic.h"
 
 #include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/IDBObjectStoreBinding.h"
 
 namespace mozilla {
 namespace dom {
 namespace indexedDB {
 
 namespace {
 
 inline
@@ -26,39 +27,30 @@ bool
 IgnoreWhitespace(char16_t c)
 {
   return false;
 }
 
 typedef nsCharSeparatedTokenizerTemplate<IgnoreWhitespace> KeyPathTokenizer;
 
 bool
-IsValidKeyPathString(JSContext* aCx, const nsAString& aKeyPath)
+IsValidKeyPathString(const nsAString& aKeyPath)
 {
   NS_ASSERTION(!aKeyPath.IsVoid(), "What?");
 
   KeyPathTokenizer tokenizer(aKeyPath, '.');
 
   while (tokenizer.hasMoreTokens()) {
     nsString token(tokenizer.nextToken());
 
     if (!token.Length()) {
       return false;
     }
 
-    JS::Rooted<JS::Value> stringVal(aCx);
-    if (!xpc::StringToJsval(aCx, token, &stringVal)) {
-      return false;
-    }
-
-    NS_ASSERTION(stringVal.toString(), "This should never happen");
-    JS::Rooted<JSString*> str(aCx, stringVal.toString());
-
-    bool isIdentifier = false;
-    if (!JS_IsIdentifier(aCx, str, &isIdentifier) || !isIdentifier) {
+    if (!JS_IsIdentifier(token.get(), token.Length())) {
       return false;
     }
   }
 
   // If the very last character was a '.', the tokenizer won't give us an empty
   // token, but the keyPath is still invalid.
   if (!aKeyPath.IsEmpty() &&
       aKeyPath.CharAt(aKeyPath.Length() - 1) == '.') {
@@ -78,17 +70,17 @@ GetJSValFromKeyPathString(JSContext* aCx
                           const JS::Value& aValue,
                           const nsAString& aKeyPathString,
                           JS::Value* aKeyJSVal,
                           KeyExtractionOptions aOptions,
                           KeyPath::ExtractOrCreateKeyCallback aCallback,
                           void* aClosure)
 {
   NS_ASSERTION(aCx, "Null pointer!");
-  NS_ASSERTION(IsValidKeyPathString(aCx, aKeyPathString),
+  NS_ASSERTION(IsValidKeyPathString(aKeyPathString),
                "This will explode!");
   NS_ASSERTION(!(aCallback || aClosure) || aOptions == CreateProperties,
                "This is not allowed!");
   NS_ASSERTION(aOptions != CreateProperties || aCallback,
                "If properties are created, there must be a callback!");
 
   nsresult rv = NS_OK;
   *aKeyJSVal = aValue;
@@ -224,118 +216,83 @@ GetJSValFromKeyPathString(JSContext* aCx
   NS_ENSURE_SUCCESS(rv, rv);
   return rv;
 }
 
 } // anonymous namespace
 
 // static
 nsresult
-KeyPath::Parse(JSContext* aCx, const nsAString& aString, KeyPath* aKeyPath)
+KeyPath::Parse(const nsAString& aString, KeyPath* aKeyPath)
 {
   KeyPath keyPath(0);
   keyPath.SetType(STRING);
 
-  if (!keyPath.AppendStringWithValidation(aCx, aString)) {
+  if (!keyPath.AppendStringWithValidation(aString)) {
     return NS_ERROR_FAILURE;
   }
 
   *aKeyPath = keyPath;
   return NS_OK;
 }
 
 //static
 nsresult
-KeyPath::Parse(JSContext* aCx, const mozilla::dom::Sequence<nsString>& aStrings,
-               KeyPath* aKeyPath)
+KeyPath::Parse(const Sequence<nsString>& aStrings, KeyPath* aKeyPath)
 {
   KeyPath keyPath(0);
   keyPath.SetType(ARRAY);
 
   for (uint32_t i = 0; i < aStrings.Length(); ++i) {
-    if (!keyPath.AppendStringWithValidation(aCx, aStrings[i])) {
+    if (!keyPath.AppendStringWithValidation(aStrings[i])) {
       return NS_ERROR_FAILURE;
     }
   }
 
   *aKeyPath = keyPath;
   return NS_OK;
 }
 
 // static
 nsresult
-KeyPath::Parse(JSContext* aCx, const JS::Value& aValue_, KeyPath* aKeyPath)
+KeyPath::Parse(const Nullable<OwningStringOrStringSequence>& aValue, KeyPath* aKeyPath)
 {
-  JS::Rooted<JS::Value> aValue(aCx, aValue_);
   KeyPath keyPath(0);
 
   aKeyPath->SetType(NONEXISTENT);
 
-  // See if this is a JS array.
-  if (JS_IsArrayObject(aCx, aValue)) {
-
-    JS::Rooted<JSObject*> obj(aCx, aValue.toObjectOrNull());
-
-    uint32_t length;
-    if (!JS_GetArrayLength(aCx, obj, &length)) {
-      return NS_ERROR_FAILURE;
-    }
-
-    if (!length) {
-      return NS_ERROR_FAILURE;
-    }
-
-    keyPath.SetType(ARRAY);
-
-    for (uint32_t index = 0; index < length; index++) {
-      JS::Rooted<JS::Value> val(aCx);
-      JSString* jsstr;
-      nsAutoJSString str;
-      if (!JS_GetElement(aCx, obj, index, &val) ||
-          !(jsstr = JS::ToString(aCx, val)) ||
-          !str.init(aCx, jsstr)) {
-        return NS_ERROR_FAILURE;
-      }
-
-      if (!keyPath.AppendStringWithValidation(aCx, str)) {
-        return NS_ERROR_FAILURE;
-      }
-    }
-  }
-  // Otherwise convert it to a string.
-  else if (!aValue.isNull() && !aValue.isUndefined()) {
-    JSString* jsstr;
-    nsAutoJSString str;
-    if (!(jsstr = JS::ToString(aCx, aValue)) ||
-        !str.init(aCx, jsstr)) {
-      return NS_ERROR_FAILURE;
-    }
-
-    keyPath.SetType(STRING);
-
-    if (!keyPath.AppendStringWithValidation(aCx, str)) {
-      return NS_ERROR_FAILURE;
-    }
+  if (aValue.IsNull()) {
+    *aKeyPath = keyPath;
+    return NS_OK;
   }
 
-  *aKeyPath = keyPath;
-  return NS_OK;
+  if (aValue.Value().IsString()) {
+    return Parse(aValue.Value().GetAsString(), aKeyPath);
+  }
+
+  MOZ_ASSERT(aValue.Value().IsStringSequence());
+
+  const Sequence<nsString>& seq = aValue.Value().GetAsStringSequence();
+  if (seq.Length() == 0) {
+    return NS_ERROR_FAILURE;
+  }
+  return Parse(seq, aKeyPath);
 }
 
 void
 KeyPath::SetType(KeyPathType aType)
 {
   mType = aType;
   mStrings.Clear();
 }
 
 bool
-KeyPath::AppendStringWithValidation(JSContext* aCx, const nsAString& aString)
+KeyPath::AppendStringWithValidation(const nsAString& aString)
 {
-  if (!IsValidKeyPathString(aCx, aString)) {
+  if (!IsValidKeyPathString(aString)) {
     return false;
   }
 
   if (IsString()) {
     NS_ASSERTION(mStrings.Length() == 0, "Too many strings!");
     mStrings.AppendElement(aString);
     return true;
   }
--- a/dom/indexedDB/KeyPath.h
+++ b/dom/indexedDB/KeyPath.h
@@ -3,19 +3,23 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_indexeddb_keypath_h__
 #define mozilla_dom_indexeddb_keypath_h__
 
 #include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/Nullable.h"
 
 namespace mozilla {
 namespace dom {
+
+class OwningStringOrStringSequence;
+
 namespace indexedDB {
 
 class IndexMetadata;
 class Key;
 class ObjectStoreMetadata;
 
 class KeyPath
 {
@@ -34,18 +38,17 @@ public:
     NONEXISTENT,
     STRING,
     ARRAY,
     ENDGUARD
   };
 
   void SetType(KeyPathType aType);
 
-  // This does not set exceptions.
-  bool AppendStringWithValidation(JSContext* aCx, const nsAString& aString);
+  bool AppendStringWithValidation(const nsAString& aString);
 
   explicit KeyPath(int aDummy)
   : mType(NONEXISTENT)
   {
     MOZ_COUNT_CTOR(KeyPath);
   }
 
   KeyPath(const KeyPath& aOther)
@@ -55,23 +58,23 @@ public:
   }
 
   ~KeyPath()
   {
     MOZ_COUNT_DTOR(KeyPath);
   }
 
   static nsresult
-  Parse(JSContext* aCx, const nsAString& aString, KeyPath* aKeyPath);
+  Parse(const nsAString& aString, KeyPath* aKeyPath);
 
   static nsresult
-  Parse(JSContext* aCx, const Sequence<nsString>& aStrings, KeyPath* aKeyPath);
+  Parse(const Sequence<nsString>& aStrings, KeyPath* aKeyPath);
 
   static nsresult
-  Parse(JSContext* aCx, const JS::Value& aValue, KeyPath* aKeyPath);
+  Parse(const Nullable<OwningStringOrStringSequence>& aValue, KeyPath* aKeyPath);
 
   nsresult
   ExtractKey(JSContext* aCx, const JS::Value& aValue, Key& aKey) const;
 
   nsresult
   ExtractKeyAsJSVal(JSContext* aCx, const JS::Value& aValue,
                     JS::Value* aOutVal) const;
 
index c41c53ee7058b5a62e8cf1ee971c0da5851392e8..c19a2ef5d69c27a29ca7104e7313222960815abc
GIT binary patch
literal 54089
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
Vhj!XIG_ij;4$;uizQFz8{{wUV_^bc`
--- a/dom/interfaces/security/nsIContentSecurityPolicy.idl
+++ b/dom/interfaces/security/nsIContentSecurityPolicy.idl
@@ -13,19 +13,45 @@ interface nsIURI;
 
 /**
  * nsIContentSecurityPolicy
  * Describes an XPCOM component used to model and enforce CSPs.  Instances of
  * this class may have multiple policies within them, but there should only be
  * one of these per document/principal.
  */
 
-[scriptable, uuid(dc86b261-5e41-4cab-ace3-a0278f5a7ec7)]
+typedef unsigned short CSPDirective;
+
+[scriptable, uuid(69b7663e-117a-4a3b-81bd-d86420b7c79e)]
 interface nsIContentSecurityPolicy : nsISerializable
 {
+  /**
+   * Directives supported by Content Security Policy.  These are enums for
+   * the CSPDirective type.
+   * The NO_DIRECTIVE entry is  used for checking default permissions and
+   * returning failure when asking CSP which directive to check.
+   *
+   * NOTE: When implementing a new directive, you will need to add it here but also
+   * add it to the CSPStrDirectives array in nsCSPUtils.h.
+   */
+  const unsigned short NO_DIRECTIVE               = 0;
+  const unsigned short DEFAULT_SRC_DIRECTIVE      = 1;
+  const unsigned short SCRIPT_SRC_DIRECTIVE       = 2;
+  const unsigned short OBJECT_SRC_DIRECTIVE       = 3;
+  const unsigned short STYLE_SRC_DIRECTIVE        = 4;
+  const unsigned short IMG_SRC_DIRECTIVE          = 5;
+  const unsigned short MEDIA_SRC_DIRECTIVE        = 6;
+  const unsigned short FRAME_SRC_DIRECTIVE        = 7;
+  const unsigned short FONT_SRC_DIRECTIVE         = 8;
+  const unsigned short CONNECT_SRC_DIRECTIVE      = 9;
+  const unsigned short REPORT_URI_DIRECTIVE       = 10;
+  const unsigned short FRAME_ANCESTORS_DIRECTIVE  = 11;
+  const unsigned short REFLECTED_XSS_DIRECTIVE    = 12;
+  const unsigned short BASE_URI_DIRECTIVE         = 13;
+  const unsigned short FORM_ACTION_DIRECTIVE      = 14;
 
   /**
    * Accessor method for a read-only string version of the policy at a given
    * index.
    */
   AString getPolicy(in unsigned long index);
 
   /**
@@ -191,34 +217,39 @@ interface nsIContentSecurityPolicy : nsI
    *    containing the protected resource
    * @return
    *    true if the frame's ancestors are all allowed by policy (except for
    *    report-only policies, which will send reports and then return true
    *    here when violated).
    */
   boolean permitsAncestry(in nsIDocShell docShell);
 
-  /**
-   * Whether this policy allows setting the document's base URI to
-   * a given value.
-   *
-   * @return
-   *    Whether or not the provided URI is allowed to be used as the
-   *    document's base URI. (block the setting if false).
-   */
-  boolean permitsBaseURI(in nsIURI aURI);
 
   /**
-   * Whether this policy allows submitting HTML forms to a given URI.
+   * Checks if a specific directive permits loading of a URI.
+   *
+   * NOTE: Calls to this may trigger violation reports when queried, so the
+   * return value should not be cached.
    *
+   * @param aURI
+   *    The URI about to be loaded or used.
+   * @param aDir
+   *    The CSPDirective to query (see above constants *_DIRECTIVE).
+   * @param aSpecific
+   *    If "true" and the directive is specified to fall back to "default-src"
+   *    when it's not explicitly provided, directivePermits will NOT try
+   *    default-src when the specific directive is not used.  Setting this to
+   *    "false" allows CSP to fall back to default-src.  This function
+   *    behaves the same for both values of canUseDefault when querying
+   *    directives that don't fall-back.
    * @return
-   *    Whether or not the provided URI is allowed to be used as a
-   *    form's action URI.
+   *    Whether or not the provided URI is allowed by CSP under the given
+   *    directive. (block the pending operation if false).
    */
-  boolean permitsFormAction(in nsIURI aURI);
+  boolean permits(in nsIURI aURI, in CSPDirective aDir, in boolean aSpecific);
 
   /**
    * Delegate method called by the service when sub-elements of the protected
    * document are being loaded.  Given a bit of information about the request,
    * decides whether or not the policy is satisfied.
    *
    * Calls to this may trigger violation reports when queried, so
    * this value should not be cached.
--- a/dom/ipc/PBrowser.ipdl
+++ b/dom/ipc/PBrowser.ipdl
@@ -435,17 +435,18 @@ child:
          TextureFactoryIdentifier textureFactoryIdentifier,
          uint64_t layersId,
          nullable PRenderFrame renderFrame);
 
     LoadURL(nsCString uri);
 
     CacheFileDescriptor(nsString path, FileDescriptor fd);
 
-    UpdateDimensions(nsIntRect rect, nsIntSize size, ScreenOrientation orientation) compress;
+    UpdateDimensions(nsIntRect rect, nsIntSize size, ScreenOrientation orientation,
+                     nsIntPoint chromeDisp) compress;
 
     UpdateFrame(FrameMetrics frame);
 
     // The following methods correspond to functions on the GeckoContentController
     // interface in gfx/layers/apz/public/GeckoContentController.h. Refer to documentation
     // in that file for these functions.
     AcknowledgeScrollUpdate(ViewID aScrollId, uint32_t aScrollGeneration);
     HandleDoubleTap(CSSPoint point, ScrollableLayerGuid aGuid);
--- a/dom/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -753,17 +753,17 @@ private:
 };
 
 class TabChild::DelayedDeleteRunnable MOZ_FINAL
   : public nsRunnable
 {
     nsRefPtr<TabChild> mTabChild;
 
 public:
-    DelayedDeleteRunnable(TabChild* aTabChild)
+    explicit DelayedDeleteRunnable(TabChild* aTabChild)
       : mTabChild(aTabChild)
     {
         MOZ_ASSERT(NS_IsMainThread());
         MOZ_ASSERT(aTabChild);
     }
 
 private:
     ~DelayedDeleteRunnable()
@@ -2005,23 +2005,25 @@ TabChild::RecvShow(const nsIntSize& aSiz
 #endif
 
     bool res = InitTabChildGlobal();
     ApplyShowInfo(aInfo);
     return res;
 }
 
 bool
-TabChild::RecvUpdateDimensions(const nsIntRect& rect, const nsIntSize& size, const ScreenOrientation& orientation)
+TabChild::RecvUpdateDimensions(const nsIntRect& rect, const nsIntSize& size,
+                               const ScreenOrientation& orientation, const nsIntPoint& chromeDisp)
 {
     if (!mRemoteFrame) {
         return true;
     }
 
     mOuterRect = rect;
+    mChromeDisp = chromeDisp;
 
     bool initialSizing = !HasValidInnerSize()
                       && (size.width != 0 && size.height != 0);
     if (initialSizing) {
       mHasValidInnerSize = true;
     }
 
     mOrientation = orientation;
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -319,17 +319,18 @@ public:
     virtual bool RecvShow(const nsIntSize& aSize,
                           const ShowInfo& aInfo,
                           const ScrollingBehavior& aScrolling,
                           const TextureFactoryIdentifier& aTextureFactoryIdentifier,
                           const uint64_t& aLayersId,
                           PRenderFrameChild* aRenderFrame) MOZ_OVERRIDE;
     virtual bool RecvUpdateDimensions(const nsIntRect& rect,
                                       const nsIntSize& size,
-                                      const ScreenOrientation& orientation) MOZ_OVERRIDE;
+                                      const ScreenOrientation& orientation,
+                                      const nsIntPoint& chromeDisp) MOZ_OVERRIDE;
     virtual bool RecvUpdateFrame(const mozilla::layers::FrameMetrics& aFrameMetrics) MOZ_OVERRIDE;
     virtual bool RecvAcknowledgeScrollUpdate(const ViewID& aScrollId,
                                              const uint32_t& aScrollGeneration) MOZ_OVERRIDE;
     virtual bool RecvHandleDoubleTap(const CSSPoint& aPoint,
                                      const mozilla::layers::ScrollableLayerGuid& aGuid) MOZ_OVERRIDE;
     virtual bool RecvHandleSingleTap(const CSSPoint& aPoint,
                                      const mozilla::layers::ScrollableLayerGuid& aGuid) MOZ_OVERRIDE;
     virtual bool RecvHandleLongTap(const CSSPoint& aPoint,
@@ -490,16 +491,18 @@ public:
 
     /**
      * Native widget remoting protocol for use with windowed plugins with e10s.
      */
     PPluginWidgetChild* AllocPPluginWidgetChild() MOZ_OVERRIDE;
     bool DeallocPPluginWidgetChild(PPluginWidgetChild* aActor) MOZ_OVERRIDE;
     already_AddRefed<nsIWidget> CreatePluginWidget(nsIWidget* aParent);
 
+    nsIntPoint GetChromeDisplacement() { return mChromeDisp; };
+
 protected:
     virtual ~TabChild();
 
     virtual PRenderFrameChild* AllocPRenderFrameChild() MOZ_OVERRIDE;
     virtual bool DeallocPRenderFrameChild(PRenderFrameChild* aFrame) MOZ_OVERRIDE;
     virtual bool RecvDestroy() MOZ_OVERRIDE;
     virtual bool RecvSetUpdateHitRegion(const bool& aEnabled) MOZ_OVERRIDE;
     virtual bool RecvSetIsDocShellActive(const bool& aIsActive) MOZ_OVERRIDE;
@@ -648,16 +651,18 @@ private:
 
     bool mTouchEndCancelled;
     bool mEndTouchIsClick;
 
     bool mIgnoreKeyPressEvent;
     nsRefPtr<ActiveElementManager> mActiveElementManager;
     bool mHasValidInnerSize;
     bool mDestroyed;
+    // Position of tab, relative to parent widget (typically the window)
+    nsIntPoint mChromeDisp;
     TabId mUniqueId;
 
     DISALLOW_EVIL_CONSTRUCTORS(TabChild);
 };
 
 }
 }
 
--- a/dom/ipc/TabParent.cpp
+++ b/dom/ipc/TabParent.cpp
@@ -613,33 +613,34 @@ TabParent::Show(const nsIntSize& size)
       bool isPrivate = mFrameElement->HasAttr(kNameSpaceID_None, nsGkAtoms::mozprivatebrowsing);
       info = ShowInfo(name, allowFullscreen, isPrivate);
     }
 
     unused << SendShow(size, info, scrolling, textureFactoryIdentifier, layersId, renderFrame);
 }
 
 void
-TabParent::UpdateDimensions(const nsIntRect& rect, const nsIntSize& size)
+TabParent::UpdateDimensions(const nsIntRect& rect, const nsIntSize& size,
+                            const nsIntPoint& aChromeDisp)
 {
   if (mIsDestroyed) {
     return;
   }
   hal::ScreenConfiguration config;
   hal::GetCurrentScreenConfiguration(&config);
   ScreenOrientation orientation = config.orientation();
 
   if (!mUpdatedDimensions || mOrientation != orientation ||
       mDimensions != size || !mRect.IsEqualEdges(rect)) {
     mUpdatedDimensions = true;
     mRect = rect;
     mDimensions = size;
     mOrientation = orientation;
 
-    unused << SendUpdateDimensions(mRect, mDimensions, mOrientation);
+    unused << SendUpdateDimensions(mRect, mDimensions, mOrientation, aChromeDisp);
   }
 }
 
 void
 TabParent::UpdateFrame(const FrameMetrics& aFrameMetrics)
 {
   if (!mIsDestroyed) {
     unused << SendUpdateFrame(aFrameMetrics);
--- a/dom/ipc/TabParent.h
+++ b/dom/ipc/TabParent.h
@@ -220,17 +220,18 @@ public:
     AllocPColorPickerParent(const nsString& aTitle, const nsString& aInitialColor) MOZ_OVERRIDE;
     virtual bool DeallocPColorPickerParent(PColorPickerParent* aColorPicker) MOZ_OVERRIDE;
 
     void LoadURL(nsIURI* aURI);
     // XXX/cjones: it's not clear what we gain by hiding these
     // message-sending functions under a layer of indirection and
     // eating the return values
     void Show(const nsIntSize& size);
-    void UpdateDimensions(const nsIntRect& rect, const nsIntSize& size);
+    void UpdateDimensions(const nsIntRect& rect, const nsIntSize& size,
+                          const nsIntPoint& chromeDisp);
     void UpdateFrame(const layers::FrameMetrics& aFrameMetrics);
     void UIResolutionChanged();
     void AcknowledgeScrollUpdate(const ViewID& aScrollId, const uint32_t& aScrollGeneration);
     void HandleDoubleTap(const CSSPoint& aPoint,
                          int32_t aModifiers,
                          const ScrollableLayerGuid& aGuid);
     void HandleSingleTap(const CSSPoint& aPoint,
                          int32_t aModifiers,
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -205,9 +205,11 @@ KeyNameAppsWarning=KeyboardEvent.key val
 KeyNameFastFwdWarning=KeyboardEvent.key value "FastFwd" is obsolete and will be renamed to "MediaFastForward". For more help https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key
 # LOCALIZATION NOTE: Do not translate "KeyboardEvent.key", "Zoom" and "ZoomToggle".
 KeyNameZoomWarning=KeyboardEvent.key value "Zoom" is obsolete and will be renamed to "ZoomToggle". For more help https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key
 # LOCALIZATION NOTE: Do not translate "KeyboardEvent.key" and "Dead".
 KeyNameDeadKeysWarning=KeyboardEvent.key values starting with "Dead" are obsolete and will be merged into just "Dead". For more help https://developer.mozilla.org/en-US/docs/Web/API/KeyboardEvent.key
 ImportXULIntoContentWarning=Importing XUL nodes into a content document is deprecated. This functionality may be removed soon.
 XMLDocumentLoadPrincipalMismatch=Use of document.load forbidden on Documents that come from other Windows. Only the Window in which a Document was created is allowed to call .load on that Document. Preferably, use XMLHttpRequest instead.
 # LOCALIZATION NOTE: Do not translate "IndexedDB".
-IndexedDBTransactionAbortNavigation=An IndexedDB transaction that was not yet complete has been aborted due to page navigation.
\ No newline at end of file
+IndexedDBTransactionAbortNavigation=An IndexedDB transaction that was not yet complete has been aborted due to page navigation.
+# LOCALIZATION NOTE (WillChangeBudgetWarning): Do not translate Will-change, %1$S,%2$S,%3$S are numbers.
+WillChangeBudgetWarning=Will-change memory consumption is too high. Surface area covers %1$S pixels, budget is the document surface area multiplied by %2$S (%3$S pixels). All occurences of will-change in the document are ignored when over budget.
--- a/dom/media/MediaDataDecodedListener.h
+++ b/dom/media/MediaDataDecodedListener.h
@@ -25,47 +25,16 @@ public:
     : mMonitor("MediaDataDecodedListener")
     , mTaskQueue(aTaskQueue)
     , mTarget(aTarget)
   {
     MOZ_ASSERT(aTarget);
     MOZ_ASSERT(aTaskQueue);
   }
 
-  virtual void OnAudioDecoded(AudioData* aSample) MOZ_OVERRIDE {
-    MonitorAutoLock lock(mMonitor);
-    if (!mTarget || !mTaskQueue) {
-      // We've been shutdown, abort.
-      return;
-    }
-    RefPtr<nsIRunnable> task(new DeliverAudioTask(aSample, mTarget));
-    mTaskQueue->Dispatch(task);
-  }
-
-  virtual void OnVideoDecoded(VideoData* aSample) MOZ_OVERRIDE {
-    MonitorAutoLock lock(mMonitor);
-    if (!mTarget || !mTaskQueue) {
-      // We've been shutdown, abort.
-      return;
-    }
-    RefPtr<nsIRunnable> task(new DeliverVideoTask(aSample, mTarget));
-    mTaskQueue->Dispatch(task);
-  }
-
-  virtual void OnNotDecoded(MediaData::Type aType,
-                            MediaDecoderReader::NotDecodedReason aReason) MOZ_OVERRIDE {
-    MonitorAutoLock lock(mMonitor);
-    if (!mTarget || !mTaskQueue) {
-      // We've been shutdown, abort.
-      return;
-    }
-    RefPtr<nsIRunnable> task(new DeliverNotDecodedTask(aType, aReason, mTarget));
-    mTaskQueue->Dispatch(task);
-  }
-
   void BreakCycles() {
     MonitorAutoLock lock(mMonitor);
     mTarget = nullptr;
     mTaskQueue = nullptr;
   }
 
   virtual void OnSeekCompleted(nsresult aResult) MOZ_OVERRIDE {
     MonitorAutoLock lock(mMonitor);
@@ -77,90 +46,16 @@ public:
                                                                    &Target::OnSeekCompleted,
                                                                    aResult));
     if (NS_FAILED(mTaskQueue->Dispatch(task))) {
       NS_WARNING("Failed to dispatch OnSeekCompleted task");
     }
   }
 
 private:
-
-  class DeliverAudioTask : public nsRunnable {
-  public:
-    DeliverAudioTask(AudioData* aSample, Target* aTarget)
-      : mSample(aSample)
-      , mTarget(aTarget)
-    {
-      MOZ_COUNT_CTOR(DeliverAudioTask);
-    }
-  protected:
-    ~DeliverAudioTask()
-    {
-      MOZ_COUNT_DTOR(DeliverAudioTask);
-    }
-  public:
-    NS_METHOD Run() {
-      mTarget->OnAudioDecoded(mSample);
-      return NS_OK;
-    }
-  private:
-    nsRefPtr<AudioData> mSample;
-    RefPtr<Target> mTarget;
-  };
-
-  class DeliverVideoTask : public nsRunnable {
-  public:
-    DeliverVideoTask(VideoData* aSample, Target* aTarget)
-      : mSample(aSample)
-      , mTarget(aTarget)
-    {
-      MOZ_COUNT_CTOR(DeliverVideoTask);
-    }
-  protected:
-    ~DeliverVideoTask()
-    {
-      MOZ_COUNT_DTOR(DeliverVideoTask);
-    }
-  public:
-    NS_METHOD Run() {
-      mTarget->OnVideoDecoded(mSample);
-      return NS_OK;
-    }
-  private:
-    nsRefPtr<VideoData> mSample;
-    RefPtr<Target> mTarget;
-  };
-
-  class DeliverNotDecodedTask : public nsRunnable {
-  public:
-    DeliverNotDecodedTask(MediaData::Type aType,
-                          MediaDecoderReader::NotDecodedReason aReason,
-                          Target* aTarget)
-      : mType(aType)
-      , mReason(aReason)
-      , mTarget(aTarget)
-    {
-      MOZ_COUNT_CTOR(DeliverNotDecodedTask);
-    }
-  protected:
-    ~DeliverNotDecodedTask()
-    {
-      MOZ_COUNT_DTOR(DeliverNotDecodedTask);
-    }
-  public:
-    NS_METHOD Run() {
-      mTarget->OnNotDecoded(mType, mReason);
-      return NS_OK;
-    }
-  private:
-    MediaData::Type mType;
-    MediaDecoderReader::NotDecodedReason mReason;
-    RefPtr<Target> mTarget;
-  };
-
   Monitor mMonitor;
   RefPtr<MediaTaskQueue> mTaskQueue;
   RefPtr<Target> mTarget;
 };
 
 } /* namespace mozilla */
 
 #endif // MediaDataDecodedListener_h_
--- a/dom/media/MediaDecoderReader.cpp
+++ b/dom/media/MediaDecoderReader.cpp
@@ -68,16 +68,17 @@ public:
   size_t mSize;
 };
 
 MediaDecoderReader::MediaDecoderReader(AbstractMediaDecoder* aDecoder)
   : mAudioCompactor(mAudioQueue)
   , mDecoder(aDecoder)
   , mIgnoreAudioOutputFormat(false)
   , mStartTime(-1)
+  , mHitAudioDecodeError(false)
   , mTaskQueueIsBorrowed(false)
   , mAudioDiscontinuity(false)
   , mVideoDiscontinuity(false)
   , mShutdown(false)
 {
   MOZ_COUNT_CTOR(MediaDecoderReader);
   EnsureMediaPromiseLog();
 }
@@ -183,79 +184,88 @@ public:
     mReader->RequestVideoData(skip, mTimeThreshold);
     return NS_OK;
   }
 private:
   nsRefPtr<MediaDecoderReader> mReader;
   int64_t mTimeThreshold;
 };
 
-void
+nsRefPtr<MediaDecoderReader::VideoDataPromise>
 MediaDecoderReader::RequestVideoData(bool aSkipToNextKeyframe,
                                      int64_t aTimeThreshold)
 {
+  nsRefPtr<VideoDataPromise> p = mBaseVideoPromise.Ensure(__func__);
   bool skip = aSkipToNextKeyframe;
   while (VideoQueue().GetSize() == 0 &&
          !VideoQueue().IsFinished()) {
     if (!DecodeVideoFrame(skip, aTimeThreshold)) {
       VideoQueue().Finish();
     } else if (skip) {
       // We still need to decode more data in order to skip to the next
       // keyframe. Post another task to the decode task queue to decode
       // again. We don't just decode straight in a loop here, as that
       // would hog the decode task queue.
       RefPtr<nsIRunnable> task(new RequestVideoWithSkipTask(this, aTimeThreshold));
       mTaskQueue->Dispatch(task);
-      return;
+      return p;
     }
   }
   if (VideoQueue().GetSize() > 0) {
     nsRefPtr<VideoData> v = VideoQueue().PopFront();
     if (v && mVideoDiscontinuity) {
       v->mDiscontinuity = true;
       mVideoDiscontinuity = false;
     }
-    GetCallback()->OnVideoDecoded(v);
+    mBaseVideoPromise.Resolve(v, __func__);
   } else if (VideoQueue().IsFinished()) {
-    GetCallback()->OnNotDecoded(MediaData::VIDEO_DATA, END_OF_STREAM);
+    mBaseVideoPromise.Reject(END_OF_STREAM, __func__);
+  } else {
+    MOZ_ASSERT(false, "Dropping this promise on the floor");
   }
+
+  return p;
 }
 
-void
+nsRefPtr<MediaDecoderReader::AudioDataPromise>
 MediaDecoderReader::RequestAudioData()
 {
+  nsRefPtr<AudioDataPromise> p = mBaseAudioPromise.Ensure(__func__);
   while (AudioQueue().GetSize() == 0 &&
          !AudioQueue().IsFinished()) {
     if (!DecodeAudioData()) {
       AudioQueue().Finish();
       break;
     }
     // AudioQueue size is still zero, post a task to try again. Don't spin
     // waiting in this while loop since it somehow prevents audio EOS from
     // coming in gstreamer 1.x when there is still video buffer waiting to be
     // consumed. (|mVideoSinkBufferCount| > 0)
     if (AudioQueue().GetSize() == 0 && mTaskQueue) {
       RefPtr<nsIRunnable> task(NS_NewRunnableMethod(
           this, &MediaDecoderReader::RequestAudioData));
       mTaskQueue->Dispatch(task.forget());
-      return;
+      return p;
     }
   }
   if (AudioQueue().GetSize() > 0) {
     nsRefPtr<AudioData> a = AudioQueue().PopFront();
     if (mAudioDiscontinuity) {
       a->mDiscontinuity = true;
       mAudioDiscontinuity = false;
     }
-    GetCallback()->OnAudioDecoded(a);
-    return;
+    mBaseAudioPromise.Resolve(a, __func__);
   } else if (AudioQueue().IsFinished()) {
-    GetCallback()->OnNotDecoded(MediaData::AUDIO_DATA, END_OF_STREAM);
-    return;
+    mBaseAudioPromise.Reject(mHitAudioDecodeError ? DECODE_ERROR : END_OF_STREAM, __func__);
+    mHitAudioDecodeError = false;
+  } else {
+    MOZ_ASSERT(false, "Dropping this promise on the floor");
   }
+
+  return p;
 }
 
 void
 MediaDecoderReader::SetCallback(RequestSampleCallback* aCallback)
 {
   mSampleDecodedCallback = aCallback;
 }
 
@@ -283,16 +293,20 @@ MediaDecoderReader::BreakCycles()
   mTaskQueue = nullptr;
 }
 
 nsRefPtr<ShutdownPromise>
 MediaDecoderReader::Shutdown()
 {
   MOZ_ASSERT(OnDecodeThread());
   mShutdown = true;
+
+  mBaseAudioPromise.RejectIfExists(END_OF_STREAM, __func__);
+  mBaseVideoPromise.RejectIfExists(END_OF_STREAM, __func__);
+
   ReleaseMediaResources();
   nsRefPtr<ShutdownPromise> p;
 
   // Spin down the task queue if necessary. We wait until BreakCycles to null
   // out mTaskQueue, since otherwise any remaining tasks could crash when they
   // invoke GetTaskQueue()->IsCurrentThreadIn().
   if (mTaskQueue && !mTaskQueueIsBorrowed) {
     // If we own our task queue, shutdown ends when the task queue is done.
@@ -302,71 +316,9 @@ MediaDecoderReader::Shutdown()
     // asynchronously).
     p = new ShutdownPromise(__func__);
     p->Resolve(true, __func__);
   }
 
   return p;
 }
 
-AudioDecodeRendezvous::AudioDecodeRendezvous()
-  : mMonitor("AudioDecodeRendezvous")
-  , mHaveResult(false)
-{
-}
-
-AudioDecodeRendezvous::~AudioDecodeRendezvous()
-{
-}
-
-void
-AudioDecodeRendezvous::OnAudioDecoded(AudioData* aSample)
-{
-  MonitorAutoLock mon(mMonitor);
-  mSample = aSample;
-  mStatus = NS_OK;
-  mHaveResult = true;
-  mon.NotifyAll();
-}
-
-void
-AudioDecodeRendezvous::OnNotDecoded(MediaData::Type aType,
-                                    MediaDecoderReader::NotDecodedReason aReason)
-{
-  MOZ_ASSERT(aType == MediaData::AUDIO_DATA);
-  MonitorAutoLock mon(mMonitor);
-  mSample = nullptr;
-  mStatus = aReason == MediaDecoderReader::DECODE_ERROR ? NS_ERROR_FAILURE : NS_OK;
-  mHaveResult = true;
-  mon.NotifyAll();
-}
-
-void
-AudioDecodeRendezvous::Reset()
-{
-  MonitorAutoLock mon(mMonitor);
-  mHaveResult = false;
-  mStatus = NS_OK;
-  mSample = nullptr;
-}
-
-nsresult
-AudioDecodeRendezvous::Await(nsRefPtr<AudioData>& aSample)
-{
-  MonitorAutoLock mon(mMonitor);
-  while (!mHaveResult) {
-    mon.Wait();
-  }
-  mHaveResult = false;
-  aSample = mSample;
-  return mStatus;
-}
-
-void
-AudioDecodeRendezvous::Cancel()
-{
-  MonitorAutoLock mon(mMonitor);
-  mStatus = NS_ERROR_ABORT;
-  mHaveResult = true;
-  mon.NotifyAll();
-}
-
 } // namespace mozilla
--- a/dom/media/MediaDecoderReader.h
+++ b/dom/media/MediaDecoderReader.h
@@ -4,16 +4,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/. */
 #if !defined(MediaDecoderReader_h_)
 #define MediaDecoderReader_h_
 
 #include "AbstractMediaDecoder.h"
 #include "MediaInfo.h"
 #include "MediaData.h"
+#include "MediaPromise.h"
 #include "MediaQueue.h"
 #include "AudioCompactor.h"
 
 namespace mozilla {
 
 namespace dom {
 class TimeRanges;
 }
@@ -32,16 +33,19 @@ class MediaDecoderReader {
 public:
   enum NotDecodedReason {
     END_OF_STREAM,
     DECODE_ERROR,
     WAITING_FOR_DATA,
     CANCELED
   };
 
+  typedef MediaPromise<nsRefPtr<AudioData>, NotDecodedReason> AudioDataPromise;
+  typedef MediaPromise<nsRefPtr<VideoData>, NotDecodedReason> VideoDataPromise;
+
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaDecoderReader)
 
   explicit MediaDecoderReader(AbstractMediaDecoder* aDecoder);
 
   // Initializes the reader, returns NS_OK on success, or NS_ERROR_FAILURE
   // on failure.
   virtual nsresult Init(MediaDecoderReader* aCloneDonor) = 0;
 
@@ -88,32 +92,32 @@ public:
   // outstanding Request*Data() calls after this is called. Calls to
   // Request*Data() made after this should be processed as usual.
   // Normally this call preceedes a Seek() call, or shutdown.
   // The first samples of every stream produced after a ResetDecode() call
   // *must* be marked as "discontinuities". If it's not, seeking work won't
   // properly!
   virtual nsresult ResetDecode();
 
-  // Requests the Reader to call OnAudioDecoded() on aCallback with one
-  // audio sample. The decode should be performed asynchronously, and
-  // the callback can be performed on any thread. Don't hold the decoder
+  // Requests one audio sample from the reader.
+  //
+  // The decode should be performed asynchronously, and the promise should
+  // be resolved when it is complete. Don't hold the decoder
   // monitor while calling this, as the implementation may try to wait
   // on something that needs the monitor and deadlock.
-  virtual void RequestAudioData();
+  virtual nsRefPtr<AudioDataPromise> RequestAudioData();
 
-  // Requests the Reader to call OnVideoDecoded() on aCallback with one
-  // video sample. The decode should be performed asynchronously, and
-  // the callback can be performed on any thread. Don't hold the decoder
-  // monitor while calling this, as the implementation may try to wait
-  // on something that needs the monitor and deadlock.
+  // Requests one video sample from the reader.
+  //
+  // Don't hold the decoder monitor while calling this, as the implementation
+  // may try to wait on something that needs the monitor and deadlock.
   // If aSkipToKeyframe is true, the decode should skip ahead to the
   // the next keyframe at or after aTimeThreshold microseconds.
-  virtual void RequestVideoData(bool aSkipToNextKeyframe,
-                                int64_t aTimeThreshold);
+  virtual nsRefPtr<VideoDataPromise>
+  RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold);
 
   virtual bool HasAudio() = 0;
   virtual bool HasVideo() = 0;
 
   // A function that is called before ReadMetadata() call.
   virtual void PreReadMetadata() {};
 
   // Read header data for all bitstreams in the file. Fills aInfo with
@@ -270,20 +274,31 @@ protected:
   // what we support.
   bool mIgnoreAudioOutputFormat;
 
   // The start time of the media, in microseconds. This is the presentation
   // time of the first frame decoded from the media. This is initialized to -1,
   // and then set to a value >= by MediaDecoderStateMachine::SetStartTime(),
   // after which point it never changes.
   int64_t mStartTime;
-private:
+
+  // This is a quick-and-dirty way for DecodeAudioData implementations to
+  // communicate the presence of a decoding error to RequestAudioData. We should
+  // replace this with a promise-y mechanism as we make this stuff properly
+  // async.
+  bool mHitAudioDecodeError;
 
+private:
   nsRefPtr<RequestSampleCallback> mSampleDecodedCallback;
 
+  // Promises used only for the base-class (sync->async adapter) implementation
+  // of Request{Audio,Video}Data.
+  MediaPromiseHolder<AudioDataPromise> mBaseAudioPromise;
+  MediaPromiseHolder<VideoDataPromise> mBaseVideoPromise;
+
   nsRefPtr<MediaTaskQueue> mTaskQueue;
   bool mTaskQueueIsBorrowed;
 
   // Flags whether a the next audio/video sample comes after a "gap" or
   // "discontinuity" in the stream. For example after a seek.
   bool mAudioDiscontinuity;
   bool mVideoDiscontinuity;
   bool mShutdown;
@@ -292,64 +307,20 @@ private:
 // Interface that callers to MediaDecoderReader::Request{Audio,Video}Data()
 // must implement to receive the requested samples asynchronously.
 // This object is refcounted, and cycles must be broken by calling
 // BreakCycles() during shutdown.
 class RequestSampleCallback {
 public:
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(RequestSampleCallback)
 
-  // Receives the result of a RequestAudioData() call.
-  virtual void OnAudioDecoded(AudioData* aSample) = 0;
-
-  // Receives the result of a RequestVideoData() call.
-  virtual void OnVideoDecoded(VideoData* aSample) = 0;
-
-  // Called when a RequestAudioData() or RequestVideoData() call can't be
-  // fulfiled. The reason is passed as aReason.
-  virtual void OnNotDecoded(MediaData::Type aType,
-                            MediaDecoderReader::NotDecodedReason aReason) = 0;
-
   virtual void OnSeekCompleted(nsresult aResult) = 0;
 
   // Called during shutdown to break any reference cycles.
   virtual void BreakCycles() = 0;
 
 protected:
   virtual ~RequestSampleCallback() {}
 };
 
-// A RequestSampleCallback implementation that can be passed to the
-// MediaDecoderReader to block the thread requesting an audio sample until
-// the audio decode is complete. This is used to adapt the asynchronous
-// model of the MediaDecoderReader to a synchronous model.
-class AudioDecodeRendezvous : public RequestSampleCallback {
-public:
-  AudioDecodeRendezvous();
-  ~AudioDecodeRendezvous();
-
-  // RequestSampleCallback implementation. Called when decode is complete.
-  // Note: aSample is null at end of stream.
-  virtual void OnAudioDecoded(AudioData* aSample) MOZ_OVERRIDE;
-  virtual void OnVideoDecoded(VideoData* aSample) MOZ_OVERRIDE {}
-  virtual void OnNotDecoded(MediaData::Type aType,
-                            MediaDecoderReader::NotDecodedReason aReason) MOZ_OVERRIDE;
-  virtual void OnSeekCompleted(nsresult aResult) MOZ_OVERRIDE {};
-  virtual void BreakCycles() MOZ_OVERRIDE {};
-  void Reset();
-
-  // Returns failure on error, or NS_OK.
-  // If *aSample is null, EOS has been reached.
-  nsresult Await(nsRefPtr<AudioData>& aSample);
-
-  // Interrupts a call to Wait().
-  void Cancel();
-
-private:
-  Monitor mMonitor;
-  nsresult mStatus;
-  nsRefPtr<AudioData> mSample;
-  bool mHaveResult;
-};
-
 } // namespace mozilla
 
 #endif
--- a/dom/media/MediaDecoderStateMachine.cpp
+++ b/dom/media/MediaDecoderStateMachine.cpp
@@ -618,17 +618,20 @@ MediaDecoderStateMachine::DecodeVideo()
     currentTime = mState == DECODER_STATE_SEEKING ? 0 : GetMediaTime();
 
     // Time the video decode, so that if it's slow, we can increase our low
     // audio threshold to reduce the chance of an audio underrun while we're
     // waiting for a video decode to complete.
     mVideoDecodeStartTime = TimeStamp::Now();
   }
 
-  mReader->RequestVideoData(skipToNextKeyFrame, currentTime);
+  mReader->RequestVideoData(skipToNextKeyFrame, currentTime)
+         ->Then(DecodeTaskQueue(), __func__, this,
+                &MediaDecoderStateMachine::OnVideoDecoded,
+                &MediaDecoderStateMachine::OnVideoNotDecoded);
 }
 
 bool
 MediaDecoderStateMachine::NeedToDecodeAudio()
 {
   AssertCurrentThreadInMonitor();
   SAMPLE_LOG("NeedToDecodeAudio() isDec=%d decToTar=%d minPrl=%d seek=%d enufAud=%d",
              IsAudioDecoding(), mDecodeToSeekTarget, mMinimizePreroll,
@@ -661,17 +664,19 @@ MediaDecoderStateMachine::DecodeAudio()
     // We don't want to consider skipping to the next keyframe if we've
     // only just started up the decode loop, so wait until we've decoded
     // some audio data before enabling the keyframe skip logic on audio.
     if (mIsAudioPrerolling &&
         GetDecodedAudioDuration() >= mAudioPrerollUsecs * mPlaybackRate) {
       mIsAudioPrerolling = false;
     }
   }
-  mReader->RequestAudioData();
+  mReader->RequestAudioData()->Then(DecodeTaskQueue(), __func__, this,
+                                    &MediaDecoderStateMachine::OnAudioDecoded,
+                                    &MediaDecoderStateMachine::OnAudioNotDecoded);
 }
 
 bool
 MediaDecoderStateMachine::IsAudioSeekComplete()
 {
   AssertCurrentThreadInMonitor();
   SAMPLE_LOG("IsAudioSeekComplete() curTarVal=%d mAudDis=%d aqFin=%d aqSz=%d",
     mCurrentSeekTarget.IsValid(), mDropAudioUntilNextDiscontinuity, AudioQueue().IsFinished(), AudioQueue().GetSize());
@@ -910,16 +915,17 @@ MediaDecoderStateMachine::MaybeFinishDec
   if (NS_FAILED(FinishDecodeFirstFrame())) {
     DecodeError();
   }
 }
 
 void
 MediaDecoderStateMachine::OnVideoDecoded(VideoData* aVideoSample)
 {
+  MOZ_ASSERT(OnDecodeThread());
   ReentrantMonitorAutoEnter mon(mDecoder->GetReentrantMonitor());
   nsRefPtr<VideoData> video(aVideoSample);
   mVideoRequestPending = false;
 
   SAMPLE_LOG("OnVideoDecoded [%lld,%lld] disc=%d",
              (video ? video->mTime : -1),
              (video ? video->GetEndTime() : -1),
              (video ? video->mDiscontinuity : 0));
@@ -2123,22 +2129,27 @@ MediaDecoderStateMachine::DecodeFirstFra
     NS_ENSURE_SUCCESS(res, res);
   } else if (mDecodingFrozenAtStateMetadata) {
     SetStartTime(mStartTime);
     nsresult res = FinishDecodeFirstFrame();
     NS_ENSURE_SUCCESS(res, res);
   } else {
     if (HasAudio()) {
       ReentrantMonitorAutoExit unlock(mDecoder->GetReentrantMonitor());
-      mReader->RequestAudioData();
+      mReader->RequestAudioData()->Then(DecodeTaskQueue(), __func__, this,
+                                        &MediaDecoderStateMachine::OnAudioDecoded,
+                                        &MediaDecoderStateMachine::OnAudioNotDecoded);
     }
     if (HasVideo()) {
       mVideoDecodeStartTime = TimeStamp::Now();
       ReentrantMonitorAutoExit unlock(mDecoder->GetReentrantMonitor());
-      mReader->RequestVideoData(false, 0);
+      mReader->RequestVideoData(false, 0)
+             ->Then(DecodeTaskQueue(), __func__, this,
+                    &MediaDecoderStateMachine::OnVideoDecoded,
+                    &MediaDecoderStateMachine::OnVideoNotDecoded);
     }
   }
 
   return NS_OK;
 }
 
 nsresult
 MediaDecoderStateMachine::FinishDecodeFirstFrame()
--- a/dom/media/MediaDecoderStateMachine.h
+++ b/dom/media/MediaDecoderStateMachine.h
@@ -371,16 +371,27 @@ public:
   // the state machine is free to return to prerolling normally. Note
   // "prerolling" in this context refers to when we decode and buffer decoded
   // samples in advance of when they're needed for playback.
   void SetMinimizePrerollUntilPlaybackStarts();
 
   void OnAudioDecoded(AudioData* aSample);
   void OnVideoDecoded(VideoData* aSample);
   void OnNotDecoded(MediaData::Type aType, MediaDecoderReader::NotDecodedReason aReason);
+  void OnAudioNotDecoded(MediaDecoderReader::NotDecodedReason aReason)
+  {
+    MOZ_ASSERT(OnDecodeThread());
+    OnNotDecoded(MediaData::AUDIO_DATA, aReason);
+  }
+  void OnVideoNotDecoded(MediaDecoderReader::NotDecodedReason aReason)
+  {
+    MOZ_ASSERT(OnDecodeThread());
+    OnNotDecoded(MediaData::VIDEO_DATA, aReason);
+  }
+
   void OnSeekCompleted(nsresult aResult);
 
 private:
   void AcquireMonitorAndInvokeDecodeError();
 
 protected:
   virtual ~MediaDecoderStateMachine();
 
--- a/dom/media/MediaDevices.h
+++ b/dom/media/MediaDevices.h
@@ -19,17 +19,18 @@ struct MediaStreamConstraints;
 
 #define MOZILLA_DOM_MEDIADEVICES_IMPLEMENTATION_IID \
 { 0x2f784d8a, 0x7485, 0x4280, \
  { 0x9a, 0x36, 0x74, 0xa4, 0xd6, 0x71, 0xa6, 0xc8 } }
 
 class MediaDevices MOZ_FINAL : public DOMEventTargetHelper
 {
 public:
-  MediaDevices(nsPIDOMWindow* aWindow) : DOMEventTargetHelper(aWindow) {}
+  explicit MediaDevices(nsPIDOMWindow* aWindow) :
+    DOMEventTargetHelper(aWindow) {}
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECLARE_STATIC_IID_ACCESSOR(MOZILLA_DOM_MEDIADEVICES_IMPLEMENTATION_IID)
 
   JSObject* WrapObject(JSContext* cx) MOZ_OVERRIDE;
 
   already_AddRefed<Promise>
   GetUserMedia(const MediaStreamConstraints& aConstraints, ErrorResult &aRv);
--- a/dom/media/MediaPromise.h
+++ b/dom/media/MediaPromise.h
@@ -21,17 +21,16 @@
 #ifdef _MSC_VER
 #define __func__ __FUNCTION__
 #endif
 
 class nsIEventTarget;
 namespace mozilla {
 
 extern PRLogModuleInfo* gMediaPromiseLog;
-void EnsureMediaPromiseLog();
 
 #define PROMISE_LOG(x, ...) \
   MOZ_ASSERT(gMediaPromiseLog); \
   PR_LOG(gMediaPromiseLog, PR_LOG_DEBUG, (x, ##__VA_ARGS__))
 
 class MediaTaskQueue;
 namespace detail {
 
@@ -53,17 +52,17 @@ template<typename T> class MediaPromiseH
 template<typename ResolveValueT, typename RejectValueT>
 class MediaPromise
 {
 public:
   typedef ResolveValueT ResolveValueType;
   typedef RejectValueT RejectValueType;
 
   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(MediaPromise)
-  MediaPromise(const char* aCreationSite)
+  explicit MediaPromise(const char* aCreationSite)
     : mCreationSite(aCreationSite)
     , mMutex("MediaPromise Mutex")
   {
     MOZ_COUNT_CTOR(MediaPromise);
     PROMISE_LOG("%s creating MediaPromise (%p)", mCreationSite, this);
   }
 
 protected:
@@ -134,17 +133,17 @@ protected:
         return NS_OK;
       }
 
     private:
       ThenValueBase* mThenValue;
       RejectValueType mRejectValue;
     };
 
-    ThenValueBase(const char* aCallSite) : mCallSite(aCallSite)
+    explicit ThenValueBase(const char* aCallSite) : mCallSite(aCallSite)
     {
       MOZ_COUNT_CTOR(ThenValueBase);
     }
 
     virtual void Dispatch(MediaPromise *aPromise) = 0;
 
   protected:
     // This may only be deleted by {Resolve,Reject}Runnable::Run.
@@ -256,18 +255,19 @@ public:
     DispatchAll();
   }
 
 protected:
   bool IsPending() { return mResolveValue.isNothing() && mRejectValue.isNothing(); }
   void DispatchAll()
   {
     mMutex.AssertCurrentThreadOwns();
-    for (size_t i = 0; i < mThenValues.Length(); ++i)
+    for (size_t i = 0; i < mThenValues.Length(); ++i) {
       mThenValues[i]->Dispatch(this);
+    }
     mThenValues.Clear();
 
     for (size_t i = 0; i < mChainedPromises.Length(); ++i) {
       ForwardTo(mChainedPromises[i]);
     }
     mChainedPromises.Clear();
   }
 
--- a/dom/media/MediaStreamError.h
+++ b/dom/media/MediaStreamError.h
@@ -39,19 +39,19 @@ protected:
   nsString mMessage;
   const nsString mConstraintName;
 };
 
 class MediaMgrError MOZ_FINAL : public nsISupports,
                                 public BaseMediaMgrError
 {
 public:
-  MediaMgrError(const nsAString& aName,
-                const nsAString& aMessage =  EmptyString(),
-                const nsAString& aConstraintName =  EmptyString())
+  explicit MediaMgrError(const nsAString& aName,
+                         const nsAString& aMessage =  EmptyString(),
+                         const nsAString& aConstraintName =  EmptyString())
   : BaseMediaMgrError(aName, aMessage, aConstraintName) {}
 
   NS_DECL_THREADSAFE_ISUPPORTS
 
 private:
   ~MediaMgrError() {}
 };
 
--- a/dom/media/MediaStreamGraph.h
+++ b/dom/media/MediaStreamGraph.h
@@ -1206,17 +1206,17 @@ public:
   }
 
   /**
    * Returns graph sample rate in Hz.
    */
   TrackRate GraphRate() const { return mSampleRate; }
 
 protected:
-  MediaStreamGraph(TrackRate aSampleRate)
+  explicit MediaStreamGraph(TrackRate aSampleRate)
     : mNextGraphUpdateIndex(1)
     , mSampleRate(aSampleRate)
   {
     MOZ_COUNT_CTOR(MediaStreamGraph);
   }
   virtual ~MediaStreamGraph()
   {
     MOZ_COUNT_DTOR(MediaStreamGraph);
--- a/dom/media/MediaTaskQueue.h
+++ b/dom/media/MediaTaskQueue.h
@@ -87,17 +87,18 @@ private:
 
   // Monitor that protects the queue and mIsRunning;
   Monitor mQueueMonitor;
 
   struct TaskQueueEntry {
     RefPtr<nsIRunnable> mRunnable;
     bool mForceDispatch;
 
-    TaskQueueEntry(TemporaryRef<nsIRunnable> aRunnable, bool aForceDispatch = false)
+    explicit TaskQueueEntry(TemporaryRef<nsIRunnable> aRunnable,
+                            bool aForceDispatch = false)
       : mRunnable(aRunnable), mForceDispatch(aForceDispatch) {}
   };
 
   // Queue of tasks to run.
   std::queue<TaskQueueEntry> mTasks;
 
   // The thread currently running the task queue. We store a reference
   // to this so that IsCurrentThreadIn() can tell if the current thread
--- a/dom/media/eme/CDMCaps.cpp
+++ b/dom/media/eme/CDMCaps.cpp
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "CDMCaps.h"
 #include "gmp-decryption.h"
 #include "EMELog.h"
 #include "nsThreadUtils.h"
+#include "SamplesWaitingForKey.h"
 
 namespace mozilla {
 
 CDMCaps::CDMCaps()
   : mMonitor("CDMCaps")
   , mCaps(0)
 {
 }
@@ -98,22 +99,17 @@ CDMCaps::AutoLock::SetKeyUsable(const Ce
     return false;
   }
   mData.mUsableKeyIds.AppendElement(key);
   auto& waiters = mData.mWaitForKeys;
   size_t i = 0;
   while (i < waiters.Length()) {
     auto& w = waiters[i];
     if (w.mKeyId == aKeyId) {
-      if (waiters[i].mTarget) {
-        EME_LOG("SetKeyUsable() notified waiter.");
-        w.mTarget->Dispatch(w.mContinuation, NS_DISPATCH_NORMAL);
-      } else {
-        w.mContinuation->Run();
-      }
+      w.mListener->NotifyUsable(aKeyId);
       waiters.RemoveElementAt(i);
     } else {
       i++;
     }
   }
   return true;
 }
 
@@ -133,39 +129,23 @@ CDMCaps::AutoLock::SetKeyUnusable(const 
       keys.RemoveElementAt(i);
       break;
     }
   }
   return true;
 }
 
 void
-CDMCaps::AutoLock::CallWhenKeyUsable(const CencKeyId& aKey,
-                                     nsIRunnable* aContinuation,
-                                     nsIThread* aTarget)
+CDMCaps::AutoLock::NotifyWhenKeyIdUsable(const CencKeyId& aKey,
+                                         SamplesWaitingForKey* aListener)
 {
   mData.mMonitor.AssertCurrentThreadOwns();
   MOZ_ASSERT(!IsKeyUsable(aKey));
-  MOZ_ASSERT(aContinuation);
-  mData.mWaitForKeys.AppendElement(WaitForKeys(aKey, aContinuation, aTarget));
-}
-
-void
-CDMCaps::AutoLock::DropKeysForSession(const nsAString& aSessionId)
-{
-  mData.mMonitor.AssertCurrentThreadOwns();
-  auto& keys = mData.mUsableKeyIds;
-  size_t i = 0;
-  while (i < keys.Length()) {
-    if (keys[i].mSessionId == aSessionId) {
-      keys.RemoveElementAt(i);
-    } else {
-      i++;
-    }
-  }
+  MOZ_ASSERT(aListener);
+  mData.mWaitForKeys.AppendElement(WaitForKeys(aKey, aListener));
 }
 
 bool
 CDMCaps::AutoLock::AreCapsKnown()
 {
   mData.mMonitor.AssertCurrentThreadOwns();
   return mData.mCaps != 0;
 }
--- a/dom/media/eme/CDMCaps.h
+++ b/dom/media/eme/CDMCaps.h
@@ -8,21 +8,20 @@
 #define CDMCaps_h_
 
 #include "nsString.h"
 #include "nsAutoPtr.h"
 #include "mozilla/Monitor.h"
 #include "nsIThread.h"
 #include "nsTArray.h"
 #include "mozilla/Attributes.h"
+#include "SamplesWaitingForKey.h"
 
 namespace mozilla {
 
-typedef nsTArray<uint8_t> CencKeyId;
-
 // CDM capabilities; what keys a CDMProxy can use, and whether it can decrypt, or
 // decrypt-and-decode on a per stream basis. Must be locked to access state.
 class CDMCaps {
 public:
   CDMCaps();
   ~CDMCaps();
 
   // Locks the CDMCaps. It must be locked to access its shared state.
@@ -41,60 +40,52 @@ public:
     // Returns true if setting this key usable results in the usable keys
     // changing for this session, i.e. the key was not previously marked usable.
     bool SetKeyUsable(const CencKeyId& aKeyId, const nsString& aSessionId);
 
     // Returns true if setting this key unusable results in the usable keys
     // changing for this session, i.e. the key was previously marked usable.
     bool SetKeyUnusable(const CencKeyId& aKeyId, const nsString& aSessionId);
 
-    void DropKeysForSession(const nsAString& aSessionId);
     void GetUsableKeysForSession(const nsAString& aSessionId,
                                  nsTArray<CencKeyId>& aOutKeyIds);
 
     // Sets the capabilities of the CDM. aCaps is the logical OR of the
     // GMP_EME_CAP_* flags from gmp-decryption.h.
     void SetCaps(uint64_t aCaps);
 
     bool CanDecryptAndDecodeAudio();
     bool CanDecryptAndDecodeVideo();
 
     bool CanDecryptAudio();
     bool CanDecryptVideo();
 
     void CallOnMainThreadWhenCapsAvailable(nsIRunnable* aContinuation);
 
-    // Calls aContinuation on aTarget thread when key become usable.
-    // Pass aTarget=nullptr and runnable will be called on the GMP thread
-    // when key becomes usable.
-    void CallWhenKeyUsable(const CencKeyId& aKey,
-                           nsIRunnable* aContinuation,
-                           nsIThread* aTarget = nullptr);
-
+    // Notifies the SamplesWaitingForKey when key become usable.
+    void NotifyWhenKeyIdUsable(const CencKeyId& aKey,
+                               SamplesWaitingForKey* aSamplesWaiting);
   private:
     // Not taking a strong ref, since this should be allocated on the stack.
     CDMCaps& mData;
   };
 
 private:
   void Lock();
   void Unlock();
   bool HasCap(uint64_t);
 
   struct WaitForKeys {
     WaitForKeys(const CencKeyId& aKeyId,
-                nsIRunnable* aContinuation,
-                nsIThread* aTarget)
+                SamplesWaitingForKey* aListener)
       : mKeyId(aKeyId)
-      , mContinuation(aContinuation)
-      , mTarget(aTarget)
+      , mListener(aListener)
     {}
     CencKeyId mKeyId;
-    nsRefPtr<nsIRunnable> mContinuation;
-    nsCOMPtr<nsIThread> mTarget;
+    nsRefPtr<SamplesWaitingForKey> mListener;
   };
 
   Monitor mMonitor;
 
   struct UsableKey {
     UsableKey(const CencKeyId& aId,
               const nsString& aSessionId)
       : mId(aId)
--- a/dom/media/eme/CDMProxy.cpp
+++ b/dom/media/eme/CDMProxy.cpp
@@ -266,20 +266,16 @@ CDMProxy::gmp_UpdateSession(nsAutoPtr<Up
 
 void
 CDMProxy::CloseSession(const nsAString& aSessionId,
                        PromiseId aPromiseId)
 {
   MOZ_ASSERT(NS_IsMainThread());
   NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
 
-  {
-    CDMCaps::AutoLock caps(Capabilites());
-    caps.DropKeysForSession(aSessionId);
-  }
   nsAutoPtr<SessionOpData> data(new SessionOpData());
   data->mPromiseId = aPromiseId;
   data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
   nsRefPtr<nsIRunnable> task(
     NS_NewRunnableMethodWithArg<nsAutoPtr<SessionOpData>>(this, &CDMProxy::gmp_CloseSession, data));
   mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
 }
 
@@ -296,20 +292,16 @@ CDMProxy::gmp_CloseSession(nsAutoPtr<Ses
 
 void
 CDMProxy::RemoveSession(const nsAString& aSessionId,
                         PromiseId aPromiseId)
 {
   MOZ_ASSERT(NS_IsMainThread());
   NS_ENSURE_TRUE_VOID(!mKeys.IsNull());
 
-  {
-    CDMCaps::AutoLock caps(Capabilites());
-    caps.DropKeysForSession(aSessionId);
-  }
   nsAutoPtr<SessionOpData> data(new SessionOpData());
   data->mPromiseId = aPromiseId;
   data->mSessionId = NS_ConvertUTF16toUTF8(aSessionId);
   nsRefPtr<nsIRunnable> task(
     NS_NewRunnableMethodWithArg<nsAutoPtr<SessionOpData>>(this, &CDMProxy::gmp_RemoveSession, data));
   mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
 }
 
@@ -326,28 +318,30 @@ CDMProxy::gmp_RemoveSession(nsAutoPtr<Se
 
 void
 CDMProxy::Shutdown()
 {
   MOZ_ASSERT(NS_IsMainThread());
   mKeys.Clear();
   // Note: This may end up being the last owning reference to the CDMProxy.
   nsRefPtr<nsIRunnable> task(NS_NewRunnableMethod(this, &CDMProxy::gmp_Shutdown));
-  mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
+  if (mGMPThread) {
+    mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
+  }
 }
 
 void
 CDMProxy::gmp_Shutdown()
 {
   MOZ_ASSERT(IsOnGMPThread());
 
   // Abort any pending decrypt jobs, to awaken any clients waiting on a job.
   for (size_t i = 0; i < mDecryptionJobs.Length(); i++) {
     DecryptJob* job = mDecryptionJobs[i];
-    job->mClient->Decrypted(NS_ERROR_ABORT, nullptr);
+    job->mClient->Decrypted(GMPAbortedErr, nullptr);
   }
   mDecryptionJobs.Clear();
 
   if (mCDM) {
     mCDM->Close();
     mCDM = nullptr;
   }
 }
@@ -445,17 +439,23 @@ CDMProxy::OnExpirationChange(const nsASt
   MOZ_ASSERT(NS_IsMainThread());
   NS_WARNING("CDMProxy::OnExpirationChange() not implemented");
 }
 
 void
 CDMProxy::OnSessionClosed(const nsAString& aSessionId)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  NS_WARNING("CDMProxy::OnSessionClosed() not implemented");
+  if (mKeys.IsNull()) {
+    return;
+  }
+  nsRefPtr<dom::MediaKeySession> session(mKeys->GetSession(aSessionId));
+  if (session) {
+    session->OnClosed();
+  }
 }
 
 static void
 LogToConsole(const nsAString& aMsg)
 {
   nsCOMPtr<nsIConsoleService> console(
     do_GetService("@mozilla.org/consoleservice;1"));
   if (!console) {
@@ -517,17 +517,17 @@ CDMProxy::Decrypt(mp4_demuxer::MP4Sample
 void
 CDMProxy::gmp_Decrypt(nsAutoPtr<DecryptJob> aJob)
 {
   MOZ_ASSERT(IsOnGMPThread());
   MOZ_ASSERT(aJob->mClient);
   MOZ_ASSERT(aJob->mSample);
 
   if (!mCDM) {
-    aJob->mClient->Decrypted(NS_ERROR_FAILURE, nullptr);
+    aJob->mClient->Decrypted(GMPAbortedErr, nullptr);
     return;
   }
 
   aJob->mId = ++mDecryptionJobCount;
   nsTArray<uint8_t> data;
   data.AppendElements(aJob->mSample->data, aJob->mSample->size);
   mCDM->Decrypt(aJob->mId, aJob->mSample->crypto, data);
   mDecryptionJobs.AppendElement(aJob.forget());
@@ -544,29 +544,36 @@ CDMProxy::gmp_Decrypted(uint32_t aId,
     if (job->mId == aId) {
       if (aDecryptedData.Length() != job->mSample->size) {
         NS_WARNING("CDM returned incorrect number of decrypted bytes");
       }
       if (GMP_SUCCEEDED(aResult)) {
         PodCopy(job->mSample->data,
                 aDecryptedData.Elements(),
                 std::min<size_t>(aDecryptedData.Length(), job->mSample->size));
-        job->mClient->Decrypted(NS_OK, job->mSample.forget());
+        job->mClient->Decrypted(GMPNoErr, job->mSample.forget());
+      } else if (aResult == GMPNoKeyErr) {
+        NS_WARNING("CDM returned GMPNoKeyErr");
+        // We still have the encrypted sample, so we can re-enqueue it to be
+        // decrypted again once the key is usable again.
+        job->mClient->Decrypted(GMPNoKeyErr, job->mSample.forget());
       } else {
-        job->mClient->Decrypted(NS_ERROR_FAILURE, nullptr);
+        nsAutoCString str("CDM returned decode failure GMPErr=");
+        str.AppendInt(aResult);
+        NS_WARNING(str.get());
+        job->mClient->Decrypted(aResult, nullptr);
       }
       mDecryptionJobs.RemoveElementAt(i);
       return;
-    } else {
-      NS_WARNING("GMPDecryptorChild returned incorrect job ID");
     }
   }
+  NS_WARNING("GMPDecryptorChild returned incorrect job ID");
 }
 
 void
 CDMProxy::gmp_Terminated()
 {
   MOZ_ASSERT(IsOnGMPThread());
-  EME_LOG("CDM terminated");
+  NS_WARNING("CDM terminated");
   gmp_Shutdown();
 }
 
 } // namespace mozilla
--- a/dom/media/eme/CDMProxy.h
+++ b/dom/media/eme/CDMProxy.h
@@ -22,17 +22,17 @@ class CDMCallbackProxy;
 
 namespace dom {
 class MediaKeySession;
 }
 
 class DecryptionClient {
 public:
   virtual ~DecryptionClient() {}
-  virtual void Decrypted(nsresult aResult,
+  virtual void Decrypted(GMPErr aResult,
                          mp4_demuxer::MP4Sample* aSample) = 0;
 };
 
 // Proxies calls GMP/CDM, and proxies calls back.
 // Note: Promises are passed in via a PromiseId, so that the ID can be
 // passed via IPC to the CDM, which can then signal when to reject or
 // resolve the promise using its PromiseId.
 class CDMProxy {
--- a/dom/media/eme/MediaKeySession.cpp
+++ b/dom/media/eme/MediaKeySession.cpp
@@ -199,17 +199,16 @@ MediaKeySession::Close(ErrorResult& aRv)
 
 void
 MediaKeySession::OnClosed()
 {
   if (IsClosed()) {
     return;
   }
   mIsClosed = true;
-  // TODO: reset usableKeyIds
   mKeys->OnSessionClosed(this);
   mKeys = nullptr;
   mClosed->MaybeResolve(JS::UndefinedHandleValue);
 }
 
 bool
 MediaKeySession::IsClosed() const
 {
@@ -286,23 +285,15 @@ MediaKeySession::DispatchKeyError(uint32
 }
 
 void
 MediaKeySession::DispatchKeysChange()
 {
   if (IsClosed()) {
     return;
   }
-  DebugOnly<nsresult> rv =
-    nsContentUtils::DispatchTrustedEvent(mKeys->GetOwnerDoc(),
-                                         this,
-                                         NS_LITERAL_STRING("keyschange"),
-                                         false,
-                                         false);
-#ifdef DEBUG
-  if (NS_FAILED(rv)) {
-    NS_WARNING("Failed to dispatch keyschange event");
-  }
-#endif
+  nsRefPtr<AsyncEventDispatcher> asyncDispatcher =
+    new AsyncEventDispatcher(this, NS_LITERAL_STRING("keyschange"), false);
+  asyncDispatcher->PostDOMEvent();
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/eme/MediaKeys.cpp
+++ b/dom/media/eme/MediaKeys.cpp
@@ -459,16 +459,10 @@ CopyArrayBufferViewOrArrayBufferData(con
     bufferview.ComputeLengthAndData();
     aOutData.AppendElements(bufferview.Data(), bufferview.Length());
   } else {
     return false;
   }
   return true;
 }
 
-nsIDocument*
-MediaKeys::GetOwnerDoc() const
-{
-  return mElement ? mElement->OwnerDoc() : nullptr;
-}
-
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/eme/MediaKeys.h
+++ b/dom/media/eme/MediaKeys.h
@@ -106,20 +106,16 @@ public:
 
   // Returns true if this MediaKeys has been bound to a media element.
   bool IsBoundToMediaElement() const;
 
   // Return NS_OK if the principals are the same as when the MediaKeys
   // was created, failure otherwise.
   nsresult CheckPrincipals();
 
-  // Returns a pointer to the bound media element's owner doc.
-  // If we're not bound, this returns null.
-  nsIDocument* GetOwnerDoc() const;
-
 private:
 
   bool IsInPrivateBrowsing();
 
   // Removes promise from mPromises, and returns it.
   already_AddRefed<Promise> RetrievePromise(PromiseId aId);
 
   // Owning ref to proxy. The proxy has a weak reference back to the MediaKeys,
--- a/dom/media/fmp4/MP4Reader.cpp
+++ b/dom/media/fmp4/MP4Reader.cpp
@@ -116,18 +116,18 @@ public:
   }
 
 private:
   RefPtr<MediaResource> mResource;
 };
 
 MP4Reader::MP4Reader(AbstractMediaDecoder* aDecoder)
   : MediaDecoderReader(aDecoder)
-  , mAudio("MP4 audio decoder data", Preferences::GetUint("media.mp4-audio-decode-ahead", 2))
-  , mVideo("MP4 video decoder data", Preferences::GetUint("media.mp4-video-decode-ahead", 2))
+  , mAudio(MediaData::AUDIO_DATA, Preferences::GetUint("media.mp4-audio-decode-ahead", 2))
+  , mVideo(MediaData::VIDEO_DATA, Preferences::GetUint("media.mp4-video-decode-ahead", 2))
   , mLastReportedNumDecodedFrames(0)
   , mLayersBackendType(layers::LayersBackend::LAYERS_NONE)
   , mDemuxerInitialized(false)
   , mIsEncrypted(false)
   , mIndexReady(false)
   , mIndexMonitor("MP4 index")
 {
   MOZ_ASSERT(NS_IsMainThread(), "Must be on main thread.");
@@ -149,26 +149,31 @@ MP4Reader::Shutdown()
     mAudio.mDecoder->Shutdown();
     mAudio.mDecoder = nullptr;
   }
   if (mAudio.mTaskQueue) {
     mAudio.mTaskQueue->BeginShutdown();
     mAudio.mTaskQueue->AwaitShutdownAndIdle();
     mAudio.mTaskQueue = nullptr;
   }
+  mAudio.mPromise.SetMonitor(nullptr);
+  MOZ_ASSERT(mAudio.mPromise.IsEmpty());
+
   if (mVideo.mDecoder) {
     Flush(kVideo);
     mVideo.mDecoder->Shutdown();
     mVideo.mDecoder = nullptr;
   }
   if (mVideo.mTaskQueue) {
     mVideo.mTaskQueue->BeginShutdown();
     mVideo.mTaskQueue->AwaitShutdownAndIdle();
     mVideo.mTaskQueue = nullptr;
   }
+  mVideo.mPromise.SetMonitor(nullptr);
+  MOZ_ASSERT(mVideo.mPromise.IsEmpty());
   // Dispose of the queued sample before shutting down the demuxer
   mQueuedVideoSample = nullptr;
 
   if (mPlatform) {
     mPlatform->Shutdown();
     mPlatform = nullptr;
   }
 
@@ -487,61 +492,70 @@ MP4Reader::HasVideo()
 {
   return mVideo.mActive;
 }
 
 MP4Reader::DecoderData&
 MP4Reader::GetDecoderData(TrackType aTrack)
 {
   MOZ_ASSERT(aTrack == kAudio || aTrack == kVideo);
-  return (aTrack == kAudio) ? mAudio : mVideo;
+  if (aTrack == kAudio) {
+    return mAudio;
+  }
+  return mVideo;
 }
 
-void
+nsRefPtr<MediaDecoderReader::VideoDataPromise>
 MP4Reader::RequestVideoData(bool aSkipToNextKeyframe,
                             int64_t aTimeThreshold)
 {
   MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
   VLOG("RequestVideoData skip=%d time=%lld", aSkipToNextKeyframe, aTimeThreshold);
 
   // Record number of frames decoded and parsed. Automatically update the
   // stats counters using the AutoNotifyDecoded stack-based class.
   uint32_t parsed = 0, decoded = 0;
   AbstractMediaDecoder::AutoNotifyDecoded autoNotify(mDecoder, parsed, decoded);
 
   MOZ_ASSERT(HasVideo() && mPlatform && mVideo.mDecoder);
 
+  bool eos = false;
   if (aSkipToNextKeyframe) {
-    if (!SkipVideoDemuxToNextKeyFrame(aTimeThreshold, parsed) ||
-        NS_FAILED(mVideo.mDecoder->Flush())) {
+    eos = !SkipVideoDemuxToNextKeyFrame(aTimeThreshold, parsed);
+    if (!eos && NS_FAILED(mVideo.mDecoder->Flush())) {
       NS_WARNING("Failed to skip/flush video when skipping-to-next-keyframe.");
     }
   }
 
-  auto& decoder = GetDecoderData(kVideo);
-  MonitorAutoLock lock(decoder.mMonitor);
-  decoder.mOutputRequested = true;
-  ScheduleUpdate(kVideo);
+  MonitorAutoLock lock(mVideo.mMonitor);
+  nsRefPtr<VideoDataPromise> p = mVideo.mPromise.Ensure(__func__);
+  if (eos) {
+    mVideo.mPromise.Reject(END_OF_STREAM, __func__);
+  } else {
+    ScheduleUpdate(kVideo);
+  }
 
   // Report the number of "decoded" frames as the difference in the
   // mNumSamplesOutput field since the last time we were called.
   uint64_t delta = mVideo.mNumSamplesOutput - mLastReportedNumDecodedFrames;
   decoded = static_cast<uint32_t>(delta);
   mLastReportedNumDecodedFrames = mVideo.mNumSamplesOutput;
+
+  return p;
 }
 
-void
+nsRefPtr<MediaDecoderReader::AudioDataPromise>
 MP4Reader::RequestAudioData()
 {
   MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
   VLOG("RequestAudioData");
-  auto& decoder = GetDecoderData(kAudio);
-  MonitorAutoLock lock(decoder.mMonitor);
-  decoder.mOutputRequested = true;
+  MonitorAutoLock lock(mAudio.mMonitor);
+  nsRefPtr<AudioDataPromise> p = mAudio.mPromise.Ensure(__func__);
   ScheduleUpdate(kAudio);
+  return p;
 }
 
 void
 MP4Reader::ScheduleUpdate(TrackType aTrack)
 {
   auto& decoder = GetDecoderData(aTrack);
   decoder.mMonitor.AssertCurrentThreadOwns();
   if (decoder.mUpdateScheduled) {
@@ -561,115 +575,101 @@ MP4Reader::NeedInput(DecoderData& aDecod
   // We try to keep a few more compressed samples input than decoded samples
   // have been output, provided the state machine has requested we send it a
   // decoded sample. To account for H.264 streams which may require a longer
   // run of input than we input, decoders fire an "input exhausted" callback,
   // which overrides our "few more samples" threshold.
   return
     !aDecoder.mError &&
     !aDecoder.mDemuxEOS &&
-    aDecoder.mOutputRequested &&
+    aDecoder.HasPromise() &&
     aDecoder.mOutput.IsEmpty() &&
     (aDecoder.mInputExhausted ||
      aDecoder.mNumSamplesInput - aDecoder.mNumSamplesOutput < aDecoder.mDecodeAhead);
 }
 
 void
 MP4Reader::Update(TrackType aTrack)
 {
   MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
 
   bool needInput = false;
   bool needOutput = false;
-  bool eos = false;
   auto& decoder = GetDecoderData(aTrack);
-  nsRefPtr<MediaData> output;
   {
     MonitorAutoLock lock(decoder.mMonitor);
     decoder.mUpdateScheduled = false;
     if (NeedInput(decoder)) {
       needInput = true;
       decoder.mInputExhausted = false;
       decoder.mNumSamplesInput++;
     }
-    needOutput = decoder.mOutputRequested;
-    if (needOutput && !decoder.mOutput.IsEmpty()) {
-      output = decoder.mOutput[0];
-      decoder.mOutput.RemoveElementAt(0);
+    if (decoder.HasPromise()) {
+      needOutput = true;
+      if (!decoder.mOutput.IsEmpty()) {
+        nsRefPtr<MediaData> output = decoder.mOutput[0];
+        decoder.mOutput.RemoveElementAt(0);
+        ReturnOutput(output, aTrack);
+      } else if (decoder.mDrainComplete) {
+        decoder.RejectPromise(END_OF_STREAM, __func__);
+      }
     }
-    eos = decoder.mDrainComplete;
   }
-  VLOG("Update(%s) ni=%d no=%d iex=%d or=%d fl=%d",
+
+  VLOG("Update(%s) ni=%d no=%d iex=%d fl=%d",
        TrackTypeToStr(aTrack),
        needInput,
        needOutput,
        decoder.mInputExhausted,
-       decoder.mOutputRequested,
        decoder.mIsFlushing);
 
   if (needInput) {
     MP4Sample* sample = PopSample(aTrack);
     if (sample) {
       decoder.mDecoder->Input(sample);
     } else {
       {
         MonitorAutoLock lock(decoder.mMonitor);
         MOZ_ASSERT(!decoder.mDemuxEOS);
         decoder.mDemuxEOS = true;
       }
       // DrainComplete takes care of reporting EOS upwards
       decoder.mDecoder->Drain();
     }
   }
-  if (needOutput) {
-    if (output) {
-      ReturnOutput(output, aTrack);
-    } else if (eos) {
-      ReturnEOS(aTrack);
-    }
-  }
 }
 
 void
 MP4Reader::ReturnOutput(MediaData* aData, TrackType aTrack)
 {
   auto& decoder = GetDecoderData(aTrack);
-  {
-    MonitorAutoLock lock(decoder.mMonitor);
-    MOZ_ASSERT(decoder.mOutputRequested);
-    decoder.mOutputRequested = false;
-    if (decoder.mDiscontinuity) {
-      decoder.mDiscontinuity = false;
-      aData->mDiscontinuity = true;
-    }
+  decoder.mMonitor.AssertCurrentThreadOwns();
+  MOZ_ASSERT(decoder.HasPromise());
+  if (decoder.mDiscontinuity) {
+    decoder.mDiscontinuity = false;
+    aData->mDiscontinuity = true;
   }
 
   if (aTrack == kAudio) {
     AudioData* audioData = static_cast<AudioData*>(aData);
 
     if (audioData->mChannels != mInfo.mAudio.mChannels ||
         audioData->mRate != mInfo.mAudio.mRate) {
       LOG("MP4Reader::ReturnOutput change of sampling rate:%d->%d",
           mInfo.mAudio.mRate, audioData->mRate);
       mInfo.mAudio.mRate = audioData->mRate;
       mInfo.mAudio.mChannels = audioData->mChannels;
     }
 
-    GetCallback()->OnAudioDecoded(audioData);
+    mAudio.mPromise.Resolve(audioData, __func__);
   } else if (aTrack == kVideo) {
-    GetCallback()->OnVideoDecoded(static_cast<VideoData*>(aData));
+    mVideo.mPromise.Resolve(static_cast<VideoData*>(aData), __func__);
   }
 }
 
-void
-MP4Reader::ReturnEOS(TrackType aTrack)
-{
-  GetCallback()->OnNotDecoded(aTrack == kAudio ? MediaData::AUDIO_DATA : MediaData::VIDEO_DATA, END_OF_STREAM);
-}
-
 MP4Sample*
 MP4Reader::PopSample(TrackType aTrack)
 {
   switch (aTrack) {
     case kAudio:
       return mDemuxer->DemuxAudioSample();
 
     case kVideo:
@@ -712,17 +712,17 @@ MP4Reader::Output(TrackType aTrack, Medi
   if (decoder.mIsFlushing) {
     LOG("MP4Reader produced output while flushing, discarding.");
     mon.NotifyAll();
     return;
   }
 
   decoder.mOutput.AppendElement(aSample);
   decoder.mNumSamplesOutput++;
-  if (NeedInput(decoder) || decoder.mOutputRequested) {
+  if (NeedInput(decoder) || decoder.HasPromise()) {
     ScheduleUpdate(aTrack);
   }
 }
 
 void
 MP4Reader::DrainComplete(TrackType aTrack)
 {
   DecoderData& data = GetDecoderData(aTrack);
@@ -742,18 +742,20 @@ MP4Reader::InputExhausted(TrackType aTra
 
 void
 MP4Reader::Error(TrackType aTrack)
 {
   DecoderData& data = GetDecoderData(aTrack);
   {
     MonitorAutoLock mon(data.mMonitor);
     data.mError = true;
+    if (data.HasPromise()) {
+      data.RejectPromise(DECODE_ERROR, __func__);
+    }
   }
-  GetCallback()->OnNotDecoded(aTrack == kVideo ? MediaData::VIDEO_DATA : MediaData::AUDIO_DATA, DECODE_ERROR);
 }
 
 void
 MP4Reader::Flush(TrackType aTrack)
 {
   MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
   VLOG("Flush(%s) BEGIN", TrackTypeToStr(aTrack));
   DecoderData& data = GetDecoderData(aTrack);
@@ -772,20 +774,19 @@ MP4Reader::Flush(TrackType aTrack)
   data.mDecoder->Flush();
   {
     MonitorAutoLock mon(data.mMonitor);
     data.mIsFlushing = false;
     data.mOutput.Clear();
     data.mNumSamplesInput = 0;
     data.mNumSamplesOutput = 0;
     data.mInputExhausted = false;
-    if (data.mOutputRequested) {
-      GetCallback()->OnNotDecoded(aTrack == kVideo ? MediaData::VIDEO_DATA : MediaData::AUDIO_DATA, CANCELED);
+    if (data.HasPromise()) {
+      data.RejectPromise(CANCELED, __func__);
     }
-    data.mOutputRequested = false;
     data.mDiscontinuity = true;
   }
   if (aTrack == kVideo) {
     mQueuedVideoSample = nullptr;
   }
   VLOG("Flush(%s) END", TrackTypeToStr(aTrack));
 }
 
@@ -797,22 +798,19 @@ MP4Reader::SkipVideoDemuxToNextKeyFrame(
   MOZ_ASSERT(mVideo.mDecoder);
 
   Flush(kVideo);
 
   // Loop until we reach the next keyframe after the threshold.
   while (true) {
     nsAutoPtr<MP4Sample> compressed(PopSample(kVideo));
     if (!compressed) {
-      // EOS, or error. Let the state machine know.
-      GetCallback()->OnNotDecoded(MediaData::VIDEO_DATA, END_OF_STREAM);
-      {
-        MonitorAutoLock mon(mVideo.mMonitor);
-        mVideo.mDemuxEOS = true;
-      }
+      // EOS, or error. This code assumes EOS, which may or may not be right.
+      MonitorAutoLock mon(mVideo.mMonitor);
+      mVideo.mDemuxEOS = true;
       return false;
     }
     parsed++;
     if (!compressed->is_sync_point ||
         compressed->composition_timestamp < aTimeThreshold) {
       continue;
     }
     mQueuedVideoSample = compressed;
--- a/dom/media/fmp4/MP4Reader.h
+++ b/dom/media/fmp4/MP4Reader.h
@@ -32,20 +32,20 @@ class MP4Reader MOZ_FINAL : public Media
 
 public:
   explicit MP4Reader(AbstractMediaDecoder* aDecoder);
 
   virtual ~MP4Reader();
 
   virtual nsresult Init(MediaDecoderReader* aCloneDonor) MOZ_OVERRIDE;
 
-  virtual void RequestVideoData(bool aSkipToNextKeyframe,
-                                int64_t aTimeThreshold) MOZ_OVERRIDE;
+  virtual nsRefPtr<VideoDataPromise>
+  RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold) MOZ_OVERRIDE;
 
-  virtual void RequestAudioData() MOZ_OVERRIDE;
+  virtual nsRefPtr<AudioDataPromise> RequestAudioData() MOZ_OVERRIDE;
 
   virtual bool HasAudio() MOZ_OVERRIDE;
   virtual bool HasVideo() MOZ_OVERRIDE;
 
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags) MOZ_OVERRIDE;
 
   virtual void ReadUpdatedMetadata(MediaInfo* aInfo) MOZ_OVERRIDE;
@@ -70,17 +70,16 @@ public:
     MOZ_OVERRIDE;
 
   virtual nsresult ResetDecode() MOZ_OVERRIDE;
 
   virtual nsRefPtr<ShutdownPromise> Shutdown() MOZ_OVERRIDE;
 
 private:
 
-  void ReturnEOS(TrackType aTrack);
   void ReturnOutput(MediaData* aData, TrackType aTrack);
 
   // Sends input to decoder for aTrack, and output to the state machine,
   // if necessary.
   void Update(TrackType aTrack);
 
   // Enqueues a task to call Update(aTrack) on the decoder task queue.
   // Lock for corresponding track must be held.
@@ -141,27 +140,28 @@ private:
       mReader->ReleaseMediaResources();
     }
   private:
     MP4Reader* mReader;
     mp4_demuxer::TrackType mType;
   };
 
   struct DecoderData {
-    DecoderData(const char* aMonitorName,
+    DecoderData(MediaData::Type aType,
                 uint32_t aDecodeAhead)
-      : mMonitor(aMonitorName)
+      : mType(aType)
+      , mMonitor(aType == MediaData::AUDIO_DATA ? "MP4 audio decoder data"
+                                                : "MP4 video decoder data")
       , mNumSamplesInput(0)
       , mNumSamplesOutput(0)
       , mDecodeAhead(aDecodeAhead)
       , mActive(false)
       , mInputExhausted(false)
       , mError(false)
       , mIsFlushing(false)
-      , mOutputRequested(false)
       , mUpdateScheduled(false)
       , mDemuxEOS(false)
       , mDrainComplete(false)
       , mDiscontinuity(false)
     {
     }
 
     // The platform decoder.
@@ -169,36 +169,62 @@ private:
     // TaskQueue on which decoder can choose to decode.
     // Only non-null up until the decoder is created.
     nsRefPtr<MediaTaskQueue> mTaskQueue;
     // Callback that receives output and error notifications from the decoder.
     nsAutoPtr<DecoderCallback> mCallback;
     // Decoded samples returned my mDecoder awaiting being returned to
     // state machine upon request.
     nsTArray<nsRefPtr<MediaData> > mOutput;
+    // Disambiguate Audio vs Video.
+    MediaData::Type mType;
+
+    // These get overriden in the templated concrete class.
+    virtual bool HasPromise() = 0;
+    virtual void RejectPromise(MediaDecoderReader::NotDecodedReason aReason,
+                               const char* aMethodName) = 0;
 
     // Monitor that protects all non-threadsafe state; the primitives
     // that follow.
     Monitor mMonitor;
     uint64_t mNumSamplesInput;
     uint64_t mNumSamplesOutput;
     uint32_t mDecodeAhead;
     // Whether this stream exists in the media.
     bool mActive;
     bool mInputExhausted;
     bool mError;
     bool mIsFlushing;
-    bool mOutputRequested;
     bool mUpdateScheduled;
     bool mDemuxEOS;
     bool mDrainComplete;
     bool mDiscontinuity;
   };
-  DecoderData mAudio;
-  DecoderData mVideo;
+
+  template<typename PromiseType>
+  struct DecoderDataWithPromise : public DecoderData {
+    DecoderDataWithPromise(MediaData::Type aType, uint32_t aDecodeAhead) :
+      DecoderData(aType, aDecodeAhead)
+    {
+      mPromise.SetMonitor(&mMonitor);
+    }
+
+    MediaPromiseHolder<PromiseType> mPromise;
+
+    bool HasPromise() MOZ_OVERRIDE { return !mPromise.IsEmpty(); }
+    void RejectPromise(MediaDecoderReader::NotDecodedReason aReason,
+                       const char* aMethodName) MOZ_OVERRIDE
+    {
+      mPromise.Reject(aReason, aMethodName);
+    }
+  };
+
+  DecoderDataWithPromise<AudioDataPromise> mAudio;
+  DecoderDataWithPromise<VideoDataPromise> mVideo;
+
   // Queued samples extracted by the demuxer, but not yet sent to the platform
   // decoder.
   nsAutoPtr<mp4_demuxer::MP4Sample> mQueuedVideoSample;
 
   // Returns true when the decoder for this track needs input.
   // aDecoder.mMonitor must be locked.
   bool NeedInput(DecoderData& aDecoder);
 
--- a/dom/media/fmp4/SharedDecoderManager.cpp
+++ b/dom/media/fmp4/SharedDecoderManager.cpp
@@ -7,17 +7,17 @@
 #include "SharedDecoderManager.h"
 #include "mp4_demuxer/DecoderData.h"
 
 namespace mozilla {
 
 class SharedDecoderCallback : public MediaDataDecoderCallback
 {
 public:
-  SharedDecoderCallback(SharedDecoderManager* aManager) : mManager(aManager) {}
+  explicit SharedDecoderCallback(SharedDecoderManager* aManager) : mManager(aManager) {}
 
   virtual void Output(MediaData* aData) MOZ_OVERRIDE
   {
     if (mManager->mActiveCallback) {
       mManager->mActiveCallback->Output(aData);
     }
   }
   virtual void Error() MOZ_OVERRIDE
--- a/dom/media/fmp4/eme/EMEAudioDecoder.cpp
+++ b/dom/media/fmp4/eme/EMEAudioDecoder.cpp
@@ -29,29 +29,34 @@ EMEAudioDecoder::EMEAudioDecoder(CDMProx
   , mAudioFrameSum(0)
   , mAudioFrameOffset(0)
   , mStreamOffset(0)
   , mProxy(aProxy)
   , mGMP(nullptr)
   , mConfig(aConfig)
   , mTaskQueue(aTaskQueue)
   , mCallback(aCallback)
+  , mSamplesWaitingForKey(new SamplesWaitingForKey(this, mTaskQueue, mProxy))
   , mMonitor("EMEAudioDecoder")
   , mFlushComplete(false)
+#ifdef DEBUG
+  , mIsShutdown(false)
+#endif
 {
 }
 
 EMEAudioDecoder::~EMEAudioDecoder()
 {
 }
 
 nsresult
 EMEAudioDecoder::Init()
 {
   // Note: this runs on the decode task queue.
+  MOZ_ASSERT(!mIsShutdown);
 
   MOZ_ASSERT((mConfig.bits_per_sample / 8) == 2); // Demuxer guarantees this.
 
   mMPS = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
   MOZ_ASSERT(mMPS);
 
   nsresult rv = mMPS->GetThread(getter_AddRefs(mGMPThread));
   NS_ENSURE_SUCCESS(rv, rv);
@@ -63,28 +68,34 @@ EMEAudioDecoder::Init()
 
   return NS_OK;
 }
 
 nsresult
 EMEAudioDecoder::Input(MP4Sample* aSample)
 {
   MOZ_ASSERT(!IsOnGMPThread()); // Runs on the decode task queue.
+  MOZ_ASSERT(!mIsShutdown);
+
+  if (mSamplesWaitingForKey->WaitIfKeyNotUsable(aSample)) {
+    return NS_OK;
+  }
 
   nsRefPtr<nsIRunnable> task(new DeliverSample(this, aSample));
   nsresult rv = mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
 EMEAudioDecoder::Flush()
 {
   MOZ_ASSERT(!IsOnGMPThread()); // Runs on the decode task queue.
+  MOZ_ASSERT(!mIsShutdown);
 
   {
     MonitorAutoLock mon(mMonitor);
     mFlushComplete = false;
   }
 
   nsRefPtr<nsIRunnable> task;
   task = NS_NewRunnableMethod(this, &EMEAudioDecoder::GmpFlush);
@@ -100,33 +111,42 @@ EMEAudioDecoder::Flush()
 
   return NS_OK;
 }
 
 nsresult
 EMEAudioDecoder::Drain()
 {
   MOZ_ASSERT(!IsOnGMPThread()); // Runs on the decode task queue.
+  MOZ_ASSERT(!mIsShutdown);
 
   nsRefPtr<nsIRunnable> task;
   task = NS_NewRunnableMethod(this, &EMEAudioDecoder::GmpDrain);
   nsresult rv = mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
   NS_ENSURE_SUCCESS(rv, rv);
   return NS_OK;
 }
 
 nsresult
 EMEAudioDecoder::Shutdown()
 {
   MOZ_ASSERT(!IsOnGMPThread()); // Runs on the decode task queue.
+  MOZ_ASSERT(!mIsShutdown);
+#ifdef DEBUG
+  mIsShutdown = true;
+#endif
 
   nsRefPtr<nsIRunnable> task;
   task = NS_NewRunnableMethod(this, &EMEAudioDecoder::GmpShutdown);
   nsresult rv = mGMPThread->Dispatch(task, NS_DISPATCH_SYNC);
   NS_ENSURE_SUCCESS(rv, rv);
+
+  mSamplesWaitingForKey->BreakCycles();
+  mSamplesWaitingForKey = nullptr;
+
   return NS_OK;
 }
 
 void
 EMEAudioDecoder::Decoded(const nsTArray<int16_t>& aPCM,
                          uint64_t aTimeStamp,
                          uint32_t aChannels,
                          uint32_t aRate)
@@ -216,19 +236,25 @@ EMEAudioDecoder::ResetComplete()
     mon.NotifyAll();
   }
 }
 
 void
 EMEAudioDecoder::Error(GMPErr aErr)
 {
   MOZ_ASSERT(IsOnGMPThread());
-  EME_LOG("EMEAudioDecoder::Error");
-  mCallback->Error();
-  GmpShutdown();
+  EME_LOG("EMEAudioDecoder::Error %d", aErr);
+  if (aErr == GMPNoKeyErr) {
+    // 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");
+  } else {
+    mCallback->Error();
+    GmpShutdown();
+  }
 }
 
 void
 EMEAudioDecoder::Terminated()
 {
   MOZ_ASSERT(IsOnGMPThread());
   GmpShutdown();
 }
@@ -270,28 +296,16 @@ EMEAudioDecoder::GmpInput(MP4Sample* aSa
 {
   MOZ_ASSERT(IsOnGMPThread());
   nsAutoPtr<MP4Sample> sample(aSample);
   if (!mGMP) {
     mCallback->Error();
     return NS_ERROR_FAILURE;
   }
 
-  if (sample->crypto.valid) {
-    CDMCaps::AutoLock caps(mProxy->Capabilites());
-    MOZ_ASSERT(caps.CanDecryptAndDecodeAudio());
-    const auto& keyid = sample->crypto.key;
-    if (!caps.IsKeyUsable(keyid)) {
-      // DeliverSample assumes responsibility for deleting aSample.
-      nsRefPtr<nsIRunnable> task(new DeliverSample(this, sample.forget()));
-      caps.CallWhenKeyUsable(keyid, task, mGMPThread);
-      return NS_OK;
-    }
-  }
-
   gmp::GMPAudioSamplesImpl samples(sample, mAudioChannels, mAudioRate);
   mGMP->Decode(samples);
 
   mStreamOffset = sample->byte_offset;
 
   return NS_OK;
 }
 
--- a/dom/media/fmp4/eme/EMEAudioDecoder.h
+++ b/dom/media/fmp4/eme/EMEAudioDecoder.h
@@ -8,16 +8,17 @@
 #define EMEAACDecoder_h_
 
 #include "PlatformDecoderModule.h"
 #include "mp4_demuxer/DecoderData.h"
 #include "mozIGeckoMediaPluginService.h"
 #include "nsServiceManagerUtils.h"
 #include "GMPAudioHost.h"
 #include "GMPAudioDecoderProxy.h"
+#include "SamplesWaitingForKey.h"
 
 namespace mozilla {
 
 class EMEAudioDecoder : public MediaDataDecoder
                       , public GMPAudioDecoderProxyCallback
 {
   typedef mp4_demuxer::MP4Sample MP4Sample;
   typedef mp4_demuxer::AudioDecoderConfig AudioDecoderConfig;
@@ -103,15 +104,21 @@ private:
   nsCOMPtr<nsIThread> mGMPThread;
   nsRefPtr<CDMProxy> mProxy;
   GMPAudioDecoderProxy* mGMP;
 
   const mp4_demuxer::AudioDecoderConfig& mConfig;
   nsRefPtr<MediaTaskQueue> mTaskQueue;
   MediaDataDecoderCallback* mCallback;
 
+  nsRefPtr<SamplesWaitingForKey> mSamplesWaitingForKey;
+
   Monitor mMonitor;
   bool mFlushComplete;
+
+#ifdef DEBUG
+  bool mIsShutdown;
+#endif
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/fmp4/eme/EMEDecoderModule.cpp
+++ b/dom/media/fmp4/eme/EMEDecoderModule.cpp
@@ -16,165 +16,158 @@
 #include "mozilla/CDMProxy.h"
 #include "mozilla/EMELog.h"
 #include "MediaTaskQueue.h"
 #include "SharedThreadPool.h"
 #include "mozilla/EMELog.h"
 #include "EMEH264Decoder.h"
 #include "EMEAudioDecoder.h"
 #include "mozilla/unused.h"
+#include "SamplesWaitingForKey.h"
 #include <string>
 
 namespace mozilla {
 
 class EMEDecryptor : public MediaDataDecoder {
   typedef mp4_demuxer::MP4Sample MP4Sample;
 
 public:
 
   EMEDecryptor(MediaDataDecoder* aDecoder,
                MediaDataDecoderCallback* aCallback,
                CDMProxy* aProxy)
     : mDecoder(aDecoder)
     , mCallback(aCallback)
     , mTaskQueue(CreateMediaDecodeTaskQueue())
     , mProxy(aProxy)
+    , mSamplesWaitingForKey(new SamplesWaitingForKey(this, mTaskQueue, mProxy))
+#ifdef DEBUG
+    , mIsShutdown(false)
+#endif
   {
   }
 
   virtual nsresult Init() MOZ_OVERRIDE {
+    MOZ_ASSERT(!mIsShutdown);
     nsresult rv = mTaskQueue->SyncDispatch(
       NS_NewRunnableMethod(mDecoder, &MediaDataDecoder::Init));
     unused << NS_WARN_IF(NS_FAILED(rv));
     return rv;
   }
 
-  class RedeliverEncryptedInput : public nsRunnable {
-  public:
-    RedeliverEncryptedInput(EMEDecryptor* aDecryptor,
-                            MediaTaskQueue* aTaskQueue,
-                            MP4Sample* aSample)
-      : mDecryptor(aDecryptor)
-      , mTaskQueue(aTaskQueue)
-      , mSample(aSample)
-    {}
-
-    NS_IMETHOD Run() {
-      RefPtr<nsIRunnable> task;
-      task = NS_NewRunnableMethodWithArg<MP4Sample*>(mDecryptor,
-                                                     &EMEDecryptor::Input,
-                                                     mSample.forget());
-      mTaskQueue->Dispatch(task.forget());
-      mTaskQueue = nullptr;
-      mDecryptor = nullptr;
-      return NS_OK;
-    }
-
-  private:
-    nsRefPtr<EMEDecryptor> mDecryptor;
-    nsRefPtr<MediaTaskQueue> mTaskQueue;
-    nsAutoPtr<MP4Sample> mSample;
-  };
-
   class DeliverDecrypted : public DecryptionClient {
   public:
     DeliverDecrypted(EMEDecryptor* aDecryptor, MediaTaskQueue* aTaskQueue)
       : mDecryptor(aDecryptor)
       , mTaskQueue(aTaskQueue)
     {}
-    virtual void Decrypted(nsresult aResult,
+    virtual void Decrypted(GMPErr aResult,
                            mp4_demuxer::MP4Sample* aSample) MOZ_OVERRIDE {
-      if (NS_FAILED(aResult)) {
+      if (aResult == GMPNoKeyErr) {
+        RefPtr<nsIRunnable> task;
+        task = NS_NewRunnableMethodWithArg<MP4Sample*>(mDecryptor,
+                                                       &EMEDecryptor::Input,
+                                                       aSample);
+        mTaskQueue->Dispatch(task.forget());
+      } else if (GMP_FAILED(aResult)) {
         mDecryptor->mCallback->Error();
         MOZ_ASSERT(!aSample);
       } else {
         RefPtr<nsIRunnable> task;
         task = NS_NewRunnableMethodWithArg<MP4Sample*>(mDecryptor,
                                                        &EMEDecryptor::Decrypted,
                                                        aSample);
         mTaskQueue->Dispatch(task.forget());
-        mTaskQueue = nullptr;
-        mDecryptor = nullptr;
       }
+      mTaskQueue = nullptr;
+      mDecryptor = nullptr;
     }
   private:
     nsRefPtr<EMEDecryptor> mDecryptor;
     nsRefPtr<MediaTaskQueue> mTaskQueue;
   };
 
   virtual nsresult Input(MP4Sample* aSample) MOZ_OVERRIDE {
+    MOZ_ASSERT(!mIsShutdown);
     // We run the PDM on its own task queue. We can't run it on the decode
     // task queue, because that calls into Input() in a loop and waits until
     // output is delivered. We need to defer some Input() calls while we wait
     // for keys to become usable, and once they do we need to dispatch an event
     // to run the PDM on the same task queue, but since the decode task queue
     // is waiting in MP4Reader::Decode() for output our task would never run.
     // So we dispatch tasks to make all calls into the wrapped decoder.
-    {
-      CDMCaps::AutoLock caps(mProxy->Capabilites());
-      if (!caps.IsKeyUsable(aSample->crypto.key)) {
-        EME_LOG("Encountered a non-usable key, waiting");
-        nsRefPtr<nsIRunnable> task(new RedeliverEncryptedInput(this,
-                                                               mTaskQueue,
-                                                               aSample));
-        caps.CallWhenKeyUsable(aSample->crypto.key, task);
-        return NS_OK;
-      }
+    if (mSamplesWaitingForKey->WaitIfKeyNotUsable(aSample)) {
+      return NS_OK;
     }
+
     mProxy->Decrypt(aSample, new DeliverDecrypted(this, mTaskQueue));
     return NS_OK;
   }
 
   void Decrypted(mp4_demuxer::MP4Sample* aSample) {
+    MOZ_ASSERT(!mIsShutdown);
     nsresult rv = mTaskQueue->Dispatch(
       NS_NewRunnableMethodWithArg<mp4_demuxer::MP4Sample*>(
         mDecoder,
         &MediaDataDecoder::Input,
         aSample));
     unused << NS_WARN_IF(NS_FAILED(rv));
   }
 
   virtual nsresult Flush() MOZ_OVERRIDE {
+    MOZ_ASSERT(!mIsShutdown);
     nsresult rv = mTaskQueue->SyncDispatch(
       NS_NewRunnableMethod(
         mDecoder,
         &MediaDataDecoder::Flush));
     unused << NS_WARN_IF(NS_FAILED(rv));
+    mSamplesWaitingForKey->Flush();
     return rv;
   }
 
   virtual nsresult Drain() MOZ_OVERRIDE {
+    MOZ_ASSERT(!mIsShutdown);
     nsresult rv = mTaskQueue->Dispatch(
       NS_NewRunnableMethod(
         mDecoder,
         &MediaDataDecoder::Drain));
     unused << NS_WARN_IF(NS_FAILED(rv));
     return rv;
   }
 
   virtual nsresult Shutdown() MOZ_OVERRIDE {
+    MOZ_ASSERT(!mIsShutdown);
+#ifdef DEBUG
+    mIsShutdown = true;
+#endif
     nsresult rv = mTaskQueue->SyncDispatch(
       NS_NewRunnableMethod(
         mDecoder,
         &MediaDataDecoder::Shutdown));
     unused << NS_WARN_IF(NS_FAILED(rv));
+    mSamplesWaitingForKey->BreakCycles();
+    mSamplesWaitingForKey = nullptr;
     mDecoder = nullptr;
     mTaskQueue->BeginShutdown();
     mTaskQueue->AwaitShutdownAndIdle();
     mTaskQueue = nullptr;
     mProxy = nullptr;
     return rv;
   }
 
 private:
 
   nsRefPtr<MediaDataDecoder> mDecoder;
   MediaDataDecoderCallback* mCallback;
   nsRefPtr<MediaTaskQueue> mTaskQueue;
   nsRefPtr<CDMProxy> mProxy;
+  nsRefPtr<SamplesWaitingForKey> mSamplesWaitingForKey;
+#ifdef DEBUG
+  bool mIsShutdown;
+#endif
 };
 
 EMEDecoderModule::EMEDecoderModule(CDMProxy* aProxy,
                                    PlatformDecoderModule* aPDM,
                                    bool aCDMDecodesAudio,
                                    bool aCDMDecodesVideo)
   : mProxy(aProxy)
   , mPDM(aPDM)
--- a/dom/media/fmp4/eme/EMEH264Decoder.cpp
+++ b/dom/media/fmp4/eme/EMEH264Decoder.cpp
@@ -29,28 +29,33 @@ EMEH264Decoder::EMEH264Decoder(CDMProxy*
   : mProxy(aProxy)
   , mGMP(nullptr)
   , mHost(nullptr)
   , mConfig(aConfig)
   , mImageContainer(aImageContainer)
   , mTaskQueue(aTaskQueue)
   , mCallback(aCallback)
   , mLastStreamOffset(0)
+  , mSamplesWaitingForKey(new SamplesWaitingForKey(this, mTaskQueue, mProxy))
   , mMonitor("EMEH264Decoder")
   , mFlushComplete(false)
+#ifdef DEBUG
+  , mIsShutdown(false)
+#endif
 {
 }
 
 EMEH264Decoder::~EMEH264Decoder() {
 }
 
 nsresult
 EMEH264Decoder::Init()
 {
   // Note: this runs on the decode task queue.
+  MOZ_ASSERT(!mIsShutdown);
 
   mMPS = do_GetService("@mozilla.org/gecko-media-plugin-service;1");
   MOZ_ASSERT(mMPS);
 
   nsresult rv = mMPS->GetThread(getter_AddRefs(mGMPThread));
   NS_ENSURE_SUCCESS(rv, rv);
 
   nsRefPtr<InitTask> task(new InitTask(this));
@@ -60,28 +65,34 @@ EMEH264Decoder::Init()
 
   return NS_OK;
 }
 
 nsresult
 EMEH264Decoder::Input(MP4Sample* aSample)
 {
   MOZ_ASSERT(!IsOnGMPThread()); // Runs on the decode task queue.
+  MOZ_ASSERT(!mIsShutdown);
+
+  if (mSamplesWaitingForKey->WaitIfKeyNotUsable(aSample)) {
+    return NS_OK;
+  }
 
   nsRefPtr<nsIRunnable> task(new DeliverSample(this, aSample));
   nsresult rv = mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return NS_OK;
 }
 
 nsresult
 EMEH264Decoder::Flush()
 {
   MOZ_ASSERT(!IsOnGMPThread()); // Runs on the decode task queue.
+  MOZ_ASSERT(!mIsShutdown);
 
   {
     MonitorAutoLock mon(mMonitor);
     mFlushComplete = false;
   }
 
   nsRefPtr<nsIRunnable> task;
   task = NS_NewRunnableMethod(this, &EMEH264Decoder::GmpFlush);
@@ -97,33 +108,42 @@ EMEH264Decoder::Flush()
 
   return NS_OK;
 }
 
 nsresult
 EMEH264Decoder::Drain()
 {
   MOZ_ASSERT(!IsOnGMPThread()); // Runs on the decode task queue.
+  MOZ_ASSERT(!mIsShutdown);
 
   nsRefPtr<nsIRunnable> task;
   task = NS_NewRunnableMethod(this, &EMEH264Decoder::GmpDrain);
   nsresult rv = mGMPThread->Dispatch(task, NS_DISPATCH_NORMAL);
   NS_ENSURE_SUCCESS(rv, rv);
   return NS_OK;
 }
 
 nsresult
 EMEH264Decoder::Shutdown()
 {
   MOZ_ASSERT(!IsOnGMPThread()); // Runs on the decode task queue.
+  MOZ_ASSERT(!mIsShutdown);
+#ifdef DEBUG
+  mIsShutdown = true;
+#endif
 
   nsRefPtr<nsIRunnable> task;
   task = NS_NewRunnableMethod(this, &EMEH264Decoder::GmpShutdown);
   nsresult rv = mGMPThread->Dispatch(task, NS_DISPATCH_SYNC);
   NS_ENSURE_SUCCESS(rv, rv);
+
+  mSamplesWaitingForKey->BreakCycles();
+  mSamplesWaitingForKey = nullptr;
+
   return NS_OK;
 }
 
 void
 EMEH264Decoder::Decoded(GMPVideoi420Frame* aDecodedFrame)
 {
   MOZ_ASSERT(IsOnGMPThread());
 
@@ -206,19 +226,25 @@ EMEH264Decoder::ResetComplete()
     mon.NotifyAll();
   }
 }
 
 void
 EMEH264Decoder::Error(GMPErr aErr)
 {
   MOZ_ASSERT(IsOnGMPThread());
-  EME_LOG("EMEH264Decoder::Error");
-  mCallback->Error();
-  GmpShutdown();
+  EME_LOG("EMEH264Decoder::Error %d", aErr);
+  if (aErr == GMPNoKeyErr) {
+    // 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");
+  } else {
+    mCallback->Error();
+    GmpShutdown();
+  }
 }
 
 void
 EMEH264Decoder::Terminated()
 {
   MOZ_ASSERT(IsOnGMPThread());
 
   NS_WARNING("H.264 GMP decoder terminated.");
@@ -272,28 +298,16 @@ EMEH264Decoder::GmpInput(MP4Sample* aSam
   MOZ_ASSERT(IsOnGMPThread());
 
   nsAutoPtr<MP4Sample> sample(aSample);
   if (!mGMP) {
     mCallback->Error();
     return NS_ERROR_FAILURE;
   }
 
-  if (sample->crypto.valid) {
-    CDMCaps::AutoLock caps(mProxy->Capabilites());
-    MOZ_ASSERT(caps.CanDecryptAndDecodeVideo());
-    const auto& keyid = sample->crypto.key;
-    if (!caps.IsKeyUsable(keyid)) {
-      nsRefPtr<nsIRunnable> task(new DeliverSample(this, sample.forget()));
-      caps.CallWhenKeyUsable(keyid, task, mGMPThread);
-      return NS_OK;
-    }
-  }
-
-
   mLastStreamOffset = sample->byte_offset;
 
   GMPVideoFrame* ftmp = nullptr;
   GMPErr err = mHost->CreateFrame(kGMPEncodedVideoFrame, &ftmp);
   if (GMP_FAILED(err)) {
     mCallback->Error();
     return NS_ERROR_FAILURE;
   }
--- a/dom/media/fmp4/eme/EMEH264Decoder.h
+++ b/dom/media/fmp4/eme/EMEH264Decoder.h
@@ -7,16 +7,17 @@
 #ifndef EMEH264Decoder_h_
 #define EMEH264Decoder_h_
 
 #include "PlatformDecoderModule.h"
 #include "mp4_demuxer/DecoderData.h"
 #include "ImageContainer.h"
 #include "GMPVideoDecoderProxy.h"
 #include "mozIGeckoMediaPluginService.h"
+#include "SamplesWaitingForKey.h"
 
 namespace mozilla {
 
 class CDMProxy;
 class MediaTaskQueue;
 
 class EMEH264Decoder : public MediaDataDecoder
                      , public GMPVideoDecoderCallbackProxy
@@ -99,15 +100,22 @@ private:
   GMPVideoHost* mHost;
 
   VideoInfo mVideoInfo;
   const mp4_demuxer::VideoDecoderConfig& mConfig;
   nsRefPtr<layers::ImageContainer> mImageContainer;
   nsRefPtr<MediaTaskQueue> mTaskQueue;
   MediaDataDecoderCallback* mCallback;
   int64_t mLastStreamOffset;
+
+  nsRefPtr<SamplesWaitingForKey> mSamplesWaitingForKey;
+
   Monitor mMonitor;
   bool mFlushComplete;
+
+#ifdef DEBUG
+  bool mIsShutdown;
+#endif
 };
 
 }
 
 #endif // EMEH264Decoder_h_
new file mode 100644
--- /dev/null
+++ b/dom/media/fmp4/eme/SamplesWaitingForKey.cpp
@@ -0,0 +1,82 @@
+/* -*- 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 "SamplesWaitingForKey.h"
+#include "mozilla/CDMProxy.h"
+#include "mozilla/CDMCaps.h"
+
+namespace mozilla {
+
+SamplesWaitingForKey::SamplesWaitingForKey(MediaDataDecoder* aDecoder,
+                                           MediaTaskQueue* aTaskQueue,
+                                           CDMProxy* aProxy)
+  : mMutex("SamplesWaitingForKey")
+  , mDecoder(aDecoder)
+  , mTaskQueue(aTaskQueue)
+  , mProxy(aProxy)
+{
+}
+
+SamplesWaitingForKey::~SamplesWaitingForKey()
+{
+}
+
+bool
+SamplesWaitingForKey::WaitIfKeyNotUsable(MP4Sample* aSample)
+{
+  if (!aSample || !aSample->crypto.valid || !mProxy) {
+    return false;
+  }
+  CDMCaps::AutoLock caps(mProxy->Capabilites());
+  const auto& keyid = aSample->crypto.key;
+  if (!caps.IsKeyUsable(keyid)) {
+    {
+      MutexAutoLock lock(mMutex);
+      mSamples.AppendElement(aSample);
+    }
+    caps.NotifyWhenKeyIdUsable(aSample->crypto.key, this);
+    return true;
+  }
+  return false;
+}
+
+void
+SamplesWaitingForKey::NotifyUsable(const CencKeyId& aKeyId)
+{
+  MutexAutoLock lock(mMutex);
+  size_t i = 0;
+  while (i < mSamples.Length()) {
+    if (aKeyId == mSamples[i]->crypto.key) {
+      RefPtr<nsIRunnable> task;
+      task = NS_NewRunnableMethodWithArg<MP4Sample*>(mDecoder,
+                                                     &MediaDataDecoder::Input,
+                                                     mSamples[i].forget());
+      mSamples.RemoveElementAt(i);
+      mTaskQueue->Dispatch(task.forget());
+    } else {
+      i++;
+    }
+  }
+}
+
+void
+SamplesWaitingForKey::Flush()
+{
+  MutexAutoLock lock(mMutex);
+  mSamples.Clear();
+}
+
+void
+SamplesWaitingForKey::BreakCycles()
+{
+  MutexAutoLock lock(mMutex);
+  mDecoder = nullptr;
+  mTaskQueue = nullptr;
+  mProxy = nullptr;
+  mSamples.Clear();
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/media/fmp4/eme/SamplesWaitingForKey.h
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:set ts=2 sw=2 sts=2 et cindent: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef SamplesWaitingForKey_h_
+#define SamplesWaitingForKey_h_
+
+#include "mp4_demuxer/DecoderData.h"
+#include "MediaTaskQueue.h"
+#include "PlatformDecoderModule.h"
+
+namespace mozilla {
+
+typedef nsTArray<uint8_t> CencKeyId;
+
+class CDMProxy;
+
+// Encapsulates the task of waiting for the CDMProxy to have the necessary
+// keys to decypt a given sample.
+class SamplesWaitingForKey {
+  typedef mp4_demuxer::MP4Sample MP4Sample;
+public:
+
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SamplesWaitingForKey)
+
+  explicit SamplesWaitingForKey(MediaDataDecoder* aDecoder,
+                                MediaTaskQueue* aTaskQueue,
+                                CDMProxy* aProxy);
+
+  // Returns true if we need to wait for a key to become usable.
+  // Will callback MediaDataDecoder::Input(aSample) on mDecoder once the
+  // sample is ready to be decrypted. The order of input samples is
+  // preserved.
+  bool WaitIfKeyNotUsable(MP4Sample* aSample);
+
+  void NotifyUsable(const CencKeyId& aKeyId);
+
+  void Flush();
+
+  void BreakCycles();
+
+protected:
+  ~SamplesWaitingForKey();
+
+private:
+  Mutex mMutex;
+  nsRefPtr<MediaDataDecoder> mDecoder;
+  nsRefPtr<MediaTaskQueue> mTaskQueue;
+  nsRefPtr<CDMProxy> mProxy;
+  nsTArray<nsAutoPtr<MP4Sample>> mSamples;
+};
+
+} // namespace mozilla
+
+#endif //  SamplesWaitingForKey_h_
--- a/dom/media/fmp4/eme/moz.build
+++ b/dom/media/fmp4/eme/moz.build
@@ -1,26 +1,28 @@
-# -*- 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/.
-
-EXPORTS += [
-    'EMEAudioDecoder.h',
-    'EMEDecoderModule.h',
-    'EMEH264Decoder.h',
-]
-
-UNIFIED_SOURCES += [
-    'EMEAudioDecoder.cpp',
-    'EMEDecoderModule.cpp',
-    'EMEH264Decoder.cpp',
-]
-
-include('/ipc/chromium/chromium-config.mozbuild')
-
-FINAL_LIBRARY = 'xul'
-
-FAIL_ON_WARNINGS = True
-
-if CONFIG['OS_ARCH'] == 'WINNT':
-    DEFINES['NOMINMAX'] = True
+# -*- 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/.
+
+EXPORTS += [
+    'EMEAudioDecoder.h',
+    'EMEDecoderModule.h',
+    'EMEH264Decoder.h',
+    'SamplesWaitingForKey.h',
+]
+
+UNIFIED_SOURCES += [
+    'EMEAudioDecoder.cpp',
+    'EMEDecoderModule.cpp',
+    'EMEH264Decoder.cpp',
+    'SamplesWaitingForKey.cpp',
+]
+
+include('/ipc/chromium/chromium-config.mozbuild')
+
+FINAL_LIBRARY = 'xul'
+
+FAIL_ON_WARNINGS = True
+
+if CONFIG['OS_ARCH'] == 'WINNT':
+    DEFINES['NOMINMAX'] = True
--- a/dom/media/gmp-plugin/gmp-test-decryptor.cpp
+++ b/dom/media/gmp-plugin/gmp-test-decryptor.cpp
@@ -145,19 +145,19 @@ public:
     delete this;
   }
   string mId;
   ReadContinuation* mThen;
 };
 
 class SendMessageTask : public GMPTask {
 public:
-  SendMessageTask(const string& aMessage,
-                  TestManager* aTestManager = nullptr,
-                  const string& aTestID = "")
+  explicit SendMessageTask(const string& aMessage,
+                           TestManager* aTestManager = nullptr,
+                           const string& aTestID = "")
     : mMessage(aMessage), mTestmanager(aTestManager), mTestID(aTestID) {}
 
   void Run() MOZ_OVERRIDE {
     FakeDecryptor::Message(mMessage);
     if (mTestmanager) {
       mTestmanager->EndTest(mTestID);
     }
   }
--- a/dom/media/gmp-plugin/gmp-test-decryptor.h
+++ b/dom/media/gmp-plugin/gmp-test-decryptor.h
@@ -9,17 +9,17 @@
 #include "gmp-decryption.h"
 #include "gmp-async-shutdown.h"
 #include <string>
 #include "mozilla/Attributes.h"
 
 class FakeDecryptor : public GMPDecryptor {
 public:
 
-  FakeDecryptor(GMPDecryptorHost* aHost);
+  explicit FakeDecryptor(GMPDecryptorHost* aHost);
 
   virtual void Init(GMPDecryptorCallback* aCallback) MOZ_OVERRIDE {
     mCallback = aCallback;
   }
 
   virtual void CreateSession(uint32_t aPromiseId,
                              const char* aInitDataType,
                              uint32_t aInitDataTypeSize,
--- a/dom/media/gmp/GMPChild.cpp
+++ b/dom/media/gmp/GMPChild.cpp
@@ -356,17 +356,17 @@ GMPChild::PreLoadLibraries(const std::st
 }
 #endif
 
 #if defined(MOZ_GMP_SANDBOX)
 
 #if defined(XP_MACOSX)
 class MacOSXSandboxStarter : public SandboxStarter {
 public:
-  MacOSXSandboxStarter(GMPChild* aGMPChild)
+  explicit MacOSXSandboxStarter(GMPChild* aGMPChild)
     : mGMPChild(aGMPChild)
   {}
   virtual void Start(const char* aLibPath) MOZ_OVERRIDE {
     mGMPChild->StartMacSandbox();
   }
 private:
   GMPChild* mGMPChild;
 };
--- a/dom/media/gmp/GMPStorageChild.cpp
+++ b/dom/media/gmp/GMPStorageChild.cpp
@@ -288,17 +288,17 @@ GMPStorageChild::EnumerateRecords(RecvGM
 
   CALL_ON_GMP_THREAD(SendGetRecordNames);
 
   return GMPNoErr;
 }
 
 class GMPRecordIteratorImpl : public GMPRecordIterator {
 public:
-  GMPRecordIteratorImpl(const InfallibleTArray<nsCString>& aRecordNames)
+  explicit GMPRecordIteratorImpl(const InfallibleTArray<nsCString>& aRecordNames)
     : mRecordNames(aRecordNames)
     , mIndex(0)
   {
     mRecordNames.Sort();
   }
 
   virtual GMPErr GetName(const char** aOutName, uint32_t* aOutNameLength) MOZ_OVERRIDE {
     if (!aOutName || !aOutNameLength) {
--- a/dom/media/gmp/gmp-api/gmp-errors.h
+++ b/dom/media/gmp/gmp-api/gmp-errors.h
@@ -42,15 +42,16 @@ typedef enum {
   GMPRecordInUse = 5,
   GMPQuotaExceededErr = 6,
   GMPDecodeErr = 7,
   GMPEncodeErr = 8,
   GMPNoKeyErr = 9,
   GMPCryptoErr = 10,
   GMPEndOfEnumeration = 11,
   GMPInvalidArgErr = 12,
+  GMPAbortedErr = 13,
   GMPLastErr // Placeholder, must be last. This enum's values must remain consecutive!
 } GMPErr;
 
 #define GMP_SUCCEEDED(x) ((x) == GMPNoErr)
 #define GMP_FAILED(x) ((x) != GMPNoErr)
 
 #endif // GMP_ERRORS_h_
--- a/dom/media/mediasource/ContainerParser.cpp
+++ b/dom/media/mediasource/ContainerParser.cpp
@@ -56,34 +56,41 @@ ContainerParser::ParseStartAndEndTimesta
                                             int64_t& aStart, int64_t& aEnd)
 {
   return false;
 }
 
 bool
 ContainerParser::TimestampsFuzzyEqual(int64_t aLhs, int64_t aRhs)
 {
-  NS_WARNING("Using default ContainerParser::TimestampFuzzyEquals implementation");
-  return aLhs == aRhs;
+  return llabs(aLhs - aRhs) <= GetRoundingError();
+}
+
+int64_t
+ContainerParser::GetRoundingError()
+{
+  NS_WARNING("Using default ContainerParser::GetRoundingError implementation");
+  return 0;
 }
 
 const nsTArray<uint8_t>&
 ContainerParser::InitData()
 {
   MOZ_ASSERT(mHasInitData);
   return mInitData;
 }
 
 class WebMContainerParser : public ContainerParser {
 public:
   WebMContainerParser()
     : mParser(0), mOffset(0)
   {}
 
   static const unsigned NS_PER_USEC = 1000;
+  static const unsigned USEC_PER_SEC = 1000000;
 
   bool IsInitSegmentPresent(const uint8_t* aData, uint32_t aLength)
   {
     ContainerParser::IsInitSegmentPresent(aData, aLength);
     // XXX: This is overly primitive, needs to collect data as it's appended
     // to the SB and handle, rather than assuming everything is present in a
     // single aData segment.
     // 0x1a45dfa3 // EBML
@@ -177,20 +184,20 @@ public:
               this, aStart, aEnd, mapping[0].mSyncOffset, mapping[endIdx].mEndOffset, mapping.Length(), endIdx);
 
     mapping.RemoveElementsAt(0, endIdx + 1);
     mOverlappedMapping.AppendElements(mapping);
 
     return true;
   }
 
-  bool TimestampsFuzzyEqual(int64_t aLhs, int64_t aRhs)
+  int64_t GetRoundingError()
   {
     int64_t error = mParser.GetTimecodeScale() / NS_PER_USEC;
-    return llabs(aLhs - aRhs) <= error * 2;
+    return error * 2;
   }
 
 private:
   WebMBufferedParser mParser;
   nsTArray<WebMTimeDataOffset> mOverlappedMapping;
   int64_t mOffset;
 };
 
@@ -271,19 +278,19 @@ public:
     }
     aStart = compositionRange.start;
     aEnd = compositionRange.end;
     MSE_DEBUG("MP4ContainerParser(%p)::ParseStartAndEndTimestamps: [%lld, %lld]",
               this, aStart, aEnd);
     return true;
   }
 
-  bool TimestampsFuzzyEqual(int64_t aLhs, int64_t aRhs)
+  int64_t GetRoundingError()
   {
-    return llabs(aLhs - aRhs) <= 1000;
+    return 1000;
   }
 
 private:
   nsRefPtr<mp4_demuxer::BufferStream> mStream;
   nsAutoPtr<mp4_demuxer::MoofParser> mParser;
 };
 
 /*static*/ ContainerParser*
--- a/dom/media/mediasource/ContainerParser.h
+++ b/dom/media/mediasource/ContainerParser.h
@@ -30,17 +30,19 @@ public:
   // segment.  aData may not start on a parser sync boundary.  Return true
   // if aStart and aEnd have been updated.
   virtual bool ParseStartAndEndTimestamps(const uint8_t* aData, uint32_t aLength,
                                           int64_t& aStart, int64_t& aEnd);
 
   // Compare aLhs and rHs, considering any error that may exist in the
   // timestamps from the format's base representation.  Return true if aLhs
   // == aRhs within the error epsilon.
-  virtual bool TimestampsFuzzyEqual(int64_t aLhs, int64_t aRhs);
+  bool TimestampsFuzzyEqual(int64_t aLhs, int64_t aRhs);
+
+  virtual int64_t GetRoundingError();
 
   const nsTArray<uint8_t>& InitData();
 
   bool HasInitData()
   {
     return mHasInitData;
   }
 
--- a/dom/media/mediasource/MediaSourceReader.cpp
+++ b/dom/media/mediasource/MediaSourceReader.cpp
@@ -89,156 +89,213 @@ MediaSourceReader::IsWaitingMediaResourc
     if (!mEssentialTrackBuffers[i]->IsReady()) {
       return true;
     }
   }
 
   return !mHasEssentialTrackBuffers;
 }
 
-void
+nsRefPtr<MediaDecoderReader::AudioDataPromise>
 MediaSourceReader::RequestAudioData()
 {
+  nsRefPtr<AudioDataPromise> p = mAudioPromise.Ensure(__func__);
   MSE_DEBUGV("MediaSourceReader(%p)::RequestAudioData", this);
   if (!mAudioReader) {
     MSE_DEBUG("MediaSourceReader(%p)::RequestAudioData called with no audio reader", this);
-    GetCallback()->OnNotDecoded(MediaData::AUDIO_DATA, DECODE_ERROR);
-    return;
+    mAudioPromise.Reject(DECODE_ERROR, __func__);
+    return p;
   }
   mAudioIsSeeking = false;
   SwitchAudioReader(mLastAudioTime);
-  mAudioReader->RequestAudioData();
+  mAudioReader->RequestAudioData()->Then(GetTaskQueue(), __func__, this,
+                                         &MediaSourceReader::OnAudioDecoded,
+                                         &MediaSourceReader::OnAudioNotDecoded);
+  return p;
 }
 
 void
 MediaSourceReader::OnAudioDecoded(AudioData* aSample)
 {
   MSE_DEBUGV("MediaSourceReader(%p)::OnAudioDecoded [mTime=%lld mDuration=%lld mDiscontinuity=%d]",
              this, aSample->mTime, aSample->mDuration, aSample->mDiscontinuity);
   if (mDropAudioBeforeThreshold) {
     if (aSample->mTime < mTimeThreshold) {
       MSE_DEBUG("MediaSourceReader(%p)::OnAudioDecoded mTime=%lld < mTimeThreshold=%lld",
                 this, aSample->mTime, mTimeThreshold);
-      mAudioReader->RequestAudioData();
+      mAudioReader->RequestAudioData()->Then(GetTaskQueue(), __func__, this,
+                                             &MediaSourceReader::OnAudioDecoded,
+                                             &MediaSourceReader::OnAudioNotDecoded);
       return;
     }
     mDropAudioBeforeThreshold = false;
   }
 
   // Any OnAudioDecoded callbacks received while mAudioIsSeeking must be not
   // update our last used timestamp, as these are emitted by the reader we're
   // switching away from.
   if (!mAudioIsSeeking) {
     mLastAudioTime = aSample->mTime + aSample->mDuration;
   }
-  GetCallback()->OnAudioDecoded(aSample);
+
+  mAudioPromise.Resolve(aSample, __func__);
+}
+
+// Find the closest approximation to the end time for this stream.
+// mLast{Audio,Video}Time differs from the actual end time because of
+// Bug 1065207 - the duration of a WebM fragment is an estimate not the
+// actual duration. In the case of audio time an example of where they
+// differ would be the actual sample duration being small but the
+// previous sample being large. The buffered end time uses that last
+// sample duration as an estimate of the end time duration giving an end
+// time that is greater than mLastAudioTime, which is the actual sample
+// end time.
+// Reader switching is based on the buffered end time though so they can be
+// quite different. By using the EOS_FUZZ_US and the buffered end time we
+// attempt to account for this difference.
+static void
+AdjustEndTime(int64_t* aEndTime, MediaDecoderReader* aReader)
+{
+  if (aReader) {
+    nsRefPtr<dom::TimeRanges> ranges = new dom::TimeRanges();
+    aReader->GetBuffered(ranges);
+    if (ranges->Length() > 0) {
+      // End time is a double so we convert to nearest by adding 0.5.
+      int64_t end = ranges->GetEndTime() * USECS_PER_S + 0.5;
+      *aEndTime = std::max(*aEndTime, end);
+    }
+  }
 }
 
 void
+MediaSourceReader::OnAudioNotDecoded(NotDecodedReason aReason)
+{
+  MSE_DEBUG("MediaSourceReader(%p)::OnAudioNotDecoded aReason=%u IsEnded: %d", this, aReason, IsEnded());
+  if (aReason == DECODE_ERROR || aReason == CANCELED) {
+    mAudioPromise.Reject(aReason, __func__);
+    return;
+  }
+
+  // End of stream. Force switching past this stream to another reader by
+  // switching to the end of the buffered range.
+  MOZ_ASSERT(aReason == END_OF_STREAM);
+  if (mAudioReader) {
+    AdjustEndTime(&mLastAudioTime, mAudioReader);
+  }
+
+  // See if we can find a different reader that can pick up where we left off. We use the
+  // EOS_FUZZ_US to allow for the fact that our end time can be inaccurate due to bug
+  // 1065207.
+  if (SwitchAudioReader(mLastAudioTime + EOS_FUZZ_US)) {
+    mAudioReader->RequestAudioData()->Then(GetTaskQueue(), __func__, this,
+                                           &MediaSourceReader::OnAudioDecoded,
+                                           &MediaSourceReader::OnAudioNotDecoded);
+    return;
+  }
+
+  // If the entire MediaSource is done, generate an EndOfStream.
+  if (IsEnded()) {
+    mAudioPromise.Reject(END_OF_STREAM, __func__);
+    return;
+  }
+
+  // We don't have the data the caller wants. Tell that we're waiting for JS to
+  // give us more data.
+  mAudioPromise.Reject(WAITING_FOR_DATA, __func__);
+}
+
+
+nsRefPtr<MediaDecoderReader::VideoDataPromise>
 MediaSourceReader::RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold)
 {
+  nsRefPtr<VideoDataPromise> p = mVideoPromise.Ensure(__func__);
   MSE_DEBUGV("MediaSourceReader(%p)::RequestVideoData(%d, %lld)",
              this, aSkipToNextKeyframe, aTimeThreshold);
   if (!mVideoReader) {
     MSE_DEBUG("MediaSourceReader(%p)::RequestVideoData called with no video reader", this);
-    GetCallback()->OnNotDecoded(MediaData::VIDEO_DATA, DECODE_ERROR);
-    return;
+    mVideoPromise.Reject(DECODE_ERROR, __func__);
+    return p;
   }
   if (aSkipToNextKeyframe) {
     mTimeThreshold = aTimeThreshold;
     mDropAudioBeforeThreshold = true;
     mDropVideoBeforeThreshold = true;
   }
   mVideoIsSeeking = false;
   SwitchVideoReader(mLastVideoTime);
-  mVideoReader->RequestVideoData(aSkipToNextKeyframe, aTimeThreshold);
+
+  mVideoReader->RequestVideoData(aSkipToNextKeyframe, aTimeThreshold)
+              ->Then(GetTaskQueue(), __func__, this,
+                     &MediaSourceReader::OnVideoDecoded, &MediaSourceReader::OnVideoNotDecoded);
+  return p;
 }
 
 void
 MediaSourceReader::OnVideoDecoded(VideoData* aSample)
 {
   MSE_DEBUGV("MediaSourceReader(%p)::OnVideoDecoded [mTime=%lld mDuration=%lld mDiscontinuity=%d]",
              this, aSample->mTime, aSample->mDuration, aSample->mDiscontinuity);
   if (mDropVideoBeforeThreshold) {
     if (aSample->mTime < mTimeThreshold) {
       MSE_DEBUG("MediaSourceReader(%p)::OnVideoDecoded mTime=%lld < mTimeThreshold=%lld",
                 this, aSample->mTime, mTimeThreshold);
-      mVideoReader->RequestVideoData(false, 0);
+      mVideoReader->RequestVideoData(false, 0)->Then(GetTaskQueue(), __func__, this,
+                                                     &MediaSourceReader::OnVideoDecoded,
+                                                     &MediaSourceReader::OnVideoNotDecoded);
       return;
     }
     mDropVideoBeforeThreshold = false;
   }
 
   // Any OnVideoDecoded callbacks received while mVideoIsSeeking must be not
   // update our last used timestamp, as these are emitted by the reader we're
   // switching away from.
   if (!mVideoIsSeeking) {
     mLastVideoTime = aSample->mTime + aSample->mDuration;
   }
-  GetCallback()->OnVideoDecoded(aSample);
+
+  mVideoPromise.Resolve(aSample, __func__);
 }
 
 void
-MediaSourceReader::OnNotDecoded(MediaData::Type aType, NotDecodedReason aReason)
+MediaSourceReader::OnVideoNotDecoded(NotDecodedReason aReason)
 {
-  MSE_DEBUG("MediaSourceReader(%p)::OnNotDecoded aType=%u aReason=%u IsEnded: %d", this, aType, aReason, IsEnded());
+  MSE_DEBUG("MediaSourceReader(%p)::OnVideoNotDecoded aReason=%u IsEnded: %d", this, aReason, IsEnded());
   if (aReason == DECODE_ERROR || aReason == CANCELED) {
-    GetCallback()->OnNotDecoded(aType, aReason);
+    mVideoPromise.Reject(aReason, __func__);
     return;
   }
+
   // End of stream. Force switching past this stream to another reader by
   // switching to the end of the buffered range.
   MOZ_ASSERT(aReason == END_OF_STREAM);
-  nsRefPtr<MediaDecoderReader> reader = aType == MediaData::AUDIO_DATA ?
-                                          mAudioReader : mVideoReader;
-
-  // Find the closest approximation to the end time for this stream.
-  // mLast{Audio,Video}Time differs from the actual end time because of
-  // Bug 1065207 - the duration of a WebM fragment is an estimate not the
-  // actual duration. In the case of audio time an example of where they
-  // differ would be the actual sample duration being small but the
-  // previous sample being large. The buffered end time uses that last
-  // sample duration as an estimate of the end time duration giving an end
-  // time that is greater than mLastAudioTime, which is the actual sample
-  // end time.
-  // Reader switching is based on the buffered end time though so they can be
-  // quite different. By using the EOS_FUZZ_US and the buffered end time we
-  // attempt to account for this difference.
-  int64_t* time = aType == MediaData::AUDIO_DATA ? &mLastAudioTime : &mLastVideoTime;
-  if (reader) {
-    nsRefPtr<dom::TimeRanges> ranges = new dom::TimeRanges();
-    reader->GetBuffered(ranges);
-    if (ranges->Length() > 0) {
-      // End time is a double so we convert to nearest by adding 0.5.
-      int64_t end = ranges->GetEndTime() * USECS_PER_S + 0.5;
-      *time = std::max(*time, end);
-    }
+  if (mVideoReader) {
+    AdjustEndTime(&mLastVideoTime, mAudioReader);
   }
 
   // See if we can find a different reader that can pick up where we left off. We use the
   // EOS_FUZZ_US to allow for the fact that our end time can be inaccurate due to bug
-  // 1065207 - the duration of a WebM frame is an estimate.
-  if (aType == MediaData::AUDIO_DATA && SwitchAudioReader(*time + EOS_FUZZ_US)) {
-    RequestAudioData();
-    return;
-  }
-  if (aType == MediaData::VIDEO_DATA && SwitchVideoReader(*time + EOS_FUZZ_US)) {
-    RequestVideoData(false, 0);
+  // 1065207.
+  if (SwitchVideoReader(mLastVideoTime + EOS_FUZZ_US)) {
+    mVideoReader->RequestVideoData(false, 0)
+                ->Then(GetTaskQueue(), __func__, this,
+                       &MediaSourceReader::OnVideoDecoded,
+                       &MediaSourceReader::OnVideoNotDecoded);
     return;
   }
 
   // If the entire MediaSource is done, generate an EndOfStream.
   if (IsEnded()) {
-    GetCallback()->OnNotDecoded(aType, END_OF_STREAM);
+    mVideoPromise.Reject(END_OF_STREAM, __func__);
     return;
   }
 
   // We don't have the data the caller wants. Tell that we're waiting for JS to
   // give us more data.
-  GetCallback()->OnNotDecoded(aType, WAITING_FOR_DATA);
+  mVideoPromise.Reject(WAITING_FOR_DATA, __func__);
 }
 
 nsRefPtr<ShutdownPromise>
 MediaSourceReader::Shutdown()
 {
   MOZ_ASSERT(mMediaSourceShutdownPromise.IsEmpty());
   nsRefPtr<ShutdownPromise> p = mMediaSourceShutdownPromise.Ensure(__func__);
 
@@ -259,16 +316,19 @@ MediaSourceReader::ContinueShutdown(bool
     return;
   }
 
   mAudioTrack = nullptr;
   mAudioReader = nullptr;
   mVideoTrack = nullptr;
   mVideoReader = nullptr;
 
+  MOZ_ASSERT(mAudioPromise.IsEmpty());
+  MOZ_ASSERT(mVideoPromise.IsEmpty());
+
   MediaDecoderReader::Shutdown()->ChainTo(mMediaSourceShutdownPromise.Steal(), __func__);
 }
 
 void
 MediaSourceReader::BreakCycles()
 {
   MediaDecoderReader::BreakCycles();
 
--- a/dom/media/mediasource/MediaSourceReader.h
+++ b/dom/media/mediasource/MediaSourceReader.h
@@ -41,25 +41,24 @@ public:
   }
 
   // Indicates the point in time at which the reader should consider
   // registered TrackBuffers essential for initialization.
   void PrepareInitialization();
 
   bool IsWaitingMediaResources() MOZ_OVERRIDE;
 
-  void RequestAudioData() MOZ_OVERRIDE;
+  nsRefPtr<AudioDataPromise> RequestAudioData() MOZ_OVERRIDE;
+  nsRefPtr<VideoDataPromise>
+  RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold) MOZ_OVERRIDE;
 
   void OnAudioDecoded(AudioData* aSample);
-
-  void RequestVideoData(bool aSkipToNextKeyframe, int64_t aTimeThreshold) MOZ_OVERRIDE;
-
+  void OnAudioNotDecoded(NotDecodedReason aReason);
   void OnVideoDecoded(VideoData* aSample);
-
-  void OnNotDecoded(MediaData::Type aType, NotDecodedReason aReason);
+  void OnVideoNotDecoded(NotDecodedReason aReason);
 
   void OnSeekCompleted(nsresult aResult);
 
   bool HasVideo() MOZ_OVERRIDE
   {
     return mInfo.HasVideo();
   }
 
@@ -135,16 +134,19 @@ private:
   nsRefPtr<MediaDecoderReader> mVideoReader;
 
   nsTArray<nsRefPtr<TrackBuffer>> mTrackBuffers;
   nsTArray<nsRefPtr<TrackBuffer>> mShutdownTrackBuffers;
   nsTArray<nsRefPtr<TrackBuffer>> mEssentialTrackBuffers;
   nsRefPtr<TrackBuffer> mAudioTrack;
   nsRefPtr<TrackBuffer> mVideoTrack;
 
+  MediaPromiseHolder<AudioDataPromise> mAudioPromise;
+  MediaPromiseHolder<VideoDataPromise> mVideoPromise;
+
 #ifdef MOZ_EME
   nsRefPtr<CDMProxy> mCDMProxy;
 #endif
 
   // These are read and written on the decode task queue threads.
   int64_t mLastAudioTime;
   int64_t mLastVideoTime;
 
--- a/dom/media/mediasource/TrackBuffer.cpp
+++ b/dom/media/mediasource/TrackBuffer.cpp
@@ -197,39 +197,95 @@ TrackBuffer::AppendDataToCurrentResource
   mCurrentDecoder->NotifyDataArrived(reinterpret_cast<const char*>(aData),
                                      aLength, appendOffset);
   mParentDecoder->NotifyBytesDownloaded();
   mParentDecoder->NotifyTimeRangesChanged();
 
   return true;
 }
 
+class DecoderSorter
+{
+public:
+  bool LessThan(SourceBufferDecoder* aFirst, SourceBufferDecoder* aSecond) const
+  {
+    nsRefPtr<dom::TimeRanges> first = new dom::TimeRanges();
+    aFirst->GetBuffered(first);
+
+    nsRefPtr<dom::TimeRanges> second = new dom::TimeRanges();
+    aSecond->GetBuffered(second);
+
+    return first->GetStartTime() < second->GetStartTime();
+  }
+
+  bool Equals(SourceBufferDecoder* aFirst, SourceBufferDecoder* aSecond) const
+  {
+    nsRefPtr<dom::TimeRanges> first = new dom::TimeRanges();
+    aFirst->GetBuffered(first);
+
+    nsRefPtr<dom::TimeRanges> second = new dom::TimeRanges();
+    aSecond->GetBuffered(second);
+
+    return first->GetStartTime() == second->GetStartTime();
+  }
+};
+
 bool
 TrackBuffer::EvictData(uint32_t aThreshold)
 {
   MOZ_ASSERT(NS_IsMainThread());
   ReentrantMonitorAutoEnter mon(mParentDecoder->GetReentrantMonitor());
 
   int64_t totalSize = 0;
   for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
     totalSize += mDecoders[i]->GetResource()->GetSize();
   }
 
   int64_t toEvict = totalSize - aThreshold;
   if (toEvict <= 0) {
     return false;
   }
 
-  for (uint32_t i = 0; i < mInitializedDecoders.Length(); ++i) {
+  // Get a list of initialized decoders, sorted by their start times.
+  nsTArray<SourceBufferDecoder*> decoders;
+  decoders.AppendElements(mInitializedDecoders);
+  decoders.Sort(DecoderSorter());
+
+  // First try to evict data before the current play position, starting
+  // with the earliest time.
+  uint32_t i = 0;
+  for (; i < decoders.Length(); ++i) {
     MSE_DEBUG("TrackBuffer(%p)::EvictData decoder=%u threshold=%u toEvict=%lld",
               this, i, aThreshold, toEvict);
-    toEvict -= mInitializedDecoders[i]->GetResource()->EvictData(toEvict);
-    if (!mInitializedDecoders[i]->GetResource()->GetSize() &&
-        mInitializedDecoders[i] != mCurrentDecoder) {
-      RemoveDecoder(mInitializedDecoders[i]);
+    toEvict -= decoders[i]->GetResource()->EvictData(toEvict);
+    if (!decoders[i]->GetResource()->GetSize() &&
+        decoders[i] != mCurrentDecoder) {
+      RemoveDecoder(decoders[i]);
+    }
+    if (toEvict <= 0 || decoders[i] == mCurrentDecoder) {
+      break;
+    }
+  }
+
+  // If we still need to evict more, then try to evict entire decoders,
+  // starting from the end.
+  if (toEvict > 0) {
+    uint32_t end = i;
+    MOZ_ASSERT(decoders[end] == mCurrentDecoder);
+
+    for (i = decoders.Length() - 1; i > end; --i) {
+      MSE_DEBUG("TrackBuffer(%p)::EvictData removing entire decoder=%u from end toEvict=%lld",
+                this, i, toEvict);
+      // TODO: We could implement forward-eviction within a decoder and
+      // be able to evict within the current decoder.
+      toEvict -= decoders[i]->GetResource()->GetSize();
+      RemoveDecoder(decoders[i]);
+      if (toEvict <= 0) {
+        break;
+      }
     }
   }
   return toEvict < (totalSize - aThreshold);
 }
 
 void
 TrackBuffer::EvictBefore(double aTime)
 {
@@ -255,17 +311,17 @@ TrackBuffer::Buffered(dom::TimeRanges* a
 
   double highestEndTime = 0;
 
   for (uint32_t i = 0; i < mDecoders.Length(); ++i) {
     nsRefPtr<dom::TimeRanges> r = new dom::TimeRanges();
     mDecoders[i]->GetBuffered(r);
     if (r->Length() > 0) {
       highestEndTime = std::max(highestEndTime, r->GetEndTime());
-      aRanges->Union(r);
+      aRanges->Union(r, double(mParser->GetRoundingError()) / USECS_PER_S);
     }
   }
 
   return highestEndTime;
 }
 
 already_AddRefed<SourceBufferDecoder>
 TrackBuffer::NewDecoder()
--- a/dom/media/ogg/OggReader.cpp
+++ b/dom/media/ogg/OggReader.cpp
@@ -971,17 +971,17 @@ bool OggReader::ReadOggPage(ogg_page* aP
     NS_ENSURE_TRUE(ret == 0, false);
   }
 
   return true;
 }
 
 ogg_packet* OggReader::NextOggPacket(OggCodecState* aCodecState)
 {
-  NS_ASSERTION(mDecoder->OnDecodeThread(), "Should be on decode thread.");
+  MOZ_ASSERT(mDecoder->OnDecodeThread(), "Should be on decode thread.");
 
   if (!aCodecState || !aCodecState->mActive) {
     return nullptr;
   }
 
   ogg_packet* packet;
   while ((packet = aCodecState->PacketOut()) == nullptr) {
     // The codec state does not have any buffered pages, so try to read another
--- a/dom/media/omx/MediaCodecReader.cpp
+++ b/dom/media/omx/MediaCodecReader.cpp
@@ -342,16 +342,18 @@ MediaCodecReader::ReleaseMediaResources(
     mAudioTrack.mSourceIsStopped = true;
   }
   ReleaseCriticalResources();
 }
 
 nsRefPtr<ShutdownPromise>
 MediaCodecReader::Shutdown()
 {
+  MOZ_ASSERT(mAudioPromise.IsEmpty());
+  MOZ_ASSERT(mVideoPromise.IsEmpty());
   ReleaseResources();
   return MediaDecoderReader::Shutdown();
 }
 
 void
 MediaCodecReader::DispatchAudioTask()
 {
   if (mAudioTrack.mTaskQueue && mAudioTrack.mTaskQueue->IsEmpty()) {
@@ -369,41 +371,48 @@ MediaCodecReader::DispatchVideoTask(int6
     RefPtr<nsIRunnable> task =
       NS_NewRunnableMethodWithArg<int64_t>(this,
                                            &MediaCodecReader::DecodeVideoFrameTask,
                                            aTimeThreshold);
     mVideoTrack.mTaskQueue->Dispatch(task);
   }
 }
 
-void
+nsRefPtr<MediaDecoderReader::AudioDataPromise>
 MediaCodecReader::RequestAudioData()
 {
   MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
   MOZ_ASSERT(HasAudio());
+
+  nsRefPtr<AudioDataPromise> p = mAudioPromise.Ensure(__func__);
   if (CheckAudioResources()) {
     DispatchAudioTask();
   }
+
+  return p;
 }
 
-void
+nsRefPtr<MediaDecoderReader::VideoDataPromise>
 MediaCodecReader::RequestVideoData(bool aSkipToNextKeyframe,
                                    int64_t aTimeThreshold)
 {
   MOZ_ASSERT(GetTaskQueue()->IsCurrentThreadIn());
   MOZ_ASSERT(HasVideo());
 
+  nsRefPtr<VideoDataPromise> p = mVideoPromise.Ensure(__func__);
   int64_t threshold = sInvalidTimestampUs;
   if (aSkipToNextKeyframe && IsValidTimestampUs(aTimeThreshold)) {
     mVideoTrack.mTaskQueue->Flush();
     threshold = aTimeThreshold;
   }
   if (CheckVideoResources()) {
     DispatchVideoTask(threshold);
   }
+
+  return p;
 }
 
 bool
 MediaCodecReader::DecodeAudioDataSync()
 {
   if (mAudioTrack.mCodec == nullptr || !mAudioTrack.mCodec->allocated() ||
       mAudioTrack.mOutputEndOfStream) {
     return false;
@@ -479,41 +488,41 @@ MediaCodecReader::DecodeAudioDataTask()
   bool result = DecodeAudioDataSync();
   if (AudioQueue().GetSize() > 0) {
     nsRefPtr<AudioData> a = AudioQueue().PopFront();
     if (a) {
       if (mAudioTrack.mDiscontinuity) {
         a->mDiscontinuity = true;
         mAudioTrack.mDiscontinuity = false;
       }
-      GetCallback()->OnAudioDecoded(a);
+      mAudioPromise.Resolve(a, __func__);
     }
   }
-  if (AudioQueue().AtEndOfStream()) {
-    GetCallback()->OnNotDecoded(MediaData::AUDIO_DATA, END_OF_STREAM);
+  else if (AudioQueue().AtEndOfStream()) {
+    mAudioPromise.Reject(END_OF_STREAM, __func__);
   }
   return result;
 }
 
 bool
 MediaCodecReader::DecodeVideoFrameTask(int64_t aTimeThreshold)
 {
   bool result = DecodeVideoFrameSync(aTimeThreshold);
   if (VideoQueue().GetSize() > 0) {
     nsRefPtr<VideoData> v = VideoQueue().PopFront();
     if (v) {
       if (mVideoTrack.mDiscontinuity) {
         v->mDiscontinuity = true;
         mVideoTrack.mDiscontinuity = false;
       }
-      GetCallback()->OnVideoDecoded(v);
+      mVideoPromise.Resolve(v, __func__);
     }
   }
-  if (VideoQueue().AtEndOfStream()) {
-    GetCallback()->OnNotDecoded(MediaData::VIDEO_DATA, END_OF_STREAM);
+  else if (VideoQueue().AtEndOfStream()) {
+    mVideoPromise.Reject(END_OF_STREAM, __func__);
   }
   return result;
 }
 
 bool
 MediaCodecReader::HasAudio()
 {
   return mInfo.mAudio.mHasAudio;
--- a/dom/media/omx/MediaCodecReader.h
+++ b/dom/media/omx/MediaCodecReader.h
@@ -74,21 +74,22 @@ public:
   // all contents have been continuously parsed. (ex. total duration of some
   // variable-bit-rate MP3 files.)
   virtual void NotifyDataArrived(const char* aBuffer, uint32_t aLength, int64_t aOffset);
 
   // Flush the MediaTaskQueue, flush MediaCodec and raise the mDiscontinuity.
   virtual nsresult ResetDecode() MOZ_OVERRIDE;
 
   // Disptach a DecodeVideoFrameTask to decode video data.
-  virtual void RequestVideoData(bool aSkipToNextKeyframe,
-                                int64_t aTimeThreshold) MOZ_OVERRIDE;
+  virtual nsRefPtr<VideoDataPromise>
+  RequestVideoData(bool aSkipToNextKeyframe,
+                   int64_t aTimeThreshold) MOZ_OVERRIDE;
 
   // Disptach a DecodeAduioDataTask to decode video data.
-  virtual void RequestAudioData() MOZ_OVERRIDE;
+  virtual nsRefPtr<AudioDataPromise> RequestAudioData() MOZ_OVERRIDE;
 
   virtual bool HasAudio();
   virtual bool HasVideo();
 
   virtual void PreReadMetadata() MOZ_OVERRIDE;
   // Read header data for all bitstreams in the file. Fills aInfo with
   // the data required to present the media, and optionally fills *aTags
   // with tag metadata from the file.
@@ -426,16 +427,19 @@ private:
   Mutex mTextureClientIndexesLock;
   nsDataHashtable<nsPtrHashKey<TextureClient>, size_t> mTextureClientIndexes;
 
   // media tracks
   AudioTrack mAudioTrack;
   VideoTrack mVideoTrack;
   AudioTrack mAudioOffloadTrack; // only Track::mSource is valid
 
+  MediaPromiseHolder<AudioDataPromise> mAudioPromise;
+  MediaPromiseHolder<VideoDataPromise> mVideoPromise;
+
   // color converter
   android::I420ColorConverterHelper mColorConverter;
   nsAutoArrayPtr<uint8_t> mColorConverterBuffer;
   size_t mColorConverterBufferSize;
 
   // incremental parser
   Monitor mParserMonitor;
   bool mParseDataFromCache;
--- a/dom/media/omx/RtspMediaCodecReader.cpp
+++ b/dom/media/omx/RtspMediaCodecReader.cpp
@@ -69,29 +69,29 @@ RtspMediaCodecReader::EnsureActive()
   nsIStreamingProtocolController* controller =
     mRtspResource->GetMediaStreamController();
   if (controller) {
     controller->Play();
   }
   mRtspResource->SetSuspend(false);
 }
 
-void
+nsRefPtr<MediaDecoderReader::AudioDataPromise>
 RtspMediaCodecReader::RequestAudioData()
 {
   EnsureActive();
-  MediaCodecReader::RequestAudioData();
+  return MediaCodecReader::RequestAudioData();
 }
 
-void
+nsRefPtr<MediaDecoderReader::VideoDataPromise>
 RtspMediaCodecReader::RequestVideoData(bool aSkipToNextKeyframe,
                                        int64_t aTimeThreshold)
 {
   EnsureActive();
-  MediaCodecReader::RequestVideoData(aSkipToNextKeyframe, aTimeThreshold);
+  return MediaCodecReader::RequestVideoData(aSkipToNextKeyframe, aTimeThreshold);
 }
 
 nsresult
 RtspMediaCodecReader::ReadMetadata(MediaInfo* aInfo,
                                    MetadataTags** aTags)
 {
   mRtspResource->DisablePlayoutDelay();
   EnsureActive();
--- a/dom/media/omx/RtspMediaCodecReader.h
+++ b/dom/media/omx/RtspMediaCodecReader.h
@@ -50,21 +50,22 @@ public:
   // data so the |GetBuffered| function can retrieve useful time ranges.
   virtual nsresult GetBuffered(dom::TimeRanges* aBuffered) MOZ_OVERRIDE {
     return NS_ERROR_NOT_IMPLEMENTED;
   }
 
   virtual void SetIdle() MOZ_OVERRIDE;
 
   // Disptach a DecodeVideoFrameTask to decode video data.
-  virtual void RequestVideoData(bool aSkipToNextKeyframe,
-                                int64_t aTimeThreshold) MOZ_OVERRIDE;
+  virtual nsRefPtr<VideoDataPromise>
+  RequestVideoData(bool aSkipToNextKeyframe,
+                   int64_t aTimeThreshold) MOZ_OVERRIDE;
 
   // Disptach a DecodeAudioDataTask to decode audio data.
-  virtual void RequestAudioData() MOZ_OVERRIDE;
+  virtual nsRefPtr<AudioDataPromise> RequestAudioData() MOZ_OVERRIDE;
 
   virtual nsresult ReadMetadata(MediaInfo* aInfo,
                                 MetadataTags** aTags) MOZ_OVERRIDE;
 
 private:
   // A pointer to RtspMediaResource for calling the Rtsp specific function.
   // The lifetime of mRtspResource is controlled by MediaDecoder. MediaDecoder
   // holds the MediaDecoderStateMachine and RtspMediaResource.
--- a/dom/media/webaudio/AudioContext.cpp
+++ b/dom/media/webaudio/AudioContext.cpp
@@ -469,32 +469,32 @@ AudioContext::DecodeAudioData(const Arra
 
   // Neuter the array buffer
   size_t length = aBuffer.Length();
   JS::RootedObject obj(cx, aBuffer.Obj());
 
   uint8_t* data = static_cast<uint8_t*>(JS_StealArrayBufferContents(cx, obj));
 
   // Sniff the content of the media.
-  // Failed type sniffing will be handled by AsyncDecodeMedia.
+  // Failed type sniffing will be handled by AsyncDecodeWebAudio.
   nsAutoCString contentType;
   NS_SniffContent(NS_DATA_SNIFFER_CATEGORY, nullptr, data, length, contentType);
 
   nsRefPtr<DecodeErrorCallback> failureCallback;
   nsRefPtr<DecodeSuccessCallback> successCallback;
   if (aFailureCallback.WasPassed()) {
     failureCallback = &aFailureCallback.Value();
   }
   if (aSuccessCallback.WasPassed()) {
     successCallback = &aSuccessCallback.Value();
   }
   nsRefPtr<WebAudioDecodeJob> job(
     new WebAudioDecodeJob(contentType, this,
                           promise, successCallback, failureCallback));
-  mDecoder.AsyncDecodeMedia(contentType.get(), data, length, *job);
+  AsyncDecodeWebAudio(contentType.get(), data, length, *job);
   // Transfer the ownership to mDecodeJobs
   mDecodeJobs.AppendElement(job.forget());
 
   return promise.forget();
 }
 
 void
 AudioContext::RemoveFromDecodeQueue(WebAudioDecodeJob* aDecodeJob)
@@ -580,18 +580,16 @@ AudioContext::Shutdown()
 
   // We mute rather than suspending, because the delay between the ::Shutdown
   // call and the CC would make us overbuffer in the MediaStreamGraph.
   // See bug 936784 for details.
   if (!mIsOffline) {
     Mute();
   }
 
-  mDecoder.Shutdown();
-
   // Release references to active nodes.
   // Active AudioNodes don't unregister in destructors, at which point the
   // Node is already unregistered.
   mActiveNodes.Clear();
 
   // For offline contexts, we can destroy the MediaStreamGraph at this point.
   if (mIsOffline && mDestination) {
     mDestination->OfflineShutdown();
@@ -696,17 +694,16 @@ AudioContext::SizeOfIncludingThis(mozill
   // AudioNodes are tracked separately because we do not want the AudioContext
   // to track all of the AudioNodes it creates, so we wouldn't be able to
   // traverse them from here.
 
   size_t amount = aMallocSizeOf(this);
   if (mListener) {
     amount += mListener->SizeOfIncludingThis(aMallocSizeOf);
   }
-  amount += mDecoder.SizeOfExcludingThis(aMallocSizeOf);
   amount += mDecodeJobs.SizeOfExcludingThis(aMallocSizeOf);
   for (uint32_t i = 0; i < mDecodeJobs.Length(); ++i) {
     amount += mDecodeJobs[i]->SizeOfIncludingThis(aMallocSizeOf);
   }
   amount += mActiveNodes.SizeOfExcludingThis(nullptr, aMallocSizeOf);
   amount += mPannerNodes.SizeOfExcludingThis(nullptr, aMallocSizeOf);
   return amount;
 }
--- a/dom/media/webaudio/AudioContext.h
+++ b/dom/media/webaudio/AudioContext.h
@@ -266,17 +266,16 @@ private:
   friend struct ::mozilla::WebAudioDecodeJob;
 
 private:
   // Note that it's important for mSampleRate to be initialized before
   // mDestination, as mDestination's constructor needs to access it!
   const float mSampleRate;
   nsRefPtr<AudioDestinationNode> mDestination;
   nsRefPtr<AudioListener> mListener;
-  MediaBufferDecoder mDecoder;
   nsTArray<nsRefPtr<WebAudioDecodeJob> > mDecodeJobs;
   // See RegisterActiveNode.  These will keep the AudioContext alive while it
   // is rendering and the window remains alive.
   nsTHashtable<nsRefPtrHashKey<AudioNode> > mActiveNodes;
   // Hashsets containing all the PannerNodes, to compute the doppler shift.
   // These are weak pointers.
   nsTHashtable<nsPtrHashKey<PannerNode> > mPannerNodes;
   // Number of channels passed in the OfflineAudioContext ctor.
--- a/dom/media/webaudio/BufferDecoder.cpp
+++ b/dom/media/webaudio/BufferDecoder.cpp
@@ -32,20 +32,20 @@ BufferDecoder::BufferDecoder(MediaResour
 
 BufferDecoder::~BufferDecoder()
 {
   // The dtor may run on any thread, we cannot be sure.
   MOZ_COUNT_DTOR(BufferDecoder);
 }
 
 void
-BufferDecoder::BeginDecoding(nsIThread* aDecodeThread)
+BufferDecoder::BeginDecoding(MediaTaskQueue* aTaskQueueIdentity)
 {
-  MOZ_ASSERT(!mDecodeThread && aDecodeThread);
-  mDecodeThread = aDecodeThread;
+  MOZ_ASSERT(!mTaskQueueIdentity && aTaskQueueIdentity);
+  mTaskQueueIdentity = aTaskQueueIdentity;
 }
 
 ReentrantMonitor&
 BufferDecoder::GetReentrantMonitor()
 {
   return mReentrantMonitor;
 }
 
@@ -61,18 +61,18 @@ BufferDecoder::OnStateMachineThread() co
 {
   // BufferDecoder doesn't have the concept of a state machine.
   return true;
 }
 
 bool
 BufferDecoder::OnDecodeThread() const
 {
-  MOZ_ASSERT(mDecodeThread, "Forgot to call BeginDecoding?");
-  return IsCurrentThread(mDecodeThread);
+  MOZ_ASSERT(mTaskQueueIdentity, "Forgot to call BeginDecoding?");
+  return mTaskQueueIdentity->IsCurrentThreadIn();
 }
 
 MediaResource*
 BufferDecoder::GetResource() const
 {
   return mResource;
 }
 
--- a/dom/media/webaudio/BufferDecoder.h
+++ b/dom/media/webaudio/BufferDecoder.h
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef BUFFER_DECODER_H_
 #define BUFFER_DECODER_H_
 
 #include "AbstractMediaDecoder.h"
+#include "MediaTaskQueue.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/ReentrantMonitor.h"
 
 namespace mozilla {
 
 /**
  * This class provides a decoder object which decodes a media file that lives in
  * a memory buffer.
@@ -22,17 +23,17 @@ class BufferDecoder : public AbstractMed
 public:
   // This class holds a weak pointer to MediaResource.  It's the responsibility
   // of the caller to manage the memory of the MediaResource object.
   explicit BufferDecoder(MediaResource* aResource);
 
   NS_DECL_THREADSAFE_ISUPPORTS
 
   // This has to be called before decoding begins
-  void BeginDecoding(nsIThread* aDecodeThread);
+  void BeginDecoding(MediaTaskQueue* aTaskQueueIdentity);
 
   virtual ReentrantMonitor& GetReentrantMonitor() MOZ_FINAL MOZ_OVERRIDE;
 
   virtual bool IsShutdown() const MOZ_FINAL MOZ_OVERRIDE;
 
   virtual bool OnStateMachineThread() const MOZ_FINAL MOZ_OVERRIDE;
 
   virtual bool OnDecodeThread() const MOZ_FINAL MOZ_OVERRIDE;
@@ -78,15 +79,15 @@ public:
 
 private:
   virtual ~BufferDecoder();
 
   // This monitor object is not really used to synchronize access to anything.
   // It's just there in order for us to be able to override
   // GetReentrantMonitor correctly.
   ReentrantMonitor mReentrantMonitor;
-  nsCOMPtr<nsIThread> mDecodeThread;
+  nsRefPtr<MediaTaskQueue> mTaskQueueIdentity;
   nsRefPtr<MediaResource> mResource;
 };
 
 } // namespace mozilla
 
 #endif /* BUFFER_DECODER_H_ */
--- a/dom/media/webaudio/MediaBufferDecoder.cpp
+++ b/dom/media/webaudio/MediaBufferDecoder.cpp
@@ -15,21 +15,19 @@
 #include "BufferMediaResource.h"
 #include "DecoderTraits.h"
 #include "AudioContext.h"
 #include "AudioBuffer.h"
 #include "nsAutoPtr.h"
 #include "nsIScriptObjectPrincipal.h"
 #include "nsIScriptError.h"
 #include "nsMimeTypes.h"
+#include "VideoUtils.h"
 #include "WebAudioUtils.h"
 #include "mozilla/dom/Promise.h"
-#ifdef XP_WIN
-#include "ThreadPoolCOMListener.h"
-#endif
 
 namespace mozilla {
 
 NS_IMPL_CYCLE_COLLECTION_CLASS(WebAudioDecodeJob)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(WebAudioDecodeJob)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mContext)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutput)
@@ -90,38 +88,37 @@ MOZ_BEGIN_ENUM_CLASS(PhaseEnum, int)
   Done
 MOZ_END_ENUM_CLASS(PhaseEnum)
 
 class MediaDecodeTask : public nsRunnable
 {
 public:
   MediaDecodeTask(const char* aContentType, uint8_t* aBuffer,
                   uint32_t aLength,
-                  WebAudioDecodeJob& aDecodeJob,
-                  nsIThreadPool* aThreadPool)
+                  WebAudioDecodeJob& aDecodeJob)
     : mContentType(aContentType)
     , mBuffer(aBuffer)
     , mLength(aLength)
     , mDecodeJob(aDecodeJob)
     , mPhase(PhaseEnum::Decode)
-    , mThreadPool(aThreadPool)
   {
     MOZ_ASSERT(aBuffer);
     MOZ_ASSERT(NS_IsMainThread());
 
     nsCOMPtr<nsPIDOMWindow> pWindow = do_QueryInterface(mDecodeJob.mContext->GetParentObject());
     nsCOMPtr<nsIScriptObjectPrincipal> scriptPrincipal =
       do_QueryInterface(pWindow);
     if (scriptPrincipal) {
       mPrincipal = scriptPrincipal->GetPrincipal();
     }
   }
 
   NS_IMETHOD Run();
   bool CreateReader();
+  MediaDecoderReader* Reader() { MOZ_ASSERT(mDecoderReader); return mDecoderReader; }
 
 private:
   void ReportFailureOnMainThread(WebAudioDecodeJob::ErrorCode aErrorCode) {
     if (NS_IsMainThread()) {
       Cleanup();
       mDecodeJob.OnFailure(aErrorCode);
     } else {
       // Take extra care to cleanup on the main thread
@@ -129,16 +126,20 @@ private:
 
       nsCOMPtr<nsIRunnable> event =
         new ReportResultTask(mDecodeJob, &WebAudioDecodeJob::OnFailure, aErrorCode);
       NS_DispatchToMainThread(event);
     }
   }
 
   void Decode();
+  void RequestSample();
+  void SampleDecoded(AudioData* aData);
+  void SampleNotDecoded(MediaDecoderReader::NotDecodedReason aReason);
+  void FinishDecode();
   void AllocateBuffer();
   void CallbackTheResult();
 
   void Cleanup()
   {
     MOZ_ASSERT(NS_IsMainThread());
     // MediaDecoderReader expects that BufferDecoder is alive.
     // Destruct MediaDecoderReader first.
@@ -149,20 +150,21 @@ private:
   }
 
 private:
   nsCString mContentType;
   uint8_t* mBuffer;
   uint32_t mLength;
   WebAudioDecodeJob& mDecodeJob;
   PhaseEnum mPhase;
-  nsCOMPtr<nsIThreadPool> mThreadPool;
   nsCOMPtr<nsIPrincipal> mPrincipal;
   nsRefPtr<BufferDecoder> mBufferDecoder;
   nsRefPtr<MediaDecoderReader> mDecoderReader;
+  MediaInfo mMediaInfo;
+  MediaQueue<AudioData> mAudioQueue;
 };
 
 NS_IMETHODIMP
 MediaDecodeTask::Run()
 {
   MOZ_ASSERT(mBufferDecoder);
   MOZ_ASSERT(mDecoderReader);
   switch (mPhase) {
@@ -200,16 +202,20 @@ MediaDecodeTask::CreateReader()
     return false;
   }
 
   nsresult rv = mDecoderReader->Init(nullptr);
   if (NS_FAILED(rv)) {
     return false;
   }
 
+  if (!mDecoderReader->EnsureTaskQueue()) {
+    return false;
+  }
+
   return true;
 }
 
 class AutoResampler {
 public:
   AutoResampler()
     : mResampler(nullptr)
   {}
@@ -233,60 +239,77 @@ private:
   SpeexResamplerState* mResampler;
 };
 
 void
 MediaDecodeTask::Decode()
 {
   MOZ_ASSERT(!NS_IsMainThread());
 
-  mBufferDecoder->BeginDecoding(NS_GetCurrentThread());
+  mBufferDecoder->BeginDecoding(mDecoderReader->GetTaskQueue());
 
   // Tell the decoder reader that we are not going to play the data directly,
   // and that we should not reject files with more channels than the audio
   // bakend support.
   mDecoderReader->SetIgnoreAudioOutputFormat();
 
-  MediaInfo mediaInfo;
   nsAutoPtr<MetadataTags> tags;
-  nsresult rv = mDecoderReader->ReadMetadata(&mediaInfo, getter_Transfers(tags));
+  nsresult rv = mDecoderReader->ReadMetadata(&mMediaInfo, getter_Transfers(tags));
   if (NS_FAILED(rv)) {
     mDecoderReader->Shutdown();
     ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent);
     return;
   }
 
   if (!mDecoderReader->HasAudio()) {
     mDecoderReader->Shutdown();
     ReportFailureOnMainThread(WebAudioDecodeJob::NoAudio);
     return;
   }
 
-  MediaQueue<AudioData> audioQueue;
-  nsRefPtr<AudioDecodeRendezvous> barrier(new AudioDecodeRendezvous());
-  mDecoderReader->SetCallback(barrier);
-  while (1) {
-    mDecoderReader->RequestAudioData();
-    nsRefPtr<AudioData> audio;
-    if (NS_FAILED(barrier->Await(audio))) {
-      mDecoderReader->Shutdown();
-      ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent);
-      return;
-    }
-    if (!audio) {
-      // End of stream.
-      break;
-    }
-    audioQueue.Push(audio);
+  RequestSample();
+}
+
+void
+MediaDecodeTask::RequestSample()
+{
+  mDecoderReader->RequestAudioData()->Then(mDecoderReader->GetTaskQueue(), __func__, this,
+                                           &MediaDecodeTask::SampleDecoded,
+                                           &MediaDecodeTask::SampleNotDecoded);
+}
+
+void
+MediaDecodeTask::SampleDecoded(AudioData* aData)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  mAudioQueue.Push(aData);
+  RequestSample();
+}
+
+void
+MediaDecodeTask::SampleNotDecoded(MediaDecoderReader::NotDecodedReason aReason)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+  if (aReason == MediaDecoderReader::DECODE_ERROR) {
+    mDecoderReader->Shutdown();
+    ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent);
+  } else {
+    MOZ_ASSERT(aReason == MediaDecoderReader::END_OF_STREAM);
+    FinishDecode();
   }
+}
+
+void
+MediaDecodeTask::FinishDecode()
+{
   mDecoderReader->Shutdown();
 
-  uint32_t frameCount = audioQueue.FrameCount();
-  uint32_t channelCount = mediaInfo.mAudio.mChannels;
-  uint32_t sampleRate = mediaInfo.mAudio.mRate;
+  uint32_t frameCount = mAudioQueue.FrameCount();
+  uint32_t channelCount = mMediaInfo.mAudio.mChannels;
+  uint32_t sampleRate = mMediaInfo.mAudio.mRate;
 
   if (!frameCount || !channelCount || !sampleRate) {
     ReportFailureOnMainThread(WebAudioDecodeJob::InvalidContent);
     return;
   }
 
   const uint32_t destSampleRate = mDecodeJob.mContext->SampleRate();
   AutoResampler resampler;
@@ -324,17 +347,17 @@ MediaDecodeTask::Decode()
     }
   }
   if (!memoryAllocationSuccess) {
     ReportFailureOnMainThread(WebAudioDecodeJob::UnknownError);
     return;
   }
 
   nsRefPtr<AudioData> audioData;
-  while ((audioData = audioQueue.PopFront())) {
+  while ((audioData = mAudioQueue.PopFront())) {
     audioData->EnsureAudioBuffer(); // could lead to a copy :(
     AudioDataValue* bufferData = static_cast<AudioDataValue*>
       (audioData->mAudioBuffer->Data());
 
     if (sampleRate != destSampleRate) {
       const uint32_t maxOutSamples = resampledFrames - mDecodeJob.mWriteIndex;
 
       for (uint32_t i = 0; i < audioData->mChannels; ++i) {
@@ -437,85 +460,42 @@ WebAudioDecodeJob::AllocateBuffer()
   for (uint32_t i = 0; i < mChannelBuffers.Length(); ++i) {
     mOutput->SetRawChannelContents(i, mChannelBuffers[i]);
   }
 
   return true;
 }
 
 void
-MediaBufferDecoder::AsyncDecodeMedia(const char* aContentType, uint8_t* aBuffer,
-                                     uint32_t aLength,
-                                     WebAudioDecodeJob& aDecodeJob)
+AsyncDecodeWebAudio(const char* aContentType, uint8_t* aBuffer,
+                    uint32_t aLength, WebAudioDecodeJob& aDecodeJob)
 {
   // Do not attempt to decode the media if we were not successful at sniffing
   // the content type.
   if (!*aContentType ||
       strcmp(aContentType, APPLICATION_OCTET_STREAM) == 0) {
     nsCOMPtr<nsIRunnable> event =
       new ReportResultTask(aDecodeJob,
                            &WebAudioDecodeJob::OnFailure,
                            WebAudioDecodeJob::UnknownContent);
     JS_free(nullptr, aBuffer);
     NS_DispatchToMainThread(event);
     return;
   }
 
-  if (!EnsureThreadPoolInitialized()) {
-    nsCOMPtr<nsIRunnable> event =
-      new ReportResultTask(aDecodeJob,
-                           &WebAudioDecodeJob::OnFailure,
-                           WebAudioDecodeJob::UnknownError);
-    JS_free(nullptr, aBuffer);
-    NS_DispatchToMainThread(event);
-    return;
-  }
-
-  MOZ_ASSERT(mThreadPool);
-
-  nsRefPtr<MediaDecodeTask> task =
-    new MediaDecodeTask(aContentType, aBuffer, aLength, aDecodeJob, mThreadPool);
+  RefPtr<MediaDecodeTask> task =
+    new MediaDecodeTask(aContentType, aBuffer, aLength, aDecodeJob);
   if (!task->CreateReader()) {
     nsCOMPtr<nsIRunnable> event =
       new ReportResultTask(aDecodeJob,
                            &WebAudioDecodeJob::OnFailure,
                            WebAudioDecodeJob::UnknownError);
     NS_DispatchToMainThread(event);
   } else {
-    mThreadPool->Dispatch(task, nsIThreadPool::DISPATCH_NORMAL);
-  }
-}
-
-bool
-MediaBufferDecoder::EnsureThreadPoolInitialized()
-{
-  if (!mThreadPool) {
-    mThreadPool = do_CreateInstance(NS_THREADPOOL_CONTRACTID);
-    if (!mThreadPool) {
-      return false;
-    }
-    mThreadPool->SetName(NS_LITERAL_CSTRING("MediaBufferDecoder"));
-#ifdef XP_WIN
-  // Ensure MSCOM is initialized on the thread pools threads.
-  nsCOMPtr<nsIThreadPoolListener> listener = new MSCOMInitThreadPoolListener();
-  nsresult rv = mThreadPool->SetListener(listener);
-  NS_ENSURE_SUCCESS(rv, nullptr);
-#endif
-  }
-  return true;
-}
-
-void
-MediaBufferDecoder::Shutdown() {
-  if (mThreadPool) {
-    // Setting threadLimit to 0 causes threads to exit when all events have
-    // been run, like nsIThreadPool::Shutdown(), but doesn't run a nested event
-    // loop nor wait until this has happened.
-    mThreadPool->SetThreadLimit(0);
-    mThreadPool = nullptr;
+    task->Reader()->GetTaskQueue()->Dispatch(task);
   }
 }
 
 WebAudioDecodeJob::WebAudioDecodeJob(const nsACString& aContentType,
                                      AudioContext* aContext,
                                      Promise* aPromise,
                                      DecodeSuccessCallback* aSuccessCallback,
                                      DecodeErrorCallback* aFailureCallback)
--- a/dom/media/webaudio/MediaBufferDecoder.h
+++ b/dom/media/webaudio/MediaBufferDecoder.h
@@ -4,17 +4,16 @@
  * 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 MediaBufferDecoder_h_
 #define MediaBufferDecoder_h_
 
 #include "nsWrapperCache.h"
 #include "nsCOMPtr.h"
-#include "nsIThreadPool.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "mozilla/dom/TypedArray.h"
 #include "mozilla/MemoryReporting.h"
 
 namespace mozilla {
 
 namespace dom {
@@ -65,39 +64,15 @@ struct WebAudioDecodeJob MOZ_FINAL
   nsRefPtr<dom::DecodeErrorCallback> mFailureCallback; // can be null
   nsRefPtr<dom::AudioBuffer> mOutput;
   FallibleTArray<ChannelBuffer> mChannelBuffers;
 
 private:
   ~WebAudioDecodeJob();
 };
 
-/**
- * This class is used to decode media buffers on a dedicated threadpool.
- *
- * This class manages the resources that it uses internally (such as the
- * thread-pool) and provides a clean external interface.
- */
-class MediaBufferDecoder
-{
-public:
-  void AsyncDecodeMedia(const char* aContentType, uint8_t* aBuffer,
-                        uint32_t aLength, WebAudioDecodeJob& aDecodeJob);
-
-  ~MediaBufferDecoder() { Shutdown(); }
-  void Shutdown();
-
-  size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const
-  {
-    return 0;
-  }
-
-private:
-  bool EnsureThreadPoolInitialized();
-
-private:
-  nsCOMPtr<nsIThreadPool> mThreadPool;
-};
+void AsyncDecodeWebAudio(const char* aContentType, uint8_t* aBuffer,
+                         uint32_t aLength, WebAudioDecodeJob& aDecodeJob);
 
 }
 
 #endif
 
--- a/dom/media/webm/SoftwareWebMVideoDecoder.h
+++ b/dom/media/webm/SoftwareWebMVideoDecoder.h
@@ -19,17 +19,17 @@ public:
 
   virtual nsresult Init(unsigned int aWidth, unsigned int aHeight) MOZ_OVERRIDE;
 
   virtual bool DecodeVideoFrame(bool &aKeyframeSkip,
                                 int64_t aTimeThreshold) MOZ_OVERRIDE;
 
   virtual void Shutdown() MOZ_OVERRIDE;
 
-  SoftwareWebMVideoDecoder(WebMReader* aReader);
+  explicit SoftwareWebMVideoDecoder(WebMReader* aReader);
   ~SoftwareWebMVideoDecoder();
 
 private:
   nsRefPtr<WebMReader> mReader;
 
   // VPx decoder state
   vpx_codec_ctx_t mVPX;
 };
--- a/dom/media/webm/WebMReader.cpp
+++ b/dom/media/webm/WebMReader.cpp
@@ -739,17 +739,17 @@ bool WebMReader::DecodeOpus(const unsign
   if (channels > 8) {
     return false;
   }
 
   if (mPaddingDiscarded) {
     // Discard padding should be used only on the final packet, so
     // decoding after a padding discard is invalid.
     LOG(PR_LOG_DEBUG, ("Opus error, discard padding on interstitial packet"));
-    GetCallback()->OnNotDecoded(MediaData::AUDIO_DATA, DECODE_ERROR);
+    mHitAudioDecodeError = true;
     return false;
   }
 
   // Maximum value is 63*2880, so there's no chance of overflow.
   int32_t frames_number = opus_packet_get_nb_frames(aData, aLength);
   if (frames_number <= 0) {
     return false; // Invalid packet header.
   }
@@ -793,30 +793,29 @@ bool WebMReader::DecodeOpus(const unsign
     mSkip -= skipFrames;
   }
 
   int64_t discardPadding = 0;
   (void) nestegg_packet_discard_padding(aPacket, &discardPadding);
   if (discardPadding < 0) {
     // Negative discard padding is invalid.
     LOG(PR_LOG_DEBUG, ("Opus error, negative discard padding"));
-    GetCallback()->OnNotDecoded(MediaData::AUDIO_DATA, DECODE_ERROR);
-    return false;
+    mHitAudioDecodeError = true;
   }
   if (discardPadding > 0) {
     CheckedInt64 discardFrames = UsecsToFrames(discardPadding / NS_PER_USEC,
                                                mInfo.mAudio.mRate);
     if (!discardFrames.isValid()) {
       NS_WARNING("Int overflow in DiscardPadding");
       return false;
     }
     if (discardFrames.value() > frames) {
       // Discarding more than the entire packet is invalid.
       LOG(PR_LOG_DEBUG, ("Opus error, discard padding larger than packet"));
-      GetCallback()->OnNotDecoded(MediaData::AUDIO_DATA, DECODE_ERROR);
+      mHitAudioDecodeError = true;
       return false;
     }
     LOG(PR_LOG_DEBUG, ("Opus decoder discarding %d of %d frames",
                        int32_t(discardFrames.value()), frames));
     // Padding discard is only supposed to happen on the final packet.
     // Record the discard so we can return an error if another packet is
     // decoded.
     mPaddingDiscarded = true;
--- a/dom/plugins/base/moz.build
+++ b/dom/plugins/base/moz.build
@@ -93,20 +93,22 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] != 'andr
     # using #defines, which results in Wswitch warnings in gcc-4.6.
     # Therefore, enable FAIL_ON_WARNINGS only on non-Android platforms.
     FAIL_ON_WARNINGS = True
 
 MSVC_ENABLE_PGO = True
 
 LOCAL_INCLUDES += [
     '/dom/base',
+    '/dom/plugins/ipc',
     '/layout/generic',
     '/layout/xul',
     '/widget',
     '/widget/android',
+    '/widget/cocoa',
     '/xpcom/base',
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'android':
     LOCAL_INCLUDES += [
         '/dom/plugins/base/android',
     ]
 
--- a/dom/plugins/base/nsJSNPRuntime.cpp
+++ b/dom/plugins/base/nsJSNPRuntime.cpp
@@ -149,18 +149,17 @@ NPObjWrapper_DelProperty(JSContext *cx, 
 static bool
 NPObjWrapper_SetProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, bool strict,
                          JS::MutableHandle<JS::Value> vp);
 
 static bool
 NPObjWrapper_GetProperty(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp);
 
 static bool
-NPObjWrapper_newEnumerate(JSContext *cx, JS::Handle<JSObject*> obj, JSIterateOp enum_op,
-                          JS::Value *statep, jsid *idp);
+NPObjWrapper_Enumerate(JSContext *cx, JS::Handle<JSObject*> obj, JS::AutoIdVector &properties);
 
 static bool
 NPObjWrapper_Resolve(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
                      bool *resolvedp);
 
 static bool
 NPObjWrapper_Convert(JSContext *cx, JS::Handle<JSObject*> obj, JSType type, JS::MutableHandle<JS::Value> vp);
 
@@ -179,36 +178,57 @@ NPObjWrapper_Construct(JSContext *cx, un
 static bool
 CreateNPObjectMember(NPP npp, JSContext *cx, JSObject *obj, NPObject* npobj,
                      JS::Handle<jsid> id,  NPVariant* getPropertyResult,
                      JS::MutableHandle<JS::Value> vp);
 
 const static js::Class sNPObjectJSWrapperClass =
   {
     NPRUNTIME_JSCLASS_NAME,
-    JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS | JSCLASS_NEW_ENUMERATE,
+    JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS,
     NPObjWrapper_AddProperty,
     NPObjWrapper_DelProperty,
     NPObjWrapper_GetProperty,
     NPObjWrapper_SetProperty,
-    (JSEnumerateOp)NPObjWrapper_newEnumerate,
+    nullptr,
     NPObjWrapper_Resolve,
     NPObjWrapper_Convert,
     NPObjWrapper_Finalize,
     NPObjWrapper_Call,
     nullptr,                                                /* hasInstance */
     NPObjWrapper_Construct,
     nullptr,                                                /* trace */
     JS_NULL_CLASS_SPEC,
     {
       nullptr,                                              /* outerObject */
       nullptr,                                              /* innerObject */
       false,                                                /* isWrappedNative */
       nullptr,                                              /* weakmapKeyDelegateOp */
       NPObjWrapper_ObjectMoved
+    },
+    {
+        nullptr, // lookupGeneric
+        nullptr, // lookupProperty
+        nullptr, // lookupElement
+        nullptr, // defineGeneric
+        nullptr, // defineProperty
+        nullptr, // defineElement
+        nullptr, // getGeneric
+        nullptr, // getProperty
+        nullptr, // getElement
+        nullptr, // setGeneric
+        nullptr, // setProperty
+        nullptr, // setElement
+        nullptr, // getGenericAttributes
+        nullptr, // setGenericAttributes
+        nullptr, // deleteGeneric
+        nullptr, nullptr, // watch/unwatch
+        nullptr, // getElements
+        NPObjWrapper_Enumerate,
+        nullptr,
     }
   };
 
 typedef struct NPObjectMemberPrivate {
     JS::Heap<JSObject *> npobjWrapper;
     JS::Heap<JS::Value> fieldValue;
     JS::Heap<jsid> methodName;
     NPP   npp;
@@ -224,19 +244,18 @@ static bool
 NPObjectMember_Call(JSContext *cx, unsigned argc, JS::Value *vp);
 
 static void
 NPObjectMember_Trace(JSTracer *trc, JSObject *obj);
 
 static const JSClass sNPObjectMemberClass =
   {
     "NPObject Ambiguous Member class", JSCLASS_HAS_PRIVATE | JSCLASS_IMPLEMENTS_BARRIERS,
-    nullptr, nullptr,
-    JS_PropertyStub, JS_StrictPropertyStub, nullptr,
-    nullptr, NPObjectMember_Convert,
+    nullptr, nullptr, nullptr, nullptr,
+    nullptr, nullptr, NPObjectMember_Convert,
     NPObjectMember_Finalize, NPObjectMember_Call,
     nullptr, nullptr, NPObjectMember_Trace
   };
 
 static void
 OnWrapperDestroyed();
 
 static void
@@ -1560,99 +1579,55 @@ CallNPMethod(JSContext *cx, unsigned arg
   JS::CallArgs args = JS::CallArgsFromVp(argc, vp);
   JS::Rooted<JSObject*> obj(cx, JS_THIS_OBJECT(cx, vp));
   if (!obj)
       return false;
 
   return CallNPMethodInternal(cx, obj, args.length(), args.array(), vp, false);
 }
 
-struct NPObjectEnumerateState {
-  uint32_t     index;
-  uint32_t     length;
-  NPIdentifier *value;
-};
-
 static bool
-NPObjWrapper_newEnumerate(JSContext *cx, JS::Handle<JSObject*> obj, JSIterateOp enum_op,
-                          JS::Value *statep, jsid *idp)
+NPObjWrapper_Enumerate(JSContext *cx, JS::Handle<JSObject*> obj,
+                       JS::AutoIdVector &properties)
 {
   NPObject *npobj = GetNPObject(cx, obj);
-  NPIdentifier *enum_value;
-  uint32_t length;
-  NPObjectEnumerateState *state;
-
   if (!npobj || !npobj->_class) {
     ThrowJSException(cx, "Bad NPObject as private data!");
     return false;
   }
 
   PluginDestructionGuard pdg(LookupNPP(npobj));
 
-  NS_ASSERTION(statep, "Must have a statep to enumerate!");
-
-  switch(enum_op) {
-  case JSENUMERATE_INIT:
-  case JSENUMERATE_INIT_ALL:
-    state = new NPObjectEnumerateState();
-    if (!state) {
-      ThrowJSException(cx, "Memory allocation failed for "
-                       "NPObjectEnumerateState!");
-
-      return false;
-    }
-
-    if (!NP_CLASS_STRUCT_VERSION_HAS_ENUM(npobj->_class) ||
-        !npobj->_class->enumerate) {
-      enum_value = 0;
-      length = 0;
-    } else if (!npobj->_class->enumerate(npobj, &enum_value, &length)) {
-      delete state;
-
-      if (ReportExceptionIfPending(cx)) {
-        // ReportExceptionIfPending returns a return value, which is true
-        // if no exception was thrown. In that case, throw our own.
-        ThrowJSException(cx, "Error enumerating properties on scriptable "
-                             "plugin object");
-      }
-
-      return false;
+  if (!NP_CLASS_STRUCT_VERSION_HAS_ENUM(npobj->_class) ||
+      !npobj->_class->enumerate) {
+    return true;
+  }
+
+  NPIdentifier *identifiers;
+  uint32_t length;
+  if (!npobj->_class->enumerate(npobj, &identifiers, &length)) {
+    if (ReportExceptionIfPending(cx)) {
+      // ReportExceptionIfPending returns a return value, which is true
+      // if no exception was thrown. In that case, throw our own.
+      ThrowJSException(cx, "Error enumerating properties on scriptable "
+                           "plugin object");
     }
-
-    state->value = enum_value;
-    state->length = length;
-    state->index = 0;
-    *statep = PRIVATE_TO_JSVAL(state);
-    if (idp) {
-      *idp = INT_TO_JSID(length);
-    }
-
-    break;
-
-  case JSENUMERATE_NEXT:
-    state = (NPObjectEnumerateState *)statep->toPrivate();
-    enum_value = state->value;
-    length = state->length;
-    if (state->index != length) {
-      *idp = NPIdentifierToJSId(enum_value[state->index++]);
-      return true;
-    }
-
-    // FALL THROUGH
-
-  case JSENUMERATE_DESTROY:
-    state = (NPObjectEnumerateState *)statep->toPrivate();
-    if (state->value)
-      PR_Free(state->value);
-    delete state;
-    *statep = JSVAL_NULL;
-
-    break;
+    return false;
   }
 
+  if (!properties.reserve(length))
+    return false;
+
+  JS::Rooted<jsid> id(cx);
+  for (uint32_t i = 0; i < length; i++) {
+    id = NPIdentifierToJSId(identifiers[i]);
+    properties.infallibleAppend(id);
+  }
+
+  PR_Free(identifiers);
   return true;
 }
 
 static bool
 NPObjWrapper_Resolve(JSContext *cx, JS::Handle<JSObject*> obj, JS::Handle<jsid> id,
                      bool *resolvedp)
 {
   if (JSID_IS_SYMBOL(id))
--- a/dom/plugins/base/nsNPAPIPlugin.cpp
+++ b/dom/plugins/base/nsNPAPIPlugin.cpp
@@ -47,16 +47,17 @@
 #include "nsIObserverService.h"
 #include <prinrval.h>
 
 #ifdef MOZ_WIDGET_COCOA
 #include <Carbon/Carbon.h>
 #include <ApplicationServices/ApplicationServices.h>
 #include <OpenGL/OpenGL.h>
 #include "nsCocoaFeatures.h"
+#include "PluginUtilsOSX.h"
 #endif
 
 // needed for nppdf plugin
 #if (MOZ_WIDGET_GTK)
 #include <gdk/gdk.h>
 #include <gdk/gdkx.h>
 #if (MOZ_WIDGET_GTK == 2)
 #include "gtk2xtbin.h"
@@ -2116,23 +2117,23 @@ NPError
 
   case NPNVsupportsCoreGraphicsBool: {
     *(NPBool*)result = true;
 
     return NPERR_NO_ERROR;
   }
 
   case NPNVsupportsCoreAnimationBool: {
-    *(NPBool*)result = nsCocoaFeatures::SupportCoreAnimationPlugins();
+    *(NPBool*)result = true;
 
     return NPERR_NO_ERROR;
   }
 
   case NPNVsupportsInvalidatingCoreAnimationBool: {
-    *(NPBool*)result = nsCocoaFeatures::SupportCoreAnimationPlugins();
+    *(NPBool*)result = true;
 
     return NPERR_NO_ERROR;
   }
 
   case NPNVsupportsCompositingCoreAnimationPluginsBool: {
     *(NPBool*)result = PR_TRUE;
 
     return NPERR_NO_ERROR;
@@ -2779,21 +2780,60 @@ void
     return;
 
   inst->UnscheduleTimer(timerID);
 }
 
 NPError
 _popupcontextmenu(NPP instance, NPMenu* menu)
 {
+#ifdef MOZ_WIDGET_COCOA
   nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance *)instance->ndata;
-  if (!inst)
+
+  double pluginX, pluginY;
+  double screenX, screenY;
+
+  const NPCocoaEvent* currentEvent = static_cast<NPCocoaEvent*>(inst->GetCurrentEvent());
+  if (!currentEvent) {
     return NPERR_GENERIC_ERROR;
-
-  return inst->PopUpContextMenu(menu);
+  }
+
+  // Ensure that the events has an x/y value.
+  if (currentEvent->type != NPCocoaEventMouseDown    &&
+      currentEvent->type != NPCocoaEventMouseUp      &&
+      currentEvent->type != NPCocoaEventMouseMoved   &&
+      currentEvent->type != NPCocoaEventMouseEntered &&
+      currentEvent->type != NPCocoaEventMouseExited  &&
+      currentEvent->type != NPCocoaEventMouseDragged) {
+      return NPERR_GENERIC_ERROR;
+  }
+
+  pluginX = currentEvent->data.mouse.pluginX;
+  pluginY = currentEvent->data.mouse.pluginY;
+
+  if ((pluginX < 0.0) || (pluginY < 0.0))
+    return NPERR_GENERIC_ERROR;
+
+  NPBool success = _convertpoint(instance,
+                                 pluginX,  pluginY, NPCoordinateSpacePlugin,
+                                 &screenX, &screenY, NPCoordinateSpaceScreen);
+
+  if (success) {
+    return mozilla::plugins::PluginUtilsOSX::ShowCocoaContextMenu(menu,
+                                    screenX, screenY,
+                                    nullptr,
+                                    nullptr);
+  } else {
+    NS_WARNING("Convertpoint failed, could not created contextmenu.");
+    return NPERR_GENERIC_ERROR;
+  }
+#else
+    NS_WARNING("Not supported on this platform!");
+    return NPERR_GENERIC_ERROR;
+#endif
 }
 
 NPBool
 _convertpoint(NPP instance, double sourceX, double sourceY, NPCoordinateSpace sourceSpace, double *destX, double *destY, NPCoordinateSpace destSpace)
 {
   nsNPAPIPluginInstance *inst = (nsNPAPIPluginInstance *)instance->ndata;
   if (!inst)
     return false;
--- a/dom/plugins/base/nsNPAPIPluginInstance.cpp
+++ b/dom/plugins/base/nsNPAPIPluginInstance.cpp
@@ -168,35 +168,36 @@ using namespace mozilla;
 using namespace mozilla::plugins::parent;
 using namespace mozilla::layers;
 
 static NS_DEFINE_IID(kIOutputStreamIID, NS_IOUTPUTSTREAM_IID);
 
 NS_IMPL_ISUPPORTS0(nsNPAPIPluginInstance)
 
 nsNPAPIPluginInstance::nsNPAPIPluginInstance()
-  :
-    mDrawingModel(kDefaultDrawingModel),
+  : mDrawingModel(kDefaultDrawingModel)
 #ifdef MOZ_WIDGET_ANDROID
-    mANPDrawingModel(0),
-    mFullScreenOrientation(dom::eScreenOrientation_LandscapePrimary),
-    mWakeLocked(false),
-    mFullScreen(false),
-    mInverted(false),
+  , mANPDrawingModel(0)
+  , mFullScreenOrientation(dom::eScreenOrientation_LandscapePrimary)
+  , mWakeLocked(false)
+  , mFullScreen(false)
+  , mInverted(false)
 #endif
-    mRunning(NOT_STARTED),
-    mWindowless(false),
-    mTransparent(false),
-    mCached(false),
-    mUsesDOMForCursor(false),
-    mInPluginInitCall(false),
-    mPlugin(nullptr),
-    mMIMEType(nullptr),
-    mOwner(nullptr),
-    mCurrentPluginEvent(nullptr)
+  , mRunning(NOT_STARTED)
+  , mWindowless(false)
+  , mTransparent(false)
+  , mCached(false)
+  , mUsesDOMForCursor(false)
+  , mInPluginInitCall(false)
+  , mPlugin(nullptr)
+  , mMIMEType(nullptr)
+  , mOwner(nullptr)
+#ifdef XP_MACOSX
+  , mCurrentPluginEvent(nullptr)
+#endif
 #ifdef MOZ_WIDGET_ANDROID
   , mOnScreen(true)
 #endif
   , mHaveJavaC2PJSObjectQuirk(false)
   , mCachedParamLength(0)
   , mCachedParamNames(nullptr)
   , mCachedParamValues(nullptr)
 {
@@ -666,31 +667,35 @@ nsresult nsNPAPIPluginInstance::HandleEv
   if (!mPlugin || !mPlugin->GetLibrary())
     return NS_ERROR_FAILURE;
 
   NPPluginFuncs* pluginFunctions = mPlugin->PluginFuncs();
 
   int16_t tmpResult = kNPEventNotHandled;
 
   if (pluginFunctions->event) {
+#ifdef XP_MACOSX
     mCurrentPluginEvent = event;
+#endif
 #if defined(XP_WIN)
     NS_TRY_SAFE_CALL_RETURN(tmpResult, (*pluginFunctions->event)(&mNPP, event), this,
                             aSafeToReenterGecko);
 #else
     MAIN_THREAD_JNI_REF_GUARD;
     tmpResult = (*pluginFunctions->event)(&mNPP, event);
 #endif
     NPP_PLUGIN_LOG(PLUGIN_LOG_NOISY,
       ("NPP HandleEvent called: this=%p, npp=%p, event=%p, return=%d\n",
       this, &mNPP, event, tmpResult));
 
     if (result)
       *result = tmpResult;
+#ifdef XP_MACOSX
     mCurrentPluginEvent = nullptr;
+#endif
   }
 
   return NS_OK;
 }
 
 nsresult nsNPAPIPluginInstance::GetValueFromPlugin(NPPVariable variable, void* value)
 {
   if (!mPlugin || !mPlugin->GetLibrary())
@@ -1147,17 +1152,18 @@ bool
 nsNPAPIPluginInstance::ShouldCache()
 {
   return mCached;
 }
 
 nsresult
 nsNPAPIPluginInstance::IsWindowless(bool* isWindowless)
 {
-#ifdef MOZ_WIDGET_ANDROID
+#if defined(MOZ_WIDGET_ANDROID) || defined(XP_MACOSX)
+  // All OS X plugins are windowless.
   // On android, pre-honeycomb, all plugins are treated as windowless.
   *isWindowless = true;
 #else
   *isWindowless = mWindowless;
 #endif
   return NS_OK;
 }
 
@@ -1506,33 +1512,23 @@ nsNPAPIPluginInstance::UnscheduleTimer(u
 
   // remove timer struct from array
   mTimers.RemoveElementAt(index);
 
   // delete timer
   delete t;
 }
 
-// Show the context menu at the location for the current event.
-// This can only be called from within an NPP_SendEvent call.
-NPError
-nsNPAPIPluginInstance::PopUpContextMenu(NPMenu* menu)
-{
-  if (mOwner && mCurrentPluginEvent)
-    return mOwner->ShowNativeContextMenu(menu, mCurrentPluginEvent);
-
-  return NPERR_GENERIC_ERROR;
-}
-
 NPBool
 nsNPAPIPluginInstance::ConvertPoint(double sourceX, double sourceY, NPCoordinateSpace sourceSpace,
                                     double *destX, double *destY, NPCoordinateSpace destSpace)
 {
-  if (mOwner)
+  if (mOwner) {
     return mOwner->ConvertPoint(sourceX, sourceY, sourceSpace, destX, destY, destSpace);
+  }
 
   return false;
 }
 
 nsresult
 nsNPAPIPluginInstance::GetDOMElement(nsIDOMElement* *result)
 {
   if (!mOwner) {
--- a/dom/plugins/base/nsNPAPIPluginInstance.h
+++ b/dom/plugins/base/nsNPAPIPluginInstance.h
@@ -129,16 +129,20 @@ public:
 
   NPError SetUsesDOMForCursor(bool aUsesDOMForCursor);
   bool UsesDOMForCursor();
 
   void SetDrawingModel(NPDrawingModel aModel);
   void RedrawPlugin();
 #ifdef XP_MACOSX
   void SetEventModel(NPEventModel aModel);
+
+  void* GetCurrentEvent() {
+    return mCurrentPluginEvent;
+  }
 #endif
 
 #ifdef MOZ_WIDGET_ANDROID
   void NotifyForeground(bool aForeground);
   void NotifyOnScreen(bool aOnScreen);
   void MemoryPressure();
   void NotifyFullScreen(bool aFullScreen);
   void NotifySize(nsIntSize size);
@@ -256,17 +260,16 @@ public:
 
   nsresult IsPrivateBrowsing(bool *aEnabled);
 
   nsresult GetDOMElement(nsIDOMElement* *result);
 
   nsNPAPITimer* TimerWithID(uint32_t id, uint32_t* index);
   uint32_t      ScheduleTimer(uint32_t interval, NPBool repeat, void (*timerFunc)(NPP npp, uint32_t timerID));
   void          UnscheduleTimer(uint32_t timerID);
-  NPError       PopUpContextMenu(NPMenu* menu);
   NPBool        ConvertPoint(double sourceX, double sourceY, NPCoordinateSpace sourceSpace, double *destX, double *destY, NPCoordinateSpace destSpace);
 
 
   nsTArray<nsNPAPIPluginStreamListener*> *StreamListeners();
 
   nsTArray<nsPluginStreamListenerPeer*> *FileCachedStreamListeners();
 
   nsresult AsyncSetWindow(NPWindow& window);
@@ -363,18 +366,20 @@ private:
   char* mMIMEType;
 
   // Weak pointer to the owner. The owner nulls this out (by calling
   // InvalidateOwner()) when it's no longer our owner.
   nsPluginInstanceOwner *mOwner;
 
   nsTArray<nsNPAPITimer*> mTimers;
 
+#ifdef XP_MACOSX
   // non-null during a HandleEvent call
   void* mCurrentPluginEvent;
+#endif
 
   // Timestamp for the last time this plugin was stopped.
   // This is only valid when the plugin is actually stopped!
   mozilla::TimeStamp mStopTime;
 
 #ifdef MOZ_WIDGET_ANDROID
   void EnsureSharedTexture();
   mozilla::TemporaryRef<mozilla::gl::AndroidSurfaceTexture> CreateSurfaceTexture();
--- a/dom/plugins/base/nsPluginInstanceOwner.cpp
+++ b/dom/plugins/base/nsPluginInstanceOwner.cpp
@@ -52,30 +52,33 @@ using mozilla::DefaultXDisplay;
 #include "EGLUtils.h"
 #include "nsIContentInlines.h"
 #include "mozilla/MiscEvents.h"
 #include "mozilla/MouseEvents.h"
 #include "mozilla/TextEvents.h"
 #include "mozilla/dom/HTMLObjectElementBinding.h"
 #include "mozilla/dom/TabChild.h"
 #include "nsFrameSelection.h"
+#include "PuppetWidget.h"
+#include "nsPIWindowRoot.h"
 
 #include "nsContentCID.h"
 #include "nsWidgetsCID.h"
 static NS_DEFINE_CID(kWidgetCID, NS_CHILD_CID);
 static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
 
 #ifdef XP_WIN
 #include <wtypes.h>
 #include <winuser.h>
 #endif
 
 #ifdef XP_MACOSX
-#include <Carbon/Carbon.h>
-#include "nsPluginUtilsOSX.h"
+#include "ComplexTextInputPanel.h"
+#include "nsIDOMXULDocument.h"
+#include "nsIDOMXULCommandDispatcher.h"
 #endif
 
 #ifdef MOZ_WIDGET_GTK
 #include <gdk/gdk.h>
 #include <gtk/gtk.h>
 #endif
 
 #ifdef MOZ_WIDGET_ANDROID
@@ -90,16 +93,20 @@ using namespace mozilla::dom;
 #include <android/log.h>
 #define LOG(args...)  __android_log_print(ANDROID_LOG_INFO, "GeckoPlugins" , ## args)
 #endif
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::layers;
 
+static inline nsPoint AsNsPoint(const nsIntPoint &p) {
+  return nsPoint(p.x, p.y);
+}
+
 // special class for handeling DOM context menu events because for
 // some reason it starves other mouse events if implemented on the
 // same class
 class nsPluginDOMContextMenuListener : public nsIDOMEventListener
 {
   virtual ~nsPluginDOMContextMenuListener();
 
 public:
@@ -325,18 +332,21 @@ nsPluginInstanceOwner::nsPluginInstanceO
 
   mPluginFrame = nullptr;
   mContent = nullptr;
   mWidgetCreationComplete = false;
 #ifdef XP_MACOSX
   memset(&mCGPluginPortCopy, 0, sizeof(NP_CGContext));
   mInCGPaintLevel = 0;
   mSentInitialTopLevelWindowEvent = false;
+  mLastWindowIsActive = false;
+  mLastContentFocused = false;
+  mLastScaleFactor = 1.0;
   mColorProfile = nullptr;
-  mPluginPortChanged = false;
+  mShouldBlurOnActivate = false;
 #endif
   mContentFocused = false;
   mWidgetVisible = true;
   mPluginWindowVisible = false;
   mPluginDocumentActiveState = true;
   mLastMouseDownButtonType = -1;
 
 #ifdef XP_MACOSX
@@ -724,43 +734,262 @@ NS_IMETHODIMP nsPluginInstanceOwner::Set
 #ifdef XP_MACOSX
   mEventModel = static_cast<NPEventModel>(eventModel);
   return NS_OK;
 #else
   return NS_ERROR_NOT_IMPLEMENTED;
 #endif
 }
 
+// This is no longer used, just leaving it here so we don't have to change
+// the nsIPluginInstanceOwner interface.
 NPError nsPluginInstanceOwner::ShowNativeContextMenu(NPMenu* menu, void* event)
 {
-  if (!menu || !event)
-    return NPERR_GENERIC_ERROR;
+  return NPERR_GENERIC_ERROR;
+}
 
 #ifdef XP_MACOSX
-  if (GetEventModel() != NPEventModelCocoa)
-    return NPERR_INCOMPATIBLE_VERSION_ERROR;
-
-  return NS_NPAPI_ShowCocoaContextMenu(static_cast<void*>(menu), mWidget,
-                                       static_cast<NPCocoaEvent*>(event));
-#else
-  return NPERR_INCOMPATIBLE_VERSION_ERROR;
-#endif
+NPBool nsPluginInstanceOwner::ConvertPointPuppet(PuppetWidget *widget,
+                                                 nsPluginFrame* pluginFrame,
+                                                 double sourceX, double sourceY,
+                                                 NPCoordinateSpace sourceSpace,
+                                                 double *destX, double *destY,
+                                                 NPCoordinateSpace destSpace)
+{
+  NS_ENSURE_TRUE(widget && widget->GetOwningTabChild() && pluginFrame, false);
+  // Caller has to want a result.
+  NS_ENSURE_TRUE(destX || destY, false);
+
+  if (sourceSpace == destSpace) {
+    if (destX) {
+      *destX = sourceX;
+    }
+    if (destY) {
+      *destY = sourceY;
+    }
+    return true;
+  }
+
+  nsPresContext* presContext = pluginFrame->PresContext();
+  double scaleFactor = double(nsPresContext::AppUnitsPerCSSPixel())/
+    presContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom();
+
+  PuppetWidget *puppetWidget = static_cast<PuppetWidget*>(widget);
+  PuppetWidget *rootWidget = static_cast<PuppetWidget*>(widget->GetTopLevelWidget());
+  if (!rootWidget) {
+    return false;
+  }
+  nsPoint chromeSize = AsNsPoint(rootWidget->GetChromeDimensions()) / scaleFactor;
+  nsIntSize intScreenDims = rootWidget->GetScreenDimensions();
+  nsSize screenDims = nsSize(intScreenDims.width / scaleFactor,
+                             intScreenDims.height / scaleFactor);
+  int32_t screenH = screenDims.height;
+  nsPoint windowPosition = AsNsPoint(rootWidget->GetWindowPosition()) / scaleFactor;
+
+  // Window size is tab size + chrome size.
+  nsIntRect tabContentBounds;
+  NS_ENSURE_SUCCESS(puppetWidget->GetBounds(tabContentBounds), false);
+  tabContentBounds.ScaleInverseRoundOut(scaleFactor);
+  int32_t windowH = tabContentBounds.height + int(chromeSize.y);
+
+  // This is actually relative to window-chrome.
+  nsPoint pluginPosition = AsNsPoint(pluginFrame->GetScreenRect().TopLeft());
+
+  // Convert (sourceX, sourceY) to 'real' (not PuppetWidget) screen space.
+  // In OSX, the Y-axis increases upward, which is the reverse of ours.
+  // We want OSX coordinates for window and screen so those equations are swapped.
+  nsPoint sourcePoint(sourceX, sourceY);
+  nsPoint screenPoint;
+  switch (sourceSpace) {
+    case NPCoordinateSpacePlugin:
+      screenPoint = sourcePoint + pluginFrame->GetContentRectRelativeToSelf().TopLeft() +
+        chromeSize + pluginPosition + windowPosition;
+      break;
+    case NPCoordinateSpaceWindow:
+      screenPoint = nsPoint(sourcePoint.x, windowH-sourcePoint.y) +
+        windowPosition;
+      break;
+    case NPCoordinateSpaceFlippedWindow:
+      screenPoint = sourcePoint + windowPosition;
+      break;
+    case NPCoordinateSpaceScreen:
+      screenPoint = nsPoint(sourcePoint.x, screenH-sourcePoint.y);
+      break;
+    case NPCoordinateSpaceFlippedScreen:
+      screenPoint = sourcePoint;
+      break;
+    default:
+      return false;
+  }
+
+  // Convert from screen to dest space.
+  nsPoint destPoint;
+  switch (destSpace) {
+    case NPCoordinateSpacePlugin:
+      destPoint = screenPoint - pluginFrame->GetContentRectRelativeToSelf().TopLeft() -
+        chromeSize - pluginPosition - windowPosition;
+      break;
+    case NPCoordinateSpaceWindow:
+      destPoint = screenPoint - windowPosition;
+      destPoint.y = windowH - destPoint.y;
+      break;
+    case NPCoordinateSpaceFlippedWindow:
+      destPoint = screenPoint - windowPosition;
+      break;
+    case NPCoordinateSpaceScreen:
+      destPoint = nsPoint(screenPoint.x, screenH-screenPoint.y);
+      break;
+    case NPCoordinateSpaceFlippedScreen:
+      destPoint = screenPoint;
+      break;
+    default:
+      return false;
+  }
+
+  if (destX) {
+    *destX = destPoint.x;
+  }
+  if (destY) {
+    *destY = destPoint.y;
+  }
+
+  return true;
 }
 
+NPBool nsPluginInstanceOwner::ConvertPointNoPuppet(nsIWidget *widget,
+                                                   nsPluginFrame* pluginFrame,
+                                                   double sourceX, double sourceY,
+                                                   NPCoordinateSpace sourceSpace,
+                                                   double *destX, double *destY,
+                                                   NPCoordinateSpace destSpace)
+{
+  NS_ENSURE_TRUE(widget && pluginFrame, false);
+  // Caller has to want a result.
+  NS_ENSURE_TRUE(destX || destY, false);
+
+  if (sourceSpace == destSpace) {
+    if (destX) {
+      *destX = sourceX;
+    }
+    if (destY) {
+      *destY = sourceY;
+    }
+    return true;
+  }
+
+  nsPresContext* presContext = pluginFrame->PresContext();
+  double scaleFactor = double(nsPresContext::AppUnitsPerCSSPixel())/
+    presContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom();
+
+  nsCOMPtr<nsIScreenManager> screenMgr = do_GetService("@mozilla.org/gfx/screenmanager;1");
+  if (!screenMgr) {
+    return false;
+  }
+  nsCOMPtr<nsIScreen> screen;
+  screenMgr->ScreenForNativeWidget(widget->GetNativeData(NS_NATIVE_WINDOW), getter_AddRefs(screen));
+  if (!screen) {
+    return false;
+  }
+
+  int32_t screenX, screenY, screenWidth, screenHeight;
+  screen->GetRect(&screenX, &screenY, &screenWidth, &screenHeight);
+  screenHeight /= scaleFactor;
+
+  nsIntRect windowScreenBounds;
+  NS_ENSURE_SUCCESS(widget->GetScreenBounds(windowScreenBounds), false);
+  windowScreenBounds.ScaleInverseRoundOut(scaleFactor);
+  int32_t windowX = windowScreenBounds.x;
+  int32_t windowY = windowScreenBounds.y;
+  int32_t windowHeight = windowScreenBounds.height;
+
+  nsIntRect pluginScreenRect = pluginFrame->GetScreenRect();
+
+  double screenXGecko, screenYGecko;
+  switch (sourceSpace) {
+    case NPCoordinateSpacePlugin:
+      screenXGecko = pluginScreenRect.x + sourceX;
+      screenYGecko = pluginScreenRect.y + sourceY;
+      break;
+    case NPCoordinateSpaceWindow:
+      screenXGecko = windowX + sourceX;
+      screenYGecko = windowY + (windowHeight - sourceY);
+      break;
+    case NPCoordinateSpaceFlippedWindow:
+      screenXGecko = windowX + sourceX;
+      screenYGecko = windowY + sourceY;
+      break;
+    case NPCoordinateSpaceScreen:
+      screenXGecko = sourceX;
+      screenYGecko = screenHeight - sourceY;
+      break;
+    case NPCoordinateSpaceFlippedScreen:
+      screenXGecko = sourceX;
+      screenYGecko = sourceY;
+      break;
+    default:
+      return false;
+  }
+
+  double destXCocoa, destYCocoa;
+  switch (destSpace) {
+    case NPCoordinateSpacePlugin:
+      destXCocoa = screenXGecko - pluginScreenRect.x;
+      destYCocoa = screenYGecko - pluginScreenRect.y;
+      break;
+    case NPCoordinateSpaceWindow:
+      destXCocoa = screenXGecko - windowX;
+      destYCocoa = windowHeight - (screenYGecko - windowY);
+      break;
+    case NPCoordinateSpaceFlippedWindow:
+      destXCocoa = screenXGecko - windowX;
+      destYCocoa = screenYGecko - windowY;
+      break;
+    case NPCoordinateSpaceScreen:
+      destXCocoa = screenXGecko;
+      destYCocoa = screenHeight - screenYGecko;
+      break;
+    case NPCoordinateSpaceFlippedScreen:
+      destXCocoa = screenXGecko;
+      destYCocoa = screenYGecko;
+      break;
+    default:
+      return false;
+  }
+
+  if (destX) {
+    *destX = destXCocoa;
+  }
+  if (destY) {
+    *destY = destYCocoa;
+  }
+
+  return true;
+}
+#endif // XP_MACOSX
+
 NPBool nsPluginInstanceOwner::ConvertPoint(double sourceX, double sourceY, NPCoordinateSpace sourceSpace,
                                            double *destX, double *destY, NPCoordinateSpace destSpace)
 {
 #ifdef XP_MACOSX
-  if (!mWidget)
+  if (!mPluginFrame) {
     return false;
-
-  return NS_NPAPI_ConvertPointCocoa(mWidget->GetNativeData(NS_NATIVE_WIDGET),
-                                    sourceX, sourceY, sourceSpace, destX, destY, destSpace);
+  }
+
+  MOZ_ASSERT(mPluginFrame->GetNearestWidget());
+
+  if (nsIWidget::UsePuppetWidgets()) {
+    return ConvertPointPuppet(static_cast<PuppetWidget*>(mPluginFrame->GetNearestWidget()),
+                               mPluginFrame, sourceX, sourceY, sourceSpace,
+                               destX, destY, destSpace);
+  }
+
+  return ConvertPointNoPuppet(mPluginFrame->GetNearestWidget(),
+                              mPluginFrame, sourceX, sourceY, sourceSpace,
+                              destX, destY, destSpace);
 #else
-  // we should implement this for all platforms
   return false;
 #endif
 }
 
 NS_IMETHODIMP nsPluginInstanceOwner::GetTagType(nsPluginTagType *result)
 {
   NS_ENSURE_ARG_POINTER(result);
 
@@ -999,38 +1228,22 @@ void* nsPluginInstanceOwner::GetPluginPo
 {
   if (GetDrawingModel() == NPDrawingModelCoreGraphics ||
       GetDrawingModel() == NPDrawingModelCoreAnimation ||
       GetDrawingModel() == NPDrawingModelInvalidatingCoreAnimation)
     return &mCGPluginPortCopy;
   return nullptr;
 }
 
-// Currently (on OS X in Cocoa widgets) any changes made as a result of
-// calling GetPluginPortFromWidget() are immediately reflected in the NPWindow
-// structure that has been passed to the plugin via SetWindow().  This is
-// because calls to nsChildView::GetNativeData(NS_NATIVE_PLUGIN_PORT_CG)
-// always return a pointer to the same internal (private) object, but may
-// make changes inside that object.  All calls to GetPluginPortFromWidget() made while
-// the plugin is active (i.e. excluding those made at our initialization)
-// need to take this into account.  The easiest way to do so is to replace
-// them with calls to SetPluginPortAndDetectChange().  This method keeps track
-// of when calls to GetPluginPortFromWidget() result in changes, and sets a flag to make
-// sure SetWindow() gets called the next time through FixUpPluginWindow(), so
-// that the plugin is notified of these changes.
-void* nsPluginInstanceOwner::SetPluginPortAndDetectChange()
+void nsPluginInstanceOwner::SetPluginPort()
 {
-  if (!mPluginWindow)
-    return nullptr;
-  void* pluginPort = GetPluginPortFromWidget();
-  if (!pluginPort)
-    return nullptr;
+  void* pluginPort = GetPluginPort();
+  if (!pluginPort || !mPluginWindow)
+    return;
   mPluginWindow->window = pluginPort;
-
-  return mPluginWindow->window;
 }
 
 void nsPluginInstanceOwner::BeginCGPaint()
 {
   ++mInCGPaintLevel;
 }
 
 void nsPluginInstanceOwner::EndCGPaint()
@@ -1266,17 +1479,16 @@ nsresult nsPluginInstanceOwner::Dispatch
   if (!mPluginWindow || (mPluginWindow->type == NPWindowTypeWindow)) {
     // continue only for cases without child window
     return aFocusEvent->PreventDefault(); // consume event
   }
 #endif
 
   WidgetEvent* theEvent = aFocusEvent->GetInternalNSEvent();
   if (theEvent) {
-    // we only care about the message in ProcessEvent
     WidgetGUIEvent focusEvent(theEvent->mFlags.mIsTrusted, theEvent->message,
                               nullptr);
     nsEventStatus rv = ProcessEvent(focusEvent);
     if (nsEventStatus_eConsumeNoDefault == rv) {
       aFocusEvent->PreventDefault();
       aFocusEvent->StopPropagation();
     }
   }
@@ -1390,40 +1602,45 @@ nsresult nsPluginInstanceOwner::Dispatch
 
 nsresult
 nsPluginInstanceOwner::HandleEvent(nsIDOMEvent* aEvent)
 {
   NS_ASSERTION(mInstance, "Should have a valid plugin instance or not receive events.");
 
   nsAutoString eventType;
   aEvent->GetType(eventType);
+
+#ifdef XP_MACOSX
+  if (eventType.EqualsLiteral("activate") ||
+      eventType.EqualsLiteral("deactivate")) {
+    WindowFocusMayHaveChanged();
+    return NS_OK;
+  }
+  if (eventType.EqualsLiteral("MozPerformDelayedBlur")) {
+    if (mShouldBlurOnActivate) {
+      WidgetGUIEvent blurEvent(true, NS_BLUR_CONTENT, nullptr);
+      ProcessEvent(blurEvent);
+      mShouldBlurOnActivate = false;
+    }
+    return NS_OK;
+  }
+#endif
+
   if (eventType.EqualsLiteral("focus")) {
     mContentFocused = true;
     return DispatchFocusToPlugin(aEvent);
   }
   if (eventType.EqualsLiteral("blur")) {
     mContentFocused = false;
     return DispatchFocusToPlugin(aEvent);
   }
   if (eventType.EqualsLiteral("mousedown")) {
     return ProcessMouseDown(aEvent);
   }
   if (eventType.EqualsLiteral("mouseup")) {
-    // Don't send a mouse-up event to the plugin if its button type doesn't
-    // match that of the preceding mouse-down event (if any).  This kind of
-    // mismatch can happen if the previous mouse-down event was sent to a DOM
-    // element above the plugin, the mouse is still above the plugin, and the
-    // mouse-down event caused the element to disappear.  See bug 627649 and
-    // bug 909678.
-    WidgetMouseEvent* mouseEvent = aEvent->GetInternalNSEvent()->AsMouseEvent();
-    if (mouseEvent &&
-        static_cast<int>(mouseEvent->button) != mLastMouseDownButtonType) {
-      aEvent->PreventDefault();
-      return NS_OK;
-    }
     return DispatchMouseToPlugin(aEvent);
   }
   if (eventType.EqualsLiteral("mousemove")) {
     return DispatchMouseToPlugin(aEvent, true);
   }
   if (eventType.EqualsLiteral("click") ||
       eventType.EqualsLiteral("dblclick") ||
       eventType.EqualsLiteral("mouseover") ||
@@ -1459,123 +1676,296 @@ static unsigned int XInputEventState(con
   if (anEvent.IsShift()) state |= ShiftMask;
   if (anEvent.IsControl()) state |= ControlMask;
   if (anEvent.IsAlt()) state |= Mod1Mask;
   if (anEvent.IsMeta()) state |= Mod4Mask;
   return state;
 }
 #endif
 
+#ifdef XP_MACOSX
+
+// Returns whether or not content is the content that is or would be
+// focused if the top-level chrome window was active.
+static bool
+ContentIsFocusedWithinWindow(nsIContent* aContent)
+{
+  nsPIDOMWindow* outerWindow = aContent->OwnerDoc()->GetWindow();
+  if (!outerWindow) {
+    return false;
+  }
+
+  nsPIDOMWindow* rootWindow = outerWindow->GetPrivateRoot();
+  if (!rootWindow) {
+    return false;
+  }
+
+  nsFocusManager* fm = nsFocusManager::GetFocusManager();
+  if (!fm) {
+    return false;
+  }
+
+  nsCOMPtr<nsPIDOMWindow> focusedFrame;
+  nsCOMPtr<nsIContent> focusedContent = fm->GetFocusedDescendant(rootWindow, true, getter_AddRefs(focusedFrame));
+  return (focusedContent.get() == aContent);
+}
+
+static NPCocoaEventType
+CocoaEventTypeForEvent(const WidgetGUIEvent& anEvent, nsIFrame* aObjectFrame)
+{
+  const NPCocoaEvent* event = static_cast<const NPCocoaEvent*>(anEvent.mPluginEvent);
+  if (event) {
+    return event->type;
+  }
+
+  switch (anEvent.message) {
+    case NS_MOUSE_ENTER_SYNTH:
+      return NPCocoaEventMouseEntered;
+    case NS_MOUSE_EXIT_SYNTH:
+      return NPCocoaEventMouseExited;
+    case NS_MOUSE_MOVE:
+    {
+      // We don't know via information on events from the widget code whether or not
+      // we're dragging. The widget code just generates mouse move events from native
+      // drag events. If anybody is capturing, this is a drag event.
+      if (nsIPresShell::GetCapturingContent()) {
+        return NPCocoaEventMouseDragged;
+      }
+
+      return NPCocoaEventMouseMoved;
+    }
+    case NS_MOUSE_BUTTON_DOWN:
+      return NPCocoaEventMouseDown;
+    case NS_MOUSE_BUTTON_UP:
+      return NPCocoaEventMouseUp;
+    case NS_KEY_DOWN:
+      return NPCocoaEventKeyDown;
+    case NS_KEY_UP:
+      return NPCocoaEventKeyUp;
+    case NS_FOCUS_CONTENT:
+    case NS_BLUR_CONTENT:
+      return NPCocoaEventFocusChanged;
+    case NS_MOUSE_SCROLL:
+      return NPCocoaEventScrollWheel;
+    default:
+      return (NPCocoaEventType)0;
+  }
+}
+
+static NPCocoaEvent
+TranslateToNPCocoaEvent(WidgetGUIEvent* anEvent, nsIFrame* aObjectFrame)
+{
+  NPCocoaEvent cocoaEvent;
+  InitializeNPCocoaEvent(&cocoaEvent);
+  cocoaEvent.type = CocoaEventTypeForEvent(*anEvent, aObjectFrame);
+
+  if (anEvent->message == NS_MOUSE_MOVE ||
+      anEvent->message == NS_MOUSE_BUTTON_DOWN ||
+      anEvent->message == NS_MOUSE_BUTTON_UP ||
+      anEvent->message == NS_MOUSE_SCROLL ||
+      anEvent->message == NS_MOUSE_ENTER_SYNTH ||
+      anEvent->message == NS_MOUSE_EXIT_SYNTH)
+  {
+    nsPoint pt = nsLayoutUtils::GetEventCoordinatesRelativeTo(anEvent, aObjectFrame) -
+                 aObjectFrame->GetContentRectRelativeToSelf().TopLeft();
+    nsPresContext* presContext = aObjectFrame->PresContext();
+    // Plugin event coordinates need to be translated from device pixels
+    // into "display pixels" in HiDPI modes.
+    double scaleFactor = double(nsPresContext::AppUnitsPerCSSPixel())/
+      aObjectFrame->PresContext()->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom();
+    size_t intScaleFactor = ceil(scaleFactor);
+    nsIntPoint ptPx(presContext->AppUnitsToDevPixels(pt.x) / intScaleFactor,
+                    presContext->AppUnitsToDevPixels(pt.y) / intScaleFactor);
+    cocoaEvent.data.mouse.pluginX = double(ptPx.x);
+    cocoaEvent.data.mouse.pluginY = double(ptPx.y);
+  }
+
+  switch (anEvent->message) {
+    case NS_MOUSE_BUTTON_DOWN:
+    case NS_MOUSE_BUTTON_UP:
+    {
+      WidgetMouseEvent* mouseEvent = anEvent->AsMouseEvent();
+      if (mouseEvent) {
+        switch (mouseEvent->button) {
+          case WidgetMouseEvent::eLeftButton:
+            cocoaEvent.data.mouse.buttonNumber = 0;
+            break;
+          case WidgetMouseEvent::eRightButton:
+            cocoaEvent.data.mouse.buttonNumber = 1;
+            break;
+          case WidgetMouseEvent::eMiddleButton:
+            cocoaEvent.data.mouse.buttonNumber = 2;
+            break;
+          default:
+            NS_WARNING("Mouse button we don't know about?");
+        }
+        cocoaEvent.data.mouse.clickCount = mouseEvent->clickCount;
+      } else {
+        NS_WARNING("NS_MOUSE_BUTTON_UP/DOWN is not a WidgetMouseEvent?");
+      }
+      break;
+    }
+    case NS_MOUSE_SCROLL:
+    {
+      WidgetWheelEvent* wheelEvent = anEvent->AsWheelEvent();
+      if (wheelEvent) {
+        cocoaEvent.data.mouse.deltaX = wheelEvent->lineOrPageDeltaX;
+        cocoaEvent.data.mouse.deltaY = wheelEvent->lineOrPageDeltaY;
+      } else {
+        NS_WARNING("NS_MOUSE_SCROLL is not a WidgetWheelEvent? (could be, haven't checked)");
+      }
+      break;
+    }
+    case NS_KEY_DOWN:
+    case NS_KEY_UP:
+    {
+      WidgetKeyboardEvent* keyEvent = anEvent->AsKeyboardEvent();
+
+      cocoaEvent.data.key.keyCode = keyEvent->mNativeKeyCode;
+      cocoaEvent.data.key.isARepeat = keyEvent->mIsRepeat;
+      cocoaEvent.data.key.modifierFlags = keyEvent->mNativeModifierFlags;
+      const char16_t* nativeChars = keyEvent->mNativeCharacters.get();
+      cocoaEvent.data.key.characters =
+        (NPNSString*)::CFStringCreateWithCharacters(NULL,
+                                                    reinterpret_cast<const UniChar*>(nativeChars),
+                                                    keyEvent->mNativeCharacters.Length());
+      const char16_t* nativeCharsIgnoringModifiers = keyEvent->mNativeCharactersIgnoringModifiers.get();
+      cocoaEvent.data.key.charactersIgnoringModifiers =
+        (NPNSString*)::CFStringCreateWithCharacters(NULL,
+                                                    reinterpret_cast<const UniChar*>(nativeCharsIgnoringModifiers),
+                                                    keyEvent->mNativeCharactersIgnoringModifiers.Length());
+      break;
+    }
+    case NS_FOCUS_CONTENT:
+    case NS_BLUR_CONTENT:
+      cocoaEvent.data.focus.hasFocus = (anEvent->message == NS_FOCUS_CONTENT);
+      break;
+    default:
+      break;
+  }
+  return cocoaEvent;
+}
+
+void nsPluginInstanceOwner::PerformDelayedBlurs()
+{
+  nsCOMPtr<EventTarget> windowRoot = mContent->OwnerDoc()->GetWindow()->GetTopWindowRoot();
+  nsContentUtils::DispatchTrustedEvent(mContent->OwnerDoc(),
+                                       windowRoot,
+                                       NS_LITERAL_STRING("MozPerformDelayedBlur"),
+                                       false, false, nullptr);
+}
+
+#endif
+
 nsEventStatus nsPluginInstanceOwner::ProcessEvent(const WidgetGUIEvent& anEvent)
 {
   nsEventStatus rv = nsEventStatus_eIgnore;
 
-  if (!mInstance || !mPluginFrame)   // if mInstance is null, we shouldn't be here
+  if (!mInstance || !mPluginFrame) {
     return nsEventStatus_eIgnore;
+  }
 
 #ifdef XP_MACOSX
-  if (!mWidget)
+  NPEventModel eventModel = GetEventModel();
+  if (eventModel != NPEventModelCocoa) {
     return nsEventStatus_eIgnore;
-
-  // we never care about synthesized mouse enter
-  if (anEvent.message == NS_MOUSE_ENTER_SYNTH)
+  }
+
+  // In the Cocoa event model, focus is per-window. Don't tell a plugin it lost
+  // focus unless it lost focus within the window. For example, ignore a blur
+  // event if it's coming due to the plugin's window deactivating.
+  if (anEvent.message == NS_BLUR_CONTENT &&
+      ContentIsFocusedWithinWindow(mContent)) {
+    mShouldBlurOnActivate = true;
     return nsEventStatus_eIgnore;
-
-  nsCOMPtr<nsIPluginWidget> pluginWidget = do_QueryInterface(mWidget);
-  if (!pluginWidget || NS_FAILED(pluginWidget->StartDrawPlugin()))
+  }
+
+  // Also, don't tell the plugin it gained focus again after we've already given
+  // it focus. This might happen if it has focus, its window is blurred, then the
+  // window is made active again. The plugin never lost in-window focus, so it
+  // shouldn't get a focus event again.
+  if (anEvent.message == NS_FOCUS_CONTENT &&
+      mLastContentFocused == true) {
+    mShouldBlurOnActivate = false;
     return nsEventStatus_eIgnore;
-
-  NPEventModel eventModel = GetEventModel();
-
-  // If we have to synthesize an event we'll use one of these.
-  NPCocoaEvent synthCocoaEvent;
-  const NPCocoaEvent* event = static_cast<const NPCocoaEvent*>(anEvent.mPluginEvent);
-  nsPoint pt =
-  nsLayoutUtils::GetEventCoordinatesRelativeTo(&anEvent, mPluginFrame) -
-  mPluginFrame->GetContentRectRelativeToSelf().TopLeft();
-  nsPresContext* presContext = mPluginFrame->PresContext();
-  // Plugin event coordinates need to be translated from device pixels
-  // into "display pixels" in HiDPI modes.
-  double scaleFactor = 1.0;
-  GetContentsScaleFactor(&scaleFactor);
-  size_t intScaleFactor = ceil(scaleFactor);
-  nsIntPoint ptPx(presContext->AppUnitsToDevPixels(pt.x) / intScaleFactor,
-                  presContext->AppUnitsToDevPixels(pt.y) / intScaleFactor);
-
-  if (!event) {
-    InitializeNPCocoaEvent(&synthCocoaEvent);
-    switch (anEvent.message) {
-      case NS_MOUSE_MOVE:
-      {
-        // Ignore mouse-moved events that happen as part of a dragging
-        // operation that started over another frame.  See bug 525078.
-        nsRefPtr<nsFrameSelection> frameselection = mPluginFrame->GetFrameSelection();
-        if (!frameselection->GetDragState() ||
-          (nsIPresShell::GetCapturingContent() == mPluginFrame->GetContent())) {
-          synthCocoaEvent.type = NPCocoaEventMouseMoved;
-          synthCocoaEvent.data.mouse.pluginX = static_cast<double>(ptPx.x);
-          synthCocoaEvent.data.mouse.pluginY = static_cast<double>(ptPx.y);
-          event = &synthCocoaEvent;
-        }
+  }
+
+  // Now, if we're going to send a focus event, update mLastContentFocused and
+  // tell any plugins in our window that we have taken focus, so they should
+  // perform any delayed blurs.
+  if (anEvent.message == NS_FOCUS_CONTENT ||
+      anEvent.message == NS_BLUR_CONTENT) {
+    mLastContentFocused = (anEvent.message == NS_FOCUS_CONTENT);
+    mShouldBlurOnActivate = false;
+    PerformDelayedBlurs();
+  }
+
+  NPCocoaEvent cocoaEvent = TranslateToNPCocoaEvent(const_cast<WidgetGUIEvent*>(&anEvent), mPluginFrame);
+  if (cocoaEvent.type == (NPCocoaEventType)0) {
+    return nsEventStatus_eIgnore;
+  }
+
+  if (cocoaEvent.type == NPCocoaEventKeyDown) {
+    ComplexTextInputPanel* ctiPanel = ComplexTextInputPanel::GetSharedComplexTextInputPanel();
+    if (ctiPanel && ctiPanel->IsInComposition()) {
+      nsAutoString outText;
+      ctiPanel->InterpretKeyEvent(&cocoaEvent, outText);
+      if (!outText.IsEmpty()) {
+        CFStringRef cfString = ::CFStringCreateWithCharacters(kCFAllocatorDefault,
+                                                              reinterpret_cast<const UniChar*>(outText.get()),
+                                                              outText.Length());
+
+        NPCocoaEvent textEvent;
+        InitializeNPCocoaEvent(&textEvent);
+        textEvent.type = NPCocoaEventTextInput;
+        textEvent.data.text.text = (NPNSString*)cfString;
+
+        mInstance->HandleEvent(&textEvent, nullptr);
       }
-        break;
-      case NS_MOUSE_BUTTON_DOWN:
-        synthCocoaEvent.type = NPCocoaEventMouseDown;
-        synthCocoaEvent.data.mouse.pluginX = static_cast<double>(ptPx.x);
-        synthCocoaEvent.data.mouse.pluginY = static_cast<double>(ptPx.y);
-        event = &synthCocoaEvent;
-        break;
-      case NS_MOUSE_BUTTON_UP:
-        // If we're in a dragging operation that started over another frame,
-        // convert it into a mouse-entered event (in the Cocoa Event Model).
-        // See bug 525078.
-        if (anEvent.AsMouseEvent()->button == WidgetMouseEvent::eLeftButton &&
-            (nsIPresShell::GetCapturingContent() != mPluginFrame->GetContent())) {
-          synthCocoaEvent.type = NPCocoaEventMouseEntered;
-          synthCocoaEvent.data.mouse.pluginX = static_cast<double>(ptPx.x);
-          synthCocoaEvent.data.mouse.pluginY = static_cast<double>(ptPx.y);
-          event = &synthCocoaEvent;
-        } else {
-          synthCocoaEvent.type = NPCocoaEventMouseUp;
-          synthCocoaEvent.data.mouse.pluginX = static_cast<double>(ptPx.x);
-          synthCocoaEvent.data.mouse.pluginY = static_cast<double>(ptPx.y);
-          event = &synthCocoaEvent;
-        }
-        break;
-      default:
-        break;
-    }
-
-    // If we still don't have an event, bail.
-    if (!event) {
-      pluginWidget->EndDrawPlugin();
-      return nsEventStatus_eIgnore;
+      return nsEventStatus_eConsumeNoDefault;
     }
   }
 
   int16_t response = kNPEventNotHandled;
-  void* window = FixUpPluginWindow(ePluginPaintEnable);
-  if (window || (eventModel == NPEventModelCocoa)) {
-    mInstance->HandleEvent(const_cast<NPCocoaEvent*>(event),
-                           &response,
-                           NS_PLUGIN_CALL_SAFE_TO_REENTER_GECKO);
+  mInstance->HandleEvent(&cocoaEvent,
+                         &response,
+                         NS_PLUGIN_CALL_SAFE_TO_REENTER_GECKO);
+  if (response == kNPEventStartIME) {
+    nsAutoString outText;
+    ComplexTextInputPanel* ctiPanel = ComplexTextInputPanel::GetSharedComplexTextInputPanel();
+
+    // Place ctiPanel by passing the coordinates of the bottom-left of the plugin,
+    // in screen-coordinates.
+    double screenX, screenY;
+    ConvertPoint(0.0, mPluginFrame->GetScreenRect().height, NPCoordinateSpacePlugin,
+                 &screenX, &screenY, NPCoordinateSpaceScreen);
+
+    ctiPanel->PlacePanel(screenX, screenY);
+    ctiPanel->InterpretKeyEvent(&cocoaEvent, outText);
+
+    if (!outText.IsEmpty()) {
+      CFStringRef cfString = ::CFStringCreateWithCharacters(kCFAllocatorDefault,
+                                                            reinterpret_cast<const UniChar*>(outText.get()),
+                                                            outText.Length());
+
+      NPCocoaEvent textEvent;
+      InitializeNPCocoaEvent(&textEvent);
+      textEvent.type = NPCocoaEventTextInput;
+      textEvent.data.text.text = (NPNSString*)cfString;
+
+      mInstance->HandleEvent(&textEvent, nullptr);
+     }
   }
 
-  if (eventModel == NPEventModelCocoa && response == kNPEventStartIME) {
-    pluginWidget->StartComplexTextInputForCurrentEvent();
-  }
-
-  if ((response == kNPEventHandled || response == kNPEventStartIME) &&
-      !(anEvent.message == NS_MOUSE_BUTTON_DOWN &&
-        anEvent.AsMouseEvent()->button == WidgetMouseEvent::eLeftButton &&
-        !mContentFocused)) {
+  bool handled = (response == kNPEventHandled || response == kNPEventStartIME);
+  bool leftMouseButtonDown = (anEvent.message == NS_MOUSE_BUTTON_DOWN) &&
+                             (anEvent.AsMouseEvent()->button == WidgetMouseEvent::eLeftButton);
+  if (handled && !(leftMouseButtonDown && !mContentFocused)) {
     rv = nsEventStatus_eConsumeNoDefault;
   }
-
-  pluginWidget->EndDrawPlugin();
 #endif
 
 #ifdef XP_WIN
   // this code supports windowless plugins
   const NPEvent *pPluginEvent = static_cast<const NPEvent*>(anEvent.mPluginEvent);
   // we can get synthetic events from the EventStateManager... these
   // have no pluginEvent
   NPEvent pluginEvent;
@@ -2039,21 +2429,17 @@ void nsPluginInstanceOwner::Paint(const 
   GetContentsScaleFactor(&scaleFactor);
   if (scaleFactor != 1.0) {
     ::CGContextScaleCTM(cgContext, scaleFactor, scaleFactor);
     // Convert aDirtyRect from device pixels to "display pixels"
     // for HiDPI modes
     dirtyRectCopy.ScaleRoundOut(1.0 / scaleFactor);
   }
 
-  nsCOMPtr<nsIPluginWidget> pluginWidget = do_QueryInterface(mWidget);
-  if (pluginWidget && NS_SUCCEEDED(pluginWidget->StartDrawPlugin())) {
-    DoCocoaEventDrawRect(dirtyRectCopy, cgContext);
-    pluginWidget->EndDrawPlugin();
-  }
+  DoCocoaEventDrawRect(dirtyRectCopy, cgContext);
 }
 
 void nsPluginInstanceOwner::DoCocoaEventDrawRect(const gfxRect& aDrawRect, CGContextRef cgContext)
 {
   if (!mInstance || !mPluginFrame)
     return;
 
   // The context given here is only valid during the HandleEvent call.
@@ -2404,36 +2790,30 @@ nsresult nsPluginInstanceOwner::Init(nsI
   mContent->AddEventListener(NS_LITERAL_STRING("dragexit"), this, true);
   mContent->AddEventListener(NS_LITERAL_STRING("dragstart"), this, true);
   mContent->AddEventListener(NS_LITERAL_STRING("draggesture"), this, true);
   mContent->AddEventListener(NS_LITERAL_STRING("dragend"), this, true);
 
   return NS_OK;
 }
 
-void* nsPluginInstanceOwner::GetPluginPortFromWidget()
+void* nsPluginInstanceOwner::GetPluginPort()
 {
 //!!! Port must be released for windowless plugins on Windows, because it is HDC !!!
 
   void* result = nullptr;
   if (mWidget) {
 #ifdef XP_WIN
     if (mPluginWindow && (mPluginWindow->type == NPWindowTypeDrawable))
       result = mWidget->GetNativeData(NS_NATIVE_GRAPHIC); // HDC
     else
 #endif
-#ifdef XP_MACOSX
-    if (GetDrawingModel() == NPDrawingModelCoreGraphics ||
-        GetDrawingModel() == NPDrawingModelCoreAnimation ||
-        GetDrawingModel() == NPDrawingModelInvalidatingCoreAnimation)
-      result = mWidget->GetNativeData(NS_NATIVE_PLUGIN_PORT_CG);
-    else
-#endif
       result = mWidget->GetNativeData(NS_NATIVE_PLUGIN_PORT); // HWND/gdk window
   }
+
   return result;
 }
 
 void nsPluginInstanceOwner::ReleasePluginPort(void * pluginPort)
 {
 #ifdef XP_WIN
   if (mWidget && mPluginWindow &&
       mPluginWindow->type == NPWindowTypeDrawable) {
@@ -2498,31 +2878,16 @@ NS_IMETHODIMP nsPluginInstanceOwner::Cre
         return rv;
       }
     }
 
 
     mWidget->EnableDragDrop(true);
     mWidget->Show(false);
     mWidget->Enable(false);
-
-#ifdef XP_MACOSX
-    // Now that we have a widget we want to set the event model before
-    // any events are processed.
-    nsCOMPtr<nsIPluginWidget> pluginWidget = do_QueryInterface(mWidget);
-    if (!pluginWidget) {
-      return NS_ERROR_FAILURE;
-    }
-    pluginWidget->SetPluginEventModel(GetEventModel());
-    pluginWidget->SetPluginDrawingModel(GetDrawingModel());
-
-    if (GetDrawingModel() == NPDrawingModelCoreAnimation) {
-      AddToCARefreshTimer();
-    }
-#endif
   }
 
   if (mPluginFrame) {
     // nullptr widget is fine, will result in windowless setup.
     mPluginFrame->PrepForDrawing(mWidget);
   }
 
   if (windowless) {
@@ -2543,100 +2908,65 @@ NS_IMETHODIMP nsPluginInstanceOwner::Cre
     GetPluginDescription(description);
     NS_NAMED_LITERAL_CSTRING(flash10Head, "Shockwave Flash 10.");
     mFlash10Quirks = StringBeginsWith(description, flash10Head);
 #endif
   } else if (mWidget) {
     // mPluginWindow->type is used in |GetPluginPort| so it must
     // be initialized first
     mPluginWindow->type = NPWindowTypeWindow;
-    mPluginWindow->window = GetPluginPortFromWidget();
+    mPluginWindow->window = GetPluginPort();
     // tell the plugin window about the widget
     mPluginWindow->SetPluginWidget(mWidget);
 
     // tell the widget about the current plugin instance owner.
     nsCOMPtr<nsIPluginWidget> pluginWidget = do_QueryInterface(mWidget);
     if (pluginWidget) {
       pluginWidget->SetPluginInstanceOwner(this);
     }
   }
 
+#ifdef XP_MACOSX
+  if (GetDrawingModel() == NPDrawingModelCoreAnimation) {
+    AddToCARefreshTimer();
+  }
+#endif
+
   mWidgetCreationComplete = true;
 
   return NS_OK;
 }
 
 // Mac specific code to fix up the port location and clipping region
 #ifdef XP_MACOSX
 
-void* nsPluginInstanceOwner::FixUpPluginWindow(int32_t inPaintState)
+void nsPluginInstanceOwner::FixUpPluginWindow(int32_t inPaintState)
 {
-  if (!mWidget || !mPluginWindow || !mInstance || !mPluginFrame)
-    return nullptr;
-
-  nsCOMPtr<nsIPluginWidget> pluginWidget = do_QueryInterface(mWidget);
-  if (!pluginWidget)
-    return nullptr;
+  if (!mPluginWindow || !mInstance || !mPluginFrame) {
+    return;
+  }
 
   // If we've already set up a CGContext in nsPluginFrame::PaintPlugin(), we
-  // don't want calls to SetPluginPortAndDetectChange() to step on our work.
+  // don't want calls to SetPluginPort() to step on our work.
   if (mInCGPaintLevel < 1) {
-    SetPluginPortAndDetectChange();
-  }
-
-  // We'll need the top-level Cocoa window for the Cocoa event model.
-  nsIWidget* widget = mPluginFrame->GetNearestWidget();
-  if (!widget)
-    return nullptr;
-  void *cocoaTopLevelWindow = widget->GetNativeData(NS_NATIVE_WINDOW);
-  // We don't expect to have a top level window in a content process
-  if (!cocoaTopLevelWindow && XRE_GetProcessType() == GeckoProcessType_Default) {
-    return nullptr;
-  }
-
-  nsIntPoint pluginOrigin;
-  nsIntRect widgetClip;
-  bool widgetVisible;
-  pluginWidget->GetPluginClipRect(widgetClip, pluginOrigin, widgetVisible);
-  // TODO: Detect visibility for e10s mac plugins
-  if (XRE_GetProcessType() != GeckoProcessType_Default) {
-    widgetVisible = true;
+    SetPluginPort();
   }
-  mWidgetVisible = widgetVisible;
-
-  // printf("GetPluginClipRect returning visible %d\n", widgetVisible);
-
-  // This would be a lot easier if we could use obj-c here,
-  // but we can't. Since we have only nsIWidget and we can't
-  // use its native widget (an obj-c object) we have to go
-  // from the widget's screen coordinates to its window coords
-  // instead of straight to window coords.
-  nsIntPoint geckoScreenCoords = mWidget->WidgetToScreenOffset();
-
-  nsRect windowRect;
-  if (cocoaTopLevelWindow) {
-    NS_NPAPI_CocoaWindowFrame(cocoaTopLevelWindow, windowRect);
-  }
-
-  double scaleFactor = 1.0;
-  GetContentsScaleFactor(&scaleFactor);
-  int intScaleFactor = ceil(scaleFactor);
-
-  // Convert geckoScreenCoords from device pixels to "display pixels"
-  // for HiDPI modes.
-  mPluginWindow->x = geckoScreenCoords.x/intScaleFactor - windowRect.x;
-  mPluginWindow->y = geckoScreenCoords.y/intScaleFactor - windowRect.y;
+
+  nsIntSize widgetClip = mPluginFrame->GetWidgetlessClipRect().Size();
+
+  mPluginWindow->x = 0;
+  mPluginWindow->y = 0;
 
   NPRect oldClipRect = mPluginWindow->clipRect;
 
   // fix up the clipping region
-  mPluginWindow->clipRect.top    = widgetClip.y;
-  mPluginWindow->clipRect.left   = widgetClip.x;
-
-  if (!mWidgetVisible || inPaintState == ePluginPaintDisable) {
+  mPluginWindow->clipRect.top  = 0;
+  mPluginWindow->clipRect.left = 0;
+
+  if (inPaintState == ePluginPaintDisable) {
     mPluginWindow->clipRect.bottom = mPluginWindow->clipRect.top;
     mPluginWindow->clipRect.right  = mPluginWindow->clipRect.left;
   }
   else if (XRE_GetProcessType() != GeckoProcessType_Default)
   {
     // For e10s we only support async windowless plugin. This means that
     // we're always going to allocate a full window for the plugin to draw
     // for even if the plugin is mostly outside of the scroll port. Thus
@@ -2650,44 +2980,88 @@ void* nsPluginInstanceOwner::FixUpPlugin
     mPluginWindow->clipRect.right  = mPluginWindow->clipRect.left + widgetClip.width;
   }
 
   // if the clip rect changed, call SetWindow()
   // (RealPlayer needs this to draw correctly)
   if (mPluginWindow->clipRect.left    != oldClipRect.left   ||
       mPluginWindow->clipRect.top     != oldClipRect.top    ||
       mPluginWindow->clipRect.right   != oldClipRect.right  ||
-      mPluginWindow->clipRect.bottom  != oldClipRect.bottom ||
-      mPluginPortChanged)
+      mPluginWindow->clipRect.bottom  != oldClipRect.bottom)
   {
     if (UseAsyncRendering()) {
       mInstance->AsyncSetWindow(mPluginWindow);
     }
     else {
       mPluginWindow->CallSetWindow(mInstance);
     }
-    mPluginPortChanged = false;
   }
 
   // After the first NPP_SetWindow call we need to send an initial
   // top-level window focus event.
   if (!mSentInitialTopLevelWindowEvent) {
     // Set this before calling ProcessEvent to avoid endless recursion.
     mSentInitialTopLevelWindowEvent = true;
 
-    WidgetPluginEvent pluginEvent(true, NS_PLUGIN_FOCUS_EVENT, nullptr);
-    NPCocoaEvent cocoaEvent;
-    InitializeNPCocoaEvent(&cocoaEvent);
-    cocoaEvent.type = NPCocoaEventWindowFocusChanged;
-    cocoaEvent.data.focus.hasFocus = cocoaTopLevelWindow ? NS_NPAPI_CocoaWindowIsMain(cocoaTopLevelWindow) : true;
-    pluginEvent.mPluginEvent.Copy(cocoaEvent);
-    ProcessEvent(pluginEvent);
+    bool isActive = WindowIsActive();
+    SendWindowFocusChanged(isActive);
+    mLastWindowIsActive = isActive;
+  }
+}
+
+void
+nsPluginInstanceOwner::WindowFocusMayHaveChanged()
+{
+  if (!mSentInitialTopLevelWindowEvent) {
+    return;
+  }
+
+  bool isActive = WindowIsActive();
+  if (isActive != mLastWindowIsActive) {
+    SendWindowFocusChanged(isActive);
+    mLastWindowIsActive = isActive;
+  }
+}
+
+bool
+nsPluginInstanceOwner::WindowIsActive()
+{
+  if (!mPluginFrame) {
+    return false;
   }
 
-  return nullptr;
+  EventStates docState = mPluginFrame->GetContent()->OwnerDoc()->GetDocumentState();
+  return !docState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE);
+}
+
+void
+nsPluginInstanceOwner::SendWindowFocusChanged(bool aIsActive)
+{
+  if (!mInstance) {
+    return;
+  }
+
+  NPCocoaEvent cocoaEvent;
+  InitializeNPCocoaEvent(&cocoaEvent);
+  cocoaEvent.type = NPCocoaEventWindowFocusChanged;
+  cocoaEvent.data.focus.hasFocus = aIsActive;
+  mInstance->HandleEvent(&cocoaEvent,
+                         nullptr,
+                         NS_PLUGIN_CALL_SAFE_TO_REENTER_GECKO);
+}
+
+void
+nsPluginInstanceOwner::ResolutionMayHaveChanged()
+{
+  double scaleFactor = 1.0;
+  GetContentsScaleFactor(&scaleFactor);
+  if (scaleFactor != mLastScaleFactor) {
+    ContentsScaleFactorChanged(scaleFactor);
+    mLastScaleFactor = scaleFactor;
+   }
 }
 
 void
 nsPluginInstanceOwner::HidePluginWindow()
 {
   if (!mPluginWindow || !mInstance) {
     return;
   }
@@ -2831,16 +3205,28 @@ void nsPluginInstanceOwner::SetFrame(nsP
 {
   // Don't do anything if the frame situation hasn't changed.
   if (mPluginFrame == aFrame) {
     return;
   }
 
   // If we already have a frame that is changing or going away...
   if (mPluginFrame) {
+    if (mContent && mContent->OwnerDoc() && mContent->OwnerDoc()->GetWindow()) {
+      nsCOMPtr<EventTarget> windowRoot = mContent->OwnerDoc()->GetWindow()->GetTopWindowRoot();
+      if (windowRoot) {
+        windowRoot->RemoveEventListener(NS_LITERAL_STRING("activate"),
+                                              this, false);
+        windowRoot->RemoveEventListener(NS_LITERAL_STRING("deactivate"),
+                                              this, false);
+        windowRoot->RemoveEventListener(NS_LITERAL_STRING("MozPerformDelayedBlur"),
+                                              this, false);
+      }
+    }
+
     // Make sure the old frame isn't holding a reference to us.
     mPluginFrame->SetInstanceOwner(nullptr);
   }
 
   // Swap in the new frame (or no frame)
   mPluginFrame = aFrame;
 
   // Set up a new frame
@@ -2854,16 +3240,29 @@ void nsPluginInstanceOwner::SetFrame(nsP
     mPluginFrame->FixupWindow(mPluginFrame->GetContentRectRelativeToSelf().Size());
     mPluginFrame->InvalidateFrame();
 
     nsFocusManager* fm = nsFocusManager::GetFocusManager();
     const nsIContent* content = aFrame->GetContent();
     if (fm && content) {
       mContentFocused = (content == fm->GetFocusedContent());
     }
+
+    // Register for widget-focus events on the window root.
+    if (mContent && mContent->OwnerDoc() && mContent->OwnerDoc()->GetWindow()) {
+      nsCOMPtr<EventTarget> windowRoot = mContent->OwnerDoc()->GetWindow()->GetTopWindowRoot();
+      if (windowRoot) {
+        windowRoot->AddEventListener(NS_LITERAL_STRING("activate"),
+                                           this, false, false);
+        windowRoot->AddEventListener(NS_LITERAL_STRING("deactivate"),
+                                           this, false, false);
+        windowRoot->AddEventListener(NS_LITERAL_STRING("MozPerformDelayedBlur"),
+                                           this, false, false);
+      }
+    }
   }
 }
 
 nsPluginFrame* nsPluginInstanceOwner::GetFrame()
 {
   return mPluginFrame;
 }
 
--- a/dom/plugins/base/nsPluginInstanceOwner.h
+++ b/dom/plugins/base/nsPluginInstanceOwner.h
@@ -28,17 +28,22 @@ struct nsIntRect;
 class nsPluginDOMContextMenuListener;
 class nsPluginFrame;
 class nsDisplayListBuilder;
 
 namespace mozilla {
 namespace dom {
 struct MozPluginParameter;
 }
+namespace widget {
+class PuppetWidget;
 }
+}
+
+using mozilla::widget::PuppetWidget;
 
 #ifdef MOZ_X11
 class gfxXlibSurface;
 #ifdef MOZ_WIDGET_QT
 #include "gfxQtNativeRenderer.h"
 #else
 #include "gfxXlibNativeRenderer.h"
 #endif
@@ -57,16 +62,17 @@ public:
   NS_DECL_NSIPRIVACYTRANSITIONOBSERVER
   
   NS_IMETHOD GetURL(const char *aURL, const char *aTarget,
                     nsIInputStream *aPostStream, 
                     void *aHeadersData, uint32_t aHeadersDataLen) MOZ_OVERRIDE;
   
   NS_IMETHOD ShowStatus(const char16_t *aStatusMsg) MOZ_OVERRIDE;
   
+  // This can go away, just leaving it here to avoid changing the interface.
   NPError    ShowNativeContextMenu(NPMenu* menu, void* event) MOZ_OVERRIDE;
   
   NPBool     ConvertPoint(double sourceX, double sourceY, NPCoordinateSpace sourceSpace,
                           double *destX, double *destY, NPCoordinateSpace destSpace) MOZ_OVERRIDE;
   
   /**
    * Get the type of the HTML tag that was used ot instantiate this
    * plugin.  Currently supported tags are EMBED, OBJECT and APPLET.
@@ -103,45 +109,45 @@ public:
              const gfxRect& aFrameRect,
              const gfxRect& aDirtyRect);
 #endif
 
   //locals
   
   nsresult Init(nsIContent* aContent);
   
-  void* GetPluginPortFromWidget();
+  void* GetPluginPort();
   void ReleasePluginPort(void* pluginPort);
 
   nsEventStatus ProcessEvent(const mozilla::WidgetGUIEvent& anEvent);
   
 #ifdef XP_MACOSX
   enum { ePluginPaintEnable, ePluginPaintDisable };
-  
+
+  void WindowFocusMayHaveChanged();
+  void ResolutionMayHaveChanged();
+
+  bool WindowIsActive();
+  void SendWindowFocusChanged(bool aIsActive);
   NPDrawingModel GetDrawingModel();
   bool IsRemoteDrawingCoreAnimation();
   nsresult ContentsScaleFactorChanged(double aContentsScaleFactor);
   NPEventModel GetEventModel();
   static void CARefresh(nsITimer *aTimer, void *aClosure);
   void AddToCARefreshTimer();
   void RemoveFromCARefreshTimer();
   // This calls into the plugin (NPP_SetWindow) and can run script.
-  void* FixUpPluginWindow(int32_t inPaintState);
+  void FixUpPluginWindow(int32_t inPaintState);
   void HidePluginWindow();
-  // Set a flag that (if true) indicates the plugin port info has changed and
-  // SetWindow() needs to be called.
-  void SetPluginPortChanged(bool aState) { mPluginPortChanged = aState; }
   // Return a pointer to the internal nsPluginPort structure that's used to
   // store a copy of plugin port info and to detect when it's been changed.
   void* GetPluginPortCopy();
   // Set plugin port info in the plugin (in the 'window' member of the
-  // NPWindow structure passed to the plugin by SetWindow()) and set a
-  // flag (mPluginPortChanged) to indicate whether or not this info has
-  // changed, and SetWindow() needs to be called again.
-  void* SetPluginPortAndDetectChange();
+  // NPWindow structure passed to the plugin by SetWindow()).
+  void SetPluginPort();
   // Flag when we've set up a Thebes (and CoreGraphics) context in
   // nsPluginFrame::PaintPlugin().  We need to know this in
   // FixUpPluginWindow() (i.e. we need to know when FixUpPluginWindow() has
   // been called from nsPluginFrame::PaintPlugin() when we're using the
   // CoreGraphics drawing model).
   void BeginCGPaint();
   void EndCGPaint();
 #else // XP_MACOSX
@@ -283,27 +289,29 @@ private:
   NP_CGContext                              mCGPluginPortCopy;
   int32_t                                   mInCGPaintLevel;
   mozilla::RefPtr<MacIOSurface>             mIOSurface;
   mozilla::RefPtr<nsCARenderer>             mCARenderer;
   CGColorSpaceRef                           mColorProfile;
   static nsCOMPtr<nsITimer>                *sCATimer;
   static nsTArray<nsPluginInstanceOwner*>  *sCARefreshListeners;
   bool                                      mSentInitialTopLevelWindowEvent;
+  bool                                      mLastWindowIsActive;
+  bool                                      mLastContentFocused;
+  double                                    mLastScaleFactor;
+  // True if, the next time the window is activated, we should blur ourselves.
+  bool                                      mShouldBlurOnActivate;
 #endif
 
   // Initially, the event loop nesting level we were created on, it's updated
   // if we detect the appshell is on a lower level as long as we're not stopped.
   // We delay DoStopPlugin() until the appshell reaches this level or lower.
   uint32_t                    mLastEventloopNestingLevel;
   bool                        mContentFocused;
   bool                        mWidgetVisible;    // used on Mac to store our widget's visible state
-#ifdef XP_MACOSX
-  bool                        mPluginPortChanged;
-#endif
 #ifdef MOZ_X11
   // Used with windowless plugins only, initialized in CreateWidget().
   bool                        mFlash10Quirks;
 #endif
   bool                        mPluginWindowVisible;
   bool                        mPluginDocumentActiveState;
 
 #ifdef XP_MACOSX
@@ -318,16 +326,26 @@ private:
   // pointer to wrapper for nsIDOMContextMenuListener
   nsRefPtr<nsPluginDOMContextMenuListener> mCXMenuListener;
   
   nsresult DispatchKeyToPlugin(nsIDOMEvent* aKeyEvent);
   nsresult DispatchMouseToPlugin(nsIDOMEvent* aMouseEvent,
                                  bool aAllowPropagate = false);
   nsresult DispatchFocusToPlugin(nsIDOMEvent* aFocusEvent);
 
+#ifdef XP_MACOSX
+  static NPBool ConvertPointPuppet(PuppetWidget *widget, nsPluginFrame* pluginFrame,
+                            double sourceX, double sourceY, NPCoordinateSpace sourceSpace,
+                            double *destX, double *destY, NPCoordinateSpace destSpace);
+  static NPBool ConvertPointNoPuppet(nsIWidget *widget, nsPluginFrame* pluginFrame,
+                            double sourceX, double sourceY, NPCoordinateSpace sourceSpace,
+                            double *destX, double *destY, NPCoordinateSpace destSpace);
+  void PerformDelayedBlurs();
+#endif    // XP_MACOSX
+
   int mLastMouseDownButtonType;
 
 #ifdef MOZ_X11
   class Renderer
 #if defined(MOZ_WIDGET_QT)
   : public gfxQtNativeRenderer
 #else
   : public gfxXlibNativeRenderer
--- a/dom/plugins/ipc/PluginInstanceChild.cpp
+++ b/dom/plugins/ipc/PluginInstanceChild.cpp
@@ -383,22 +383,22 @@ PluginInstanceChild::NPN_GetValue(NPNVar
 
 #ifdef XP_MACOSX
    case NPNVsupportsCoreGraphicsBool: {
         *((NPBool*)aValue) = true;
         return NPERR_NO_ERROR;
     }
 
     case NPNVsupportsCoreAnimationBool: {
-        *((NPBool*)aValue) = nsCocoaFeatures::SupportCoreAnimationPlugins();
+        *((NPBool*)aValue) = true;
         return NPERR_NO_ERROR;
     }
 
     case NPNVsupportsInvalidatingCoreAnimationBool: {
-        *((NPBool*)aValue) = nsCocoaFeatures::SupportCoreAnimationPlugins();
+        *((NPBool*)aValue) = true;
         return NPERR_NO_ERROR;
     }
 
     case NPNVsupportsCompositingCoreAnimationPluginsBool: {
         *((NPBool*)aValue) = true;
         return NPERR_NO_ERROR;
     }
 
--- a/dom/plugins/ipc/PluginInstanceParent.cpp
+++ b/dom/plugins/ipc/PluginInstanceParent.cpp
@@ -2,16 +2,17 @@
  * vim: sw=4 ts=4 et :
  * 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/DebugOnly.h"
 #include <stdint.h> // for intptr_t
 
+#include "mozilla/Telemetry.h"
 #include "PluginInstanceParent.h"
 #include "BrowserStreamParent.h"
 #include "PluginBackgroundDestroyer.h"
 #include "PluginModuleParent.h"
 #include "PluginStreamParent.h"
 #include "StreamNotifyParent.h"
 #include "npfunctions.h"
 #include "nsAutoPtr.h"
@@ -140,18 +141,23 @@ PluginInstanceParent::ActorDestroy(Actor
 #endif
     }
 }
 
 NPError
 PluginInstanceParent::Destroy()
 {
     NPError retval;
-    if (!CallNPP_Destroy(&retval))
-        retval = NPERR_GENERIC_ERROR;
+    {   // Scope for timer
+        Telemetry::AutoTimer<Telemetry::BLOCKED_ON_PLUGIN_INSTANCE_DESTROY_MS>
+            timer(Module()->GetHistogramKey());
+        if (!CallNPP_Destroy(&retval)) {
+            retval = NPERR_GENERIC_ERROR;
+        }
+    }
 
 #if defined(OS_WIN)
     SharedSurfaceRelease();
     UnsubclassPluginWindow();
 #endif
 
     return retval;
 }
@@ -1299,25 +1305,30 @@ PluginInstanceParent::NPP_NewStream(NPMI
                                     NPBool seekable, uint16_t* stype)
 {
     PLUGIN_LOG_DEBUG(("%s (type=%s, stream=%p, seekable=%i)",
                       FULLFUNCTION, (char*) type, (void*) stream, (int) seekable));
 
     BrowserStreamParent* bs = new BrowserStreamParent(this, stream);
 
     NPError err;
-    if (!CallPBrowserStreamConstructor(bs,
-                                       NullableString(stream->url),
-                                       stream->end,
-                                       stream->lastmodified,
-                                       static_cast<PStreamNotifyParent*>(stream->notifyData),
-                                       NullableString(stream->headers),
-                                       NullableString(type), seekable,
-                                       &err, stype))
-        return NPERR_GENERIC_ERROR;
+    {   // Scope for timer
+        Telemetry::AutoTimer<Telemetry::BLOCKED_ON_PLUGIN_STREAM_INIT_MS>
+            timer(Module()->GetHistogramKey());
+        if (!CallPBrowserStreamConstructor(bs,
+                                           NullableString(stream->url),
+                                           stream->end,
+                                           stream->lastmodified,
+                                           static_cast<PStreamNotifyParent*>(stream->notifyData),
+                                           NullableString(stream->headers),
+                                           NullableString(type), seekable,
+                                           &err, stype)) {
+            return NPERR_GENERIC_ERROR;
+        }
+    }
 
     if (NPERR_NO_ERROR != err)
         unused << PBrowserStreamParent::Send__delete__(bs);
 
     return err;
 }
 
 NPError
--- a/dom/plugins/ipc/PluginModuleChild.h
+++ b/dom/plugins/ipc/PluginModuleChild.h
@@ -145,17 +145,17 @@ protected:
     virtual bool RecvStartProfiler(const uint32_t& aEntries,
                                    const double& aInterval,
                                    const nsTArray<nsCString>& aFeatures,
                                    const nsTArray<nsCString>& aThreadNameFilters) MOZ_OVERRIDE;
     virtual bool RecvStopProfiler() MOZ_OVERRIDE;
     virtual bool AnswerGetProfile(nsCString* aProfile) MOZ_OVERRIDE;
 
 public:
-    PluginModuleChild(bool aIsChrome);
+    explicit PluginModuleChild(bool aIsChrome);
     virtual ~PluginModuleChild();
 
     bool CommonInit(base::ProcessHandle aParentProcessHandle,
                     MessageLoop* aIOLoop,
                     IPC::Channel* aChannel);
 
     // aPluginFilename is UTF8, not native-charset!
     bool InitForChrome(const std::string& aPluginFilename,
--- a/dom/plugins/ipc/PluginModuleParent.cpp
+++ b/dom/plugins/ipc/PluginModuleParent.cpp
@@ -16,16 +16,17 @@
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/PCrashReporterParent.h"
 #include "mozilla/ipc/MessageChannel.h"
 #include "mozilla/plugins/BrowserStreamParent.h"
 #include "mozilla/plugins/PluginBridge.h"
 #include "mozilla/plugins/PluginInstanceParent.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
+#include "mozilla/Telemetry.h"
 #include "mozilla/unused.h"
 #include "nsAutoPtr.h"
 #include "nsCRT.h"
 #include "nsIFile.h"
 #include "nsIObserverService.h"
 #include "nsNPAPIPlugin.h"
 #include "nsPrintfCString.h"
 #include "prsystem.h"
@@ -92,108 +93,56 @@ mozilla::plugins::SetupBridge(uint32_t a
     nsresult rv = host->GetPluginForContentProcess(aPluginId, getter_AddRefs(plugin));
     if (NS_FAILED(rv)) {
         return false;
     }
     PluginModuleParent* chromeParent = static_cast<PluginModuleParent*>(plugin->GetLibrary());
     return PPluginModule::Bridge(aContentParent, chromeParent);
 }
 
-// A linked list used to fetch newly created PluginModuleContentParent instances
-// for LoadModule. Each pending LoadModule call owns an element in this list.
-// The element's mModule field is filled in when the new
-// PluginModuleContentParent arrives from chrome.
-struct MOZ_STACK_CLASS SavedPluginModule
-{
-    SavedPluginModule() : mModule(nullptr), mNext(sSavedModuleStack)
-    {
-        sSavedModuleStack = this;
-    }
-    ~SavedPluginModule()
-    {
-        MOZ_ASSERT(sSavedModuleStack == this);
-        sSavedModuleStack = mNext;
-    }
-
-    PluginModuleContentParent* GetModule() { return mModule; }
-
-    // LoadModule can be on the stack multiple times since the intr message it
-    // sends will dispatch arbitrary incoming messages from the chrome process,
-    // which can include new HTTP data. This makes it somewhat tricky to match
-    // up the object created in PluginModuleContentParent::Create with the
-    // LoadModule call that asked for it.
-    //
-    // Each invocation of LoadModule pushes a new SavedPluginModule object on
-    // the sSavedModuleStack stack, with the most recent invocation at the
-    // front. LoadModule messages are always processed by the chrome process in
-    // order, and PluginModuleContentParent allocation messages will also be
-    // received in order. Therefore, we need to match up the first received
-    // PluginModuleContentParent creation message with the first sent LoadModule
-    // call. This call will be the last one in the list that doesn't already
-    // have a module attached to it.
-    static void SaveModule(PluginModuleContentParent* module)
-    {
-        SavedPluginModule* saved = sSavedModuleStack;
-        SavedPluginModule* prev = nullptr;
-        while (saved && !saved->mModule) {
-            prev = saved;
-            saved = saved->mNext;
-        }
-        MOZ_ASSERT(prev);
-        MOZ_ASSERT(!prev->mModule);
-        prev->mModule = module;
-    }
-
-private:
-    PluginModuleContentParent* mModule;
-    SavedPluginModule* mNext;
-
-    static SavedPluginModule* sSavedModuleStack;
-};
-
-SavedPluginModule* SavedPluginModule::sSavedModuleStack;
+PluginModuleContentParent* PluginModuleContentParent::sSavedModuleParent;
 
 /* static */ PluginLibrary*
 PluginModuleContentParent::LoadModule(uint32_t aPluginId)
 {
-    SavedPluginModule saved;
-
+    MOZ_ASSERT(!sSavedModuleParent);
     MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_Content);
 
     /*
      * We send a LoadPlugin message to the chrome process using an intr
-     * message. Before it sends its response, it sends a message to create a
-     * PluginModuleContentParent instance. That message is handled by
-     * PluginModuleContentParent::Create. See SavedPluginModule for details
-     * about how we match up the result of PluginModuleContentParent::Create
-     * with the LoadModule call that requested it.
+     * message. Before it sends its response, it sends a message to create
+     * PluginModuleParent instance. That message is handled by
+     * PluginModuleContentParent::Create, which saves the instance in
+     * sSavedModuleParent. We fetch it from there after LoadPlugin finishes.
      */
     dom::ContentChild* cp = dom::ContentChild::GetSingleton();
     if (!cp->CallLoadPlugin(aPluginId)) {
         return nullptr;
     }
 
-    PluginModuleContentParent* parent = saved.GetModule();
+    PluginModuleContentParent* parent = sSavedModuleParent;
     MOZ_ASSERT(parent);
+    sSavedModuleParent = nullptr;
 
     return parent;
 }
 
 /* static */ PluginModuleContentParent*
 PluginModuleContentParent::Create(mozilla::ipc::Transport* aTransport,
                                   base::ProcessId aOtherProcess)
 {
     nsAutoPtr<PluginModuleContentParent> parent(new PluginModuleContentParent());
     ProcessHandle handle;
     if (!base::OpenProcessHandle(aOtherProcess, &handle)) {
         // Bug 1090578 - need to kill |aOtherProcess|, it's boned.
         return nullptr;
     }
 
-    SavedPluginModule::SaveModule(parent);
+    MOZ_ASSERT(!sSavedModuleParent);
+    sSavedModuleParent = parent;
 
     DebugOnly<bool> ok = parent->Open(aTransport, handle, XRE_GetIOMessageLoop(),
                                       mozilla::ipc::ParentSide);
     MOZ_ASSERT(ok);
 
     // Request Windows message deferral behavior on our channel. This
     // applies to the top level and all sub plugin protocols since they
     // all share the same channel.
@@ -207,22 +156,25 @@ PluginLibrary*
 PluginModuleChromeParent::LoadModule(const char* aFilePath, uint32_t aPluginId)
 {
     PLUGIN_LOG_DEBUG_FUNCTION;
 
     int32_t prefSecs = Preferences::GetInt(kLaunchTimeoutPref, 0);
 
     // Block on the child process being launched and initialized.
     nsAutoPtr<PluginModuleChromeParent> parent(new PluginModuleChromeParent(aFilePath, aPluginId));
+    TimeStamp launchStart = TimeStamp::Now();
     bool launched = parent->mSubprocess->Launch(prefSecs * 1000);
     if (!launched) {
         // We never reached open
         parent->mShutdown = true;
         return nullptr;
     }
+    TimeStamp launchEnd = TimeStamp::Now();
+    parent->mTimeBlocked = (launchEnd - launchStart);
     parent->Open(parent->mSubprocess->GetChannel(),
                  parent->mSubprocess->GetChildProcessHandle());
 
     // Request Windows message deferral behavior on our channel. This
     // applies to the top level and all sub plugin protocols since they
     // all share the same channel.
     parent->GetIPCChannel()->SetChannelFlags(MessageChannel::REQUIRE_DEFERRED_MESSAGE_PROTECTION);
 
@@ -1425,24 +1377,35 @@ PluginModuleParent::NP_Initialize(NPNets
         *error = NPERR_GENERIC_ERROR;
         return NS_ERROR_FAILURE;
     }
 
     *error = NPERR_NO_ERROR;
     if (IsChrome()) {
         PluginSettings settings;
         GetSettings(&settings);
+        TimeStamp callNpInitStart = TimeStamp::Now();
         if (!CallNP_Initialize(settings, error)) {
             Close();
             return NS_ERROR_FAILURE;
         }
         else if (*error != NPERR_NO_ERROR) {
             Close();
             return NS_OK;
         }
+        TimeStamp callNpInitEnd = TimeStamp::Now();
+        mTimeBlocked += (callNpInitEnd - callNpInitStart);
+        /** mTimeBlocked measures the time that the main thread has been blocked
+         *  on plugin module initialization. As implemented, this is the sum of
+         *  plugin-container launch + NP_Initialize
+         */
+        Telemetry::Accumulate(Telemetry::BLOCKED_ON_PLUGIN_MODULE_INIT_MS,
+                              GetHistogramKey(),
+                              static_cast<uint32_t>(mTimeBlocked.ToMilliseconds()));
+        mTimeBlocked = TimeDuration();
     }
 
     SetPluginFuncs(pFuncs);
 
     return NS_OK;
 }
 #else
 nsresult
@@ -1465,24 +1428,27 @@ nsresult
 PluginModuleChromeParent::NP_Initialize(NPNetscapeFuncs* bFuncs, NPError* error)
 {
     nsresult rv = PluginModuleParent::NP_Initialize(bFuncs, error);
     if (NS_FAILED(rv))
         return rv;
 
     PluginSettings settings;
     GetSettings(&settings);
+    TimeStamp callNpInitStart = TimeStamp::Now();
     if (!CallNP_Initialize(settings, error)) {
         Close();
         return NS_ERROR_FAILURE;
     }
     if (*error != NPERR_NO_ERROR) {
         Close();
         return NS_OK;
     }
+    TimeStamp callNpInitEnd = TimeStamp::Now();
+    mTimeBlocked += (callNpInitEnd - callNpInitStart);
 
 #if defined XP_WIN
     // Send the info needed to join the chrome process's audio session to the
     // plugin process
     nsID id;
     nsString sessionName;
     nsString iconPath;
 
@@ -1490,16 +1456,27 @@ PluginModuleChromeParent::NP_Initialize(
                                                           iconPath)))
         unused << SendSetAudioSessionData(id, sessionName, iconPath);
 #endif
 
 #ifdef MOZ_CRASHREPORTER_INJECTOR
     InitializeInjector();
 #endif
 
+    /** This Accumulate must be placed below the call to InitializeInjector()
+     *  because mTimeBlocked is modified in that function.
+     *  mTimeBlocked measures the time that the main thread has been blocked
+     *  on plugin module initialization. As implemented, this is the sum of
+     *  plugin-container launch + toolhelp32 snapshot + NP_Initialize
+     */
+    Telemetry::Accumulate(Telemetry::BLOCKED_ON_PLUGIN_MODULE_INIT_MS,
+                          GetHistogramKey(),
+                          static_cast<uint32_t>(mTimeBlocked.ToMilliseconds()));
+    mTimeBlocked = TimeDuration();
+
     return NS_OK;
 }
 #endif
 
 nsresult
 PluginModuleParent::NP_Shutdown(NPError* error)
 {
     PLUGIN_LOG_DEBUG_METHOD;
@@ -1603,27 +1580,31 @@ PluginModuleParent::NPP_New(NPMIMEType p
 
     if (!parentInstance->Init()) {
         delete parentInstance;
         return NS_ERROR_FAILURE;
     }
 
     instance->pdata = parentInstance;
 
-    if (!CallPPluginInstanceConstructor(parentInstance,
-                                        nsDependentCString(pluginType), mode,
-                                        names, values, error)) {
-        // |parentInstance| is automatically deleted.
-        instance->pdata = nullptr;
-        // if IPC is down, we'll get an immediate "failed" return, but
-        // without *error being set.  So make sure that the error
-        // condition is signaled to nsNPAPIPluginInstance
-        if (NPERR_NO_ERROR == *error)
-            *error = NPERR_GENERIC_ERROR;
-        return NS_ERROR_FAILURE;
+    {   // Scope for timer
+        Telemetry::AutoTimer<Telemetry::BLOCKED_ON_PLUGIN_INSTANCE_INIT_MS>
+            timer(GetHistogramKey());
+        if (!CallPPluginInstanceConstructor(parentInstance,
+                                            nsDependentCString(pluginType), mode,
+                                            names, values, error)) {
+            // |parentInstance| is automatically deleted.
+            instance->pdata = nullptr;
+            // if IPC is down, we'll get an immediate "failed" return, but
+            // without *error being set.  So make sure that the error
+            // condition is signaled to nsNPAPIPluginInstance
+            if (NPERR_NO_ERROR == *error)
+                *error = NPERR_GENERIC_ERROR;
+            return NS_ERROR_FAILURE;
+        }
     }
 
     if (*error != NPERR_NO_ERROR) {
         NPP_Destroy(instance, 0);
         return NS_ERROR_FAILURE;
     }
 
     UpdatePluginTimeout();
@@ -1960,19 +1941,22 @@ PluginModuleChromeParent::InitializeInje
     int32_t lastSlash = path.RFindCharInSet("\\/");
     if (kNotFound == lastSlash)
         return;
 
     if (!StringBeginsWith(Substring(path, lastSlash + 1),
                           NS_LITERAL_CSTRING(FLASH_PLUGIN_PREFIX)))
         return;
 
+    TimeStamp th32Start = TimeStamp::Now();
     HANDLE snapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
     if (INVALID_HANDLE_VALUE == snapshot)
         return;
+    TimeStamp th32End = TimeStamp::Now();
+    mTimeBlocked += (th32End - th32Start);
 
     DWORD pluginProcessPID = GetProcessId(Process()->GetChildProcessHandle());
     mFlashProcess1 = GetFlashChildOfPID(pluginProcessPID, snapshot);
     if (mFlashProcess1) {
         InjectCrashReporterIntoProcess(mFlashProcess1, this);
 
         mFlashProcess2 = GetFlashChildOfPID(mFlashProcess1, snapshot);
         if (mFlashProcess2) {
--- a/dom/plugins/ipc/PluginModuleParent.h
+++ b/dom/plugins/ipc/PluginModuleParent.h
@@ -11,16 +11,17 @@
 #include "mozilla/FileUtils.h"
 #include "mozilla/HangMonitor.h"
 #include "mozilla/PluginLibrary.h"
 #include "mozilla/plugins/ScopedMethodFactory.h"
 #include "mozilla/plugins/PluginProcessParent.h"
 #include "mozilla/plugins/PPluginModuleParent.h"
 #include "mozilla/plugins/PluginMessageUtils.h"
 #include "mozilla/plugins/PluginTypes.h"
+#include "mozilla/TimeStamp.h"
 #include "npapi.h"
 #include "npfunctions.h"
 #include "nsAutoPtr.h"
 #include "nsDataHashtable.h"
 #include "nsHashKeys.h"
 #include "nsIObserver.h"
 
 #ifdef MOZ_CRASHREPORTER
@@ -100,16 +101,20 @@ public:
     }
 
     bool OkToCleanup() const {
         return !IsOnCxxStack();
     }
 
     void ProcessRemoteNativeEventsInInterruptCall();
 
+    nsCString GetHistogramKey() const {
+        return mPluginName + mPluginVersion;
+    }
+
 protected:
     virtual mozilla::ipc::RacyInterruptPolicy
     MediateInterruptRace(const Message& parent, const Message& child) MOZ_OVERRIDE
     {
         return MediateRace(parent, child);
     }
 
     virtual bool
@@ -240,16 +245,17 @@ protected:
     bool mGetSitesWithDataSupported;
     const NPNetscapeFuncs* mNPNIface;
     nsNPAPIPlugin* mPlugin;
     ScopedMethodFactory<PluginModuleParent> mTaskFactory;
     nsString mPluginDumpID;
     nsString mBrowserDumpID;
     nsString mHangID;
     nsRefPtr<nsIObserver> mProfilerObserver;
+    TimeDuration mTimeBlocked;
     nsCString mPluginName;
     nsCString mPluginVersion;
 
 #ifdef MOZ_X11
     // Dup of plugin's X socket, used to scope its resources to this
     // object instead of the plugin process's lifetime
     ScopedClose mPluginXSocketFdDup;
 #endif
@@ -269,16 +275,18 @@ class PluginModuleContentParent : public
                                              base::ProcessId aOtherProcess);
 
   private:
     explicit PluginModuleContentParent();
 
 #ifdef MOZ_CRASHREPORTER_INJECTOR
     void OnCrash(DWORD processID) MOZ_OVERRIDE {}
 #endif
+
+    static PluginModuleContentParent* sSavedModuleParent;
 };
 
 class PluginModuleChromeParent
     : public PluginModuleParent
     , public mozilla::HangMonitor::Annotator
 {
   public:
     /**
--- a/dom/plugins/ipc/PluginScriptableObjectUtils-inl.h
+++ b/dom/plugins/ipc/PluginScriptableObjectUtils-inl.h
@@ -55,22 +55,26 @@ mozilla::plugins::ConvertToVariant(const
 
     case Variant::Tdouble: {
       DOUBLE_TO_NPVARIANT(aRemoteVariant.get_double(), aVariant);
       break;
     }
 
     case Variant::TnsCString: {
       const nsCString& string = aRemoteVariant.get_nsCString();
-      NPUTF8* buffer = reinterpret_cast<NPUTF8*>(strdup(string.get()));
+      const size_t length = string.Length();
+      NPUTF8* buffer = static_cast<NPUTF8*>(::malloc(sizeof(NPUTF8) * (length + 1)));
       if (!buffer) {
         NS_ERROR("Out of memory!");
         return false;
       }
-      STRINGN_TO_NPVARIANT(buffer, string.Length(), aVariant);
+
+      std::copy(string.get(), string.get() + length, buffer);
+      buffer[length] = '\0';
+      STRINGN_TO_NPVARIANT(buffer, length, aVariant);
       break;
     }
 
     case Variant::TPPluginScriptableObjectParent: {
       NS_ASSERTION(aInstance, "Must have an instance!");
       NPObject* object = NPObjectFromVariant(aRemoteVariant);
       if (!object) {
         NS_ERROR("Er, this shouldn't fail!");
--- a/dom/plugins/ipc/PluginUtilsOSX.mm
+++ b/dom/plugins/ipc/PluginUtilsOSX.mm
@@ -215,36 +215,42 @@ NPError mozilla::plugins::PluginUtilsOSX
   // Set the native cursor to the OS default (an arrow) before displaying the
   // context menu.  Otherwise (if the plugin has changed the cursor) it may
   // stay as the plugin has set it -- which means it may be invisible.  We
   // need to do this because we display the context menu without making the
   // plugin process the foreground process.  If we did, the cursor would
   // change to an arrow cursor automatically -- as it does in Chrome.
   [[NSCursor arrowCursor] set];
 
-  // Create a timer to process browser events while waiting
-  // on the menu. This prevents the browser from hanging
-  // during the lifetime of the menu.
-  EventProcessor* eventProcessor = [[EventProcessor alloc] init];
-  [eventProcessor setRemoteEvents:remoteEvent pluginModule:pluginModule];
-  NSTimer *eventTimer = [NSTimer timerWithTimeInterval:EVENT_PROCESS_DELAY
-                             target:eventProcessor selector:@selector(onTick) 
-                             userInfo:nil repeats:TRUE];
-  // Use NSEventTrackingRunLoopMode otherwise the timer will
-  // not fire during the right click menu.
-  [[NSRunLoop currentRunLoop] addTimer:eventTimer 
-                              forMode:NSEventTrackingRunLoopMode];
+  EventProcessor* eventProcessor = nullptr;
+  NSTimer *eventTimer = nullptr;
+  if (pluginModule) {
+    // Create a timer to process browser events while waiting
+    // on the menu. This prevents the browser from hanging
+    // during the lifetime of the menu.
+    eventProcessor = [[EventProcessor alloc] init];
+    [eventProcessor setRemoteEvents:remoteEvent pluginModule:pluginModule];
+    eventTimer = [NSTimer timerWithTimeInterval:EVENT_PROCESS_DELAY
+                                   target:eventProcessor selector:@selector(onTick)
+                                   userInfo:nil repeats:TRUE];
+    // Use NSEventTrackingRunLoopMode otherwise the timer will
+    // not fire during the right click menu.
+    [[NSRunLoop currentRunLoop] addTimer:eventTimer
+                                 forMode:NSEventTrackingRunLoopMode];
+  }
 
   NSMenu* nsmenu = reinterpret_cast<NSMenu*>(aMenu);
   NSPoint screen_point = ::NSMakePoint(aX, aY);
 
   [nsmenu popUpMenuPositioningItem:nil atLocation:screen_point inView:nil];
 
-  [eventTimer invalidate];
-  [eventProcessor release];
+  if (pluginModule) {
+    [eventTimer invalidate];
+    [eventProcessor release];
+  }
 
   return NPERR_NO_ERROR;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NPERR_GENERIC_ERROR);
 }
 
 void mozilla::plugins::PluginUtilsOSX::InvokeNativeEventLoop()
 {
--- a/dom/plugins/test/mochitest/cocoa_focus.html
+++ b/dom/plugins/test/mochitest/cocoa_focus.html
@@ -54,64 +54,64 @@
       }
       is(initialStateUnknown, true, "Initial state should be unknown, assumed false.");
 
       // Give the plugin focus (the window is already focused).
       utils.sendNativeMouseEvent(plugin1X, plugin1Y, NSLeftMouseDown, 0, plugin1);
       utils.sendNativeMouseEvent(plugin1X, plugin1Y, NSLeftMouseUp, 0, plugin1);
       expectedEventCount++;
 
-      is(plugin1.getFocusState(), true, "Plugin should have focus.");
+      is(plugin1.getFocusState(), true, "(1) Plugin should have focus.");
       is(plugin1.getFocusEventCount(), expectedEventCount, "Focus event count should be " + expectedEventCount);
 
       // Make sure window activation state changes don't spontaneously
       // change plugin focus.
 
       // Blur the window.
       window.blur();
 
-      is(plugin1.getFocusState(), true, "Plugin should still have focus.");
+      is(plugin1.getFocusState(), true, "(2) Plugin should still have focus.");
       is(plugin1.getFocusEventCount(), expectedEventCount, "Focus event count should be " + expectedEventCount);
 
       // Focus the window.
       window.focus();
 
-      is(plugin1.getFocusState(), true, "Plugin should still have focus.");
+      is(plugin1.getFocusState(), true, "(3) Plugin should still have focus.");
       is(plugin1.getFocusEventCount(), expectedEventCount, "Focus event count should be " + expectedEventCount);
 
       // Take focus from the plugin.
       utils.sendNativeMouseEvent(plugin2X, plugin2Y, NSLeftMouseDown, 0, plugin2);
       utils.sendNativeMouseEvent(plugin2X, plugin2Y, NSLeftMouseUp, 0, plugin2);
       expectedEventCount++;
 
-      is(plugin1.getFocusState(), false, "Plugin should not have focus.");
+      is(plugin1.getFocusState(), false, "(4) Plugin should not have focus.");
       is(plugin1.getFocusEventCount(), expectedEventCount, "Focus event count should be " + expectedEventCount);
 
       // Make sure window activation causes the plugin to be informed of focus
       // changes that took place while the window was inactive.
 
       // Give the plugin focus (the window is already focused).
       utils.sendNativeMouseEvent(plugin1X, plugin1Y, NSLeftMouseDown, 0, plugin1);
       utils.sendNativeMouseEvent(plugin1X, plugin1Y, NSLeftMouseUp, 0, plugin1);
       expectedEventCount++;
 
       // Blur the window.
       window.blur();
 
       // Take focus from the plugin while the window is blurred.
       plugin2.focus();
 
-      is(plugin1.getFocusState(), true, "Plugin should still have focus.");
+      is(plugin1.getFocusState(), true, "(5) Plugin should still have focus.");
       is(plugin1.getFocusEventCount(), expectedEventCount, "Focus event count should be " + expectedEventCount);
 
       // Focus the window.
       window.focus();
       expectedEventCount++;
 
-      is(plugin1.getFocusState(), false, "Plugin should not have focus.");
+      is(plugin1.getFocusState(), false, "(6) Plugin should not have focus.");
       is(plugin1.getFocusEventCount(), expectedEventCount, "Focus event count should be " + expectedEventCount);
 
       window.opener.testsFinished();
     }
 
     // Onload hander doesn't work for these tests -- no events arrive at the plugin.
     window.opener.SimpleTest.waitForFocus(runTests, window);
 
--- a/dom/plugins/test/mochitest/mochitest.ini
+++ b/dom/plugins/test/mochitest/mochitest.ini
@@ -105,15 +105,16 @@ skip-if = true # disabled due to oddness
 [test_propertyAndMethod.html]
 [test_queryContentsScaleFactor.html]
 skip-if = toolkit != "cocoa"
 [test_redirect_handling.html]
 [test_secondPlugin.html]
 [test_src_url_change.html]
 [test_streamNotify.html]
 skip-if = e10s
+[test_stringHandling.html]
 [test_streamatclose.html]
 [test_twostreams.html]
 [test_windowed_invalidate.html]
 skip-if = os != "win"
 [test_visibility.html]
 skip-if = toolkit == "cocoa"
 [test_zero_opacity.html]
--- a/dom/plugins/test/mochitest/test_convertpoint.xul
+++ b/dom/plugins/test/mochitest/test_convertpoint.xul
@@ -24,21 +24,20 @@ function runTests() {
   // the plugin hasn't been placed yet.
   if (pluginElement.convertPointX(1, 0, 0, 2) == 0) {
     setTimeout(runTests, 0);
     return;
   }
 
   var domWindowUtils = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
                              .getInterface(Components.interfaces.nsIDOMWindowUtils);
-  var devPxPerCSSPx = domWindowUtils.screenPixelsPerCSSPixel;
 
   var pluginRect = pluginElement.getBoundingClientRect();
-  var pluginX = (pluginRect.left * devPxPerCSSPx) + ((window.mozInnerScreenX * devPxPerCSSPx) - window.screenX);
-  var pluginY = (pluginRect.top * devPxPerCSSPx) + ((window.mozInnerScreenY * devPxPerCSSPx) - window.screenY);
+  var pluginX = pluginRect.left + (window.mozInnerScreenX - window.screenX);
+  var pluginY = pluginRect.top + (window.mozInnerScreenY - window.screenY);
 
   var windowX = window.screenX;
   var windowY = window.screenY;
   var windowHeight = window.outerHeight;
 
   var screenHeight = window.screen.height;
 
   // arbitrary coordinates of test point in plugin top-left origin terms
new file mode 100644
--- /dev/null
+++ b/dom/plugins/test/mochitest/test_stringHandling.html
@@ -0,0 +1,35 @@
+<html>
+<head>
+  <title>NPAPI string test</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript" src="utils.js"></script>
+
+  <link rel="stylesheet" type="text/css"
+        href="/tests/SimpleTest/test.css" />
+</head>
+
+<body onload="runTests()">
+  <script class="testbody" type="application/javascript">
+    SimpleTest.waitForExplicitFinish();
+    setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
+
+    function runTests() {
+      try {
+        var plugin = document.getElementById("plugin1");
+        var badData = 'foo ' + '\x00'.repeat(260000);
+        var ret = plugin.echoString(badData);
+        ok(true, "Did not crash.");
+        is(ret, badData, "Returned string should equal what we passed in.");
+      } catch (e) {
+        ok(false, "Failed to call plugin.echoString() properly.");
+      } finally {
+        SimpleTest.finish();
+      }
+    }
+  </script>
+
+  <p id="display"></p>
+
+  <embed id="plugin1" type="application/x-test" width="400" height="400"></embed>
+</body>
+</html>
--- a/dom/plugins/test/testplugin/nptest.cpp
+++ b/dom/plugins/test/testplugin/nptest.cpp
@@ -166,16 +166,17 @@ static bool isVisible(NPObject* npobj, c
 static bool getWindowPosition(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool constructObject(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool setSitesWithData(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool setSitesWithDataCapabilities(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getLastKeyText(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getNPNVdocumentOrigin(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool getMouseUpEventCount(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 static bool queryContentsScaleFactor(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
+static bool echoString(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result);
 
 static const NPUTF8* sPluginMethodIdentifierNames[] = {
   "npnEvaluateTest",
   "npnInvokeTest",
   "npnInvokeDefaultTest",
   "setUndefinedValueTest",
   "identifierToStringTest",
   "timerTest",
@@ -230,17 +231,18 @@ static const NPUTF8* sPluginMethodIdenti
   "isVisible",
   "getWindowPosition",
   "constructObject",
   "setSitesWithData",
   "setSitesWithDataCapabilities",
   "getLastKeyText",
   "getNPNVdocumentOrigin",
   "getMouseUpEventCount",
-  "queryContentsScaleFactor"
+  "queryContentsScaleFactor",
+  "echoString",
 };
 static NPIdentifier sPluginMethodIdentifiers[ARRAY_LENGTH(sPluginMethodIdentifierNames)];
 static const ScriptableFunction sPluginMethodFunctions[] = {
   npnEvaluateTest,
   npnInvokeTest,
   npnInvokeDefaultTest,
   setUndefinedValueTest,
   identifierToStringTest,
@@ -296,17 +298,18 @@ static const ScriptableFunction sPluginM
   isVisible,
   getWindowPosition,
   constructObject,
   setSitesWithData,
   setSitesWithDataCapabilities,
   getLastKeyText,
   getNPNVdocumentOrigin,
   getMouseUpEventCount,
-  queryContentsScaleFactor
+  queryContentsScaleFactor,
+  echoString,
 };
 
 STATIC_ASSERT(ARRAY_LENGTH(sPluginMethodIdentifierNames) ==
               ARRAY_LENGTH(sPluginMethodFunctions));
 
 static const NPUTF8* sPluginPropertyIdentifierNames[] = {
   "propertyAndMethod"
 };
@@ -3389,17 +3392,17 @@ getTopLevelWindowActivationEventCount(NP
   InstanceData* id = static_cast<InstanceData*>(npp->pdata);
 
   INT32_TO_NPVARIANT(id->topLevelWindowActivationEventCount, *result);
 
   return true;
 }
 
 // Returns top-level window activation state as indicated by Cocoa NPAPI's
-// NPCocoaEventWindowFocusChanged events - 'true' if active, 'false' if not.
+// NPCocoaEventFocusChanged events - 'true' if active, 'false' if not.
 // Throws an exception if no events have been received and thus this state
 // is unknown.
 bool
 getFocusState(NPObject* npobj, const NPVariant* args, uint32_t argCount,
               NPVariant* result)
 {
   if (argCount != 0)
     return false;
@@ -3679,8 +3682,31 @@ bool queryContentsScaleFactor(NPObject* 
                              NPNVcontentsScaleFactor, &scaleFactor);
   if (err != NPERR_NO_ERROR) {
     return false;
   }
 #endif
   DOUBLE_TO_NPVARIANT(scaleFactor, *result);
   return true;
 }
+
+bool echoString(NPObject* npobj, const NPVariant* args, uint32_t argCount, NPVariant* result)
+{
+  if (argCount != 1) {
+    return false;
+  }
+
+  if (!NPVARIANT_IS_STRING(args[0])) {
+    return false;
+  }
+
+  const NPString& arg = NPVARIANT_TO_STRING(args[0]);
+  NPUTF8* buffer = static_cast<NPUTF8*>(NPN_MemAlloc(sizeof(NPUTF8) * arg.UTF8Length));
+  if (!buffer) {
+    return false;
+  }
+
+  std::copy(arg.UTF8Characters, arg.UTF8Characters + arg.UTF8Length, buffer);
+  STRINGN_TO_NPVARIANT(buffer, arg.UTF8Length, *result);
+
+  return true;
+}
+
--- a/dom/plugins/test/testplugin/nptest_macosx.mm
+++ b/dom/plugins/test/testplugin/nptest_macosx.mm
@@ -233,16 +233,17 @@ pluginHandleEvent(InstanceData* instance
 
   switch (cocoaEvent->type) {
     case NPCocoaEventDrawRect:
       pluginDraw(instanceData, cocoaEvent);
       break;
     case NPCocoaEventMouseDown:
     case NPCocoaEventMouseUp:
     case NPCocoaEventMouseMoved:
+    case NPCocoaEventMouseDragged:
       instanceData->lastMouseX = (int32_t)cocoaEvent->data.mouse.pluginX;
       instanceData->lastMouseY = (int32_t)cocoaEvent->data.mouse.pluginY;
       if (cocoaEvent->type == NPCocoaEventMouseUp) {
         instanceData->mouseUpEventCount++;
       }
       break;
     case NPCocoaEventWindowFocusChanged:
       instanceData->topLevelWindowActivationState = cocoaEvent->data.focus.hasFocus ?
--- a/dom/quota/QuotaManager.cpp
+++ b/dom/quota/QuotaManager.cpp
@@ -4686,24 +4686,20 @@ StorageDirectoryHelper::CreateOrUpgradeM
     if (mPersistent) {
       rv = MaybeUpgradeOriginDirectory(originDir);
       if (NS_WARN_IF(NS_FAILED(rv))) {
         return rv;
       }
     }
 
     if (leafName.EqualsLiteral(kChromeOrigin)) {
-      if (mPersistent) {
-        OriginProps* originProps = mOriginProps.AppendElement();
-        originProps->mDirectory = originDir;
-        originProps->mSpec = kChromeOrigin;
-        originProps->mType = OriginProps::eChrome;
-      } else {
-        NS_WARNING("chrome in temporary storage directory?!");
-      }
+      OriginProps* originProps = mOriginProps.AppendElement();
+      originProps->mDirectory = originDir;
+      originProps->mSpec = kChromeOrigin;
+      originProps->mType = OriginProps::eChrome;
     } else {
       nsCString spec;
       uint32_t appId;
       bool inMozBrowser;
       if (NS_WARN_IF(!OriginParser::ParseOrigin(NS_ConvertUTF16toUTF8(leafName),
                                                 &appId, &inMozBrowser, spec))) {
         return NS_ERROR_FAILURE;
       }
@@ -4830,18 +4826,16 @@ StorageDirectoryHelper::RunOnMainThread(
 
   for (uint32_t count = mOriginProps.Length(), index = 0;
        index < count;
        index++) {
     OriginProps& originProps = mOriginProps[index];
 
     switch (originProps.mType) {
       case OriginProps::eChrome: {
-        MOZ_ASSERT(mPersistent);
-
         QuotaManager::GetInfoForChrome(&originProps.mGroup,
                                        &originProps.mOrigin,
                                        &originProps.mIsApp,
                                        nullptr);
         break;
       }
 
       case OriginProps::eContent: {
--- a/dom/security/nsCSPContext.cpp
+++ b/dom/security/nsCSPContext.cpp
@@ -130,16 +130,23 @@ nsCSPContext::ShouldLoad(nsContentPolicy
   if (isCached && cacheKey.Length() > 0) {
     // this is cached, use the cached value.
     return NS_OK;
   }
 
   // Default decision, CSP can revise it if there's a policy to enforce
   *outDecision = nsIContentPolicy::ACCEPT;
 
+  // If the content type doesn't map to a CSP directive, there's nothing for
+  // CSP to do.
+  CSPDirective dir = CSP_ContentTypeToDirective(aContentType);
+  if (dir == nsIContentSecurityPolicy::NO_DIRECTIVE) {
+    return NS_OK;
+  }
+
   // This may be a load or a preload. If it is a preload, the document will
   // not have been fully parsed yet, and aRequestContext will be an
   // nsIDOMHTMLDocument rather than the nsIDOMHTMLElement associated with the
   // resource. As a result, we cannot extract the element's corresponding
   // nonce attribute, and so we cannot correctly check the nonce on a preload.
   //
   // Therefore, the decision returned here for a preload may be *incorrect* as
   // it cannot take the nonce into account. We will still check the load, but
@@ -160,63 +167,106 @@ nsCSPContext::ShouldLoad(nsContentPolicy
   if (!isPreload) {
     nsCOMPtr<nsIDOMHTMLElement> htmlElement = do_QueryInterface(aRequestContext);
     if (htmlElement) {
       rv = htmlElement->GetAttribute(NS_LITERAL_STRING("nonce"), nonce);
       NS_ENSURE_SUCCESS(rv, rv);
     }
   }
 
-  nsAutoString violatedDirective;
-  for (uint32_t p = 0; p < mPolicies.Length(); p++) {
-    if (!mPolicies[p]->permits(aContentType,
-                               aContentLocation,
-                               nonce,
-                               // aExtra is only non-null if
-                               // the channel got redirected.
-                               (aExtra != nullptr),
-                               violatedDirective)) {
-      // If the policy is violated and not report-only, reject the load and
-      // report to the console
-      if (!mPolicies[p]->getReportOnlyFlag()) {
-        CSPCONTEXTLOG(("nsCSPContext::ShouldLoad, nsIContentPolicy::REJECT_SERVER"));
-        *outDecision = nsIContentPolicy::REJECT_SERVER;
-      }
+  // aExtra is only non-null if the channel got redirected.
+  bool wasRedirected = (aExtra != nullptr);
+  nsCOMPtr<nsIURI> originalURI = do_QueryInterface(aExtra);
 
-      // Do not send a report or notify observers if this is a preload - the
-      // decision may be wrong due to the inability to get the nonce, and will
-      // incorrectly fail the unit tests.
-      if (!isPreload) {
-        nsCOMPtr<nsIURI> originalURI = do_QueryInterface(aExtra);
-        this->AsyncReportViolation(aContentLocation,
-                                   originalURI,   /* in case of redirect originalURI is not null */
-                                   violatedDirective,
-                                   p,             /* policy index        */
-                                   EmptyString(), /* no observer subject */
-                                   EmptyString(), /* no source file      */
-                                   EmptyString(), /* no script sample    */
-                                   0);            /* no line number      */
-      }
-    }
-  }
+  bool permitted = permitsInternal(dir,
+                                   aContentLocation,
+                                   originalURI,
+                                   nonce,
+                                   wasRedirected,
+                                   isPreload,
+                                   false,     // allow fallback to default-src
+                                   true,      // send violation reports
+                                   true);     // send blocked URI in violation reports
+
+  *outDecision = permitted ? nsIContentPolicy::ACCEPT
+                           : nsIContentPolicy::REJECT_SERVER;
+
   // Done looping, cache any relevant result
   if (cacheKey.Length() > 0 && !isPreload) {
     mShouldLoadCache.Put(cacheKey, *outDecision);
   }
 
 #ifdef PR_LOGGING
   {
   nsAutoCString spec;
   aContentLocation->GetSpec(spec);
   CSPCONTEXTLOG(("nsCSPContext::ShouldLoad, decision: %s, aContentLocation: %s", *outDecision ? "load" : "deny", spec.get()));
   }
 #endif
   return NS_OK;
 }
 
+bool
+nsCSPContext::permitsInternal(CSPDirective aDir,
+                              nsIURI* aContentLocation,
+                              nsIURI* aOriginalURI,
+                              const nsAString& aNonce,
+                              bool aWasRedirected,
+                              bool aIsPreload,
+                              bool aSpecific,
+                              bool aSendViolationReports,
+                              bool aSendContentLocationInViolationReports)
+{
+  bool permits = true;
+
+  nsAutoString violatedDirective;
+  for (uint32_t p = 0; p < mPolicies.Length(); p++) {
+
+    // According to the W3C CSP spec, frame-ancestors checks are ignored for
+    // report-only policies (when "monitoring").
+    if (aDir == nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE &&
+        mPolicies[p]->getReportOnlyFlag()) {
+      continue;
+    }
+
+    if (!mPolicies[p]->permits(aDir,
+                               aContentLocation,
+                               aNonce,
+                               aWasRedirected,
+                               aSpecific,
+                               violatedDirective)) {
+      // If the policy is violated and not report-only, reject the load and
+      // report to the console
+      if (!mPolicies[p]->getReportOnlyFlag()) {
+        CSPCONTEXTLOG(("nsCSPContext::permitsInternal, false"));
+        permits = false;
+      }
+
+      // Do not send a report or notify observers if this is a preload - the
+      // decision may be wrong due to the inability to get the nonce, and will
+      // incorrectly fail the unit tests.
+      if (!aIsPreload && aSendViolationReports) {
+        this->AsyncReportViolation((aSendContentLocationInViolationReports ?
+                                    aContentLocation : nullptr),
+                                   aOriginalURI,  /* in case of redirect originalURI is not null */
+                                   violatedDirective,
+                                   p,             /* policy index        */
+                                   EmptyString(), /* no observer subject */
+                                   EmptyString(), /* no source file      */
+                                   EmptyString(), /* no script sample    */
+                                   0);            /* no line number      */
+      }
+    }
+  }
+
+  return permits;
+}
+
+
+
 /* ===== nsISupports implementation ========== */
 
 NS_IMPL_CLASSINFO(nsCSPContext,
                   nullptr,
                   nsIClassInfo::MAIN_THREAD_ONLY,
                   NS_CSPCONTEXT_CID)
 
 NS_IMPL_ISUPPORTS_CI(nsCSPContext,
@@ -1036,138 +1086,76 @@ nsCSPContext::PermitsAncestry(nsIDocShel
     // next ancestor
     treeItem = parentTreeItem;
   }
 
   nsAutoString violatedDirective;
 
   // Now that we've got the ancestry chain in ancestorsArray, time to check
   // them against any CSP.
-  for (uint32_t i = 0; i < mPolicies.Length(); i++) {
-
-    // According to the W3C CSP spec, frame-ancestors checks are ignored for
-    // report-only policies (when "monitoring").
-    if (mPolicies[i]->getReportOnlyFlag()) {
-      continue;
-    }
+  // NOTE:  the ancestors are not allowed to be sent cross origin; this is a
+  // restriction not placed on subresource loads.
 
-    for (uint32_t a = 0; a < ancestorsArray.Length(); a++) {
-      // TODO(sid) the mapping from frame-ancestors context to TYPE_DOCUMENT is
-      // forced. while this works for now, we will implement something in
-      // bug 999656.
+  for (uint32_t a = 0; a < ancestorsArray.Length(); a++) {
 #ifdef PR_LOGGING
-      {
-      nsAutoCString spec;
-      ancestorsArray[a]->GetSpec(spec);
-      CSPCONTEXTLOG(("nsCSPContext::PermitsAncestry, checking ancestor: %s", spec.get()));
-      }
+    {
+    nsAutoCString spec;
+    ancestorsArray[a]->GetSpec(spec);
+    CSPCONTEXTLOG(("nsCSPContext::PermitsAncestry, checking ancestor: %s", spec.get()));
+    }
 #endif
-      if (!mPolicies[i]->permits(nsIContentPolicy::TYPE_DOCUMENT,
-                                 ancestorsArray[a],
-                                 EmptyString(), // no nonce
-                                 false, // no redirect
-                                 violatedDirective)) {
-        // Policy is violated
-        // Send reports, but omit the ancestor URI if cross-origin as per spec
-        // (it is a violation of the same-origin policy).
-        bool okToSendAncestor = NS_SecurityCompareURIs(ancestorsArray[a], mSelfURI, true);
+    // omit the ancestor URI in violation reports if cross-origin as per spec
+    // (it is a violation of the same-origin policy).
+    bool okToSendAncestor = NS_SecurityCompareURIs(ancestorsArray[a], mSelfURI, true);
+
 
-        this->AsyncReportViolation((okToSendAncestor ? ancestorsArray[a] : nullptr),
-                                   nullptr,       /* originalURI in case of redirect */
-                                   violatedDirective,
-                                   i,             /* policy index        */
-                                   EmptyString(), /* no observer subject */
-                                   EmptyString(), /* no source file      */
-                                   EmptyString(), /* no script sample    */
-                                   0);            /* no line number      */
-        *outPermitsAncestry = false;
-      }
+    bool permits = permitsInternal(nsIContentSecurityPolicy::FRAME_ANCESTORS_DIRECTIVE,
+                                   ancestorsArray[a],
+                                   nullptr, // no redirect here.
+                                   EmptyString(), // no nonce
+                                   false,   // no redirect here.
+                                   false,   // not a preload.
+                                   true,    // specific, do not use default-src
+                                   true,    // send violation reports
+                                   okToSendAncestor);
+    if (!permits) {
+      *outPermitsAncestry = false;
     }
   }
   return NS_OK;
 }
 
 NS_IMETHODIMP
-nsCSPContext::PermitsBaseURI(nsIURI* aURI, bool* outPermitsBaseURI)
+nsCSPContext::Permits(nsIURI* aURI,
+                      CSPDirective aDir,
+                      bool aSpecific,
+                      bool* outPermits)
 {
   // Can't perform check without aURI
   if (aURI == nullptr) {
     return NS_ERROR_FAILURE;
   }
 
-  *outPermitsBaseURI = true;
-
-  for (uint32_t i = 0; i < mPolicies.Length(); i++) {
-    if (!mPolicies[i]->permitsBaseURI(aURI)) {
-      // policy is violated, report to caller if not report-only
-      if (!mPolicies[i]->getReportOnlyFlag()) {
-        *outPermitsBaseURI = false;
-      }
-      nsAutoString violatedDirective;
-      mPolicies[i]->getDirectiveAsString(CSP_BASE_URI, violatedDirective);
-      this->AsyncReportViolation(aURI,
-                                 nullptr,       /* originalURI in case of redirect */
-                                 violatedDirective,
-                                 i,             /* policy index        */
-                                 EmptyString(), /* no observer subject */
-                                 EmptyString(), /* no source file      */
-                                 EmptyString(), /* no script sample    */
-                                 0);            /* no line number      */
-    }
-  }
+  *outPermits = permitsInternal(aDir,
+                                aURI,
+                                nullptr,  // no original (pre-redirect) URI
+                                EmptyString(),  // no nonce
+                                false,    // not redirected.
+                                false,    // not a preload.
+                                aSpecific,
+                                true,     // send violation reports
+                                true);    // send blocked URI in violation reports
 
 #ifdef PR_LOGGING
   {
     nsAutoCString spec;
     aURI->GetSpec(spec);
-    CSPCONTEXTLOG(("nsCSPContext::PermitsBaseURI, aUri: %s, isAllowed: %s",
-                  spec.get(),
-                  *outPermitsBaseURI ? "allow" : "deny"));
-  }
-#endif
-
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-nsCSPContext::PermitsFormAction(nsIURI* aURI, bool* outPermitsFormAction)
-{
-  // Can't perform check without aURI
-  if (!aURI) {
-    return NS_ERROR_FAILURE;
-  }
-
-  *outPermitsFormAction = true;
-
-  for (uint32_t i = 0; i < mPolicies.Length(); i++) {
-    if (!mPolicies[i]->permitsFormAction(aURI)) {
-      // policy is violated, report to caller if not report-only
-      if (!mPolicies[i]->getReportOnlyFlag()) {
-        *outPermitsFormAction = false;
-      }
-      nsAutoString violatedDirective;
-      mPolicies[i]->getDirectiveAsString(CSP_FORM_ACTION, violatedDirective);
-      this->AsyncReportViolation(aURI,
-                                 mSelfURI,
-                                 violatedDirective,
-                                 i,             /* policy index        */
-                                 EmptyString(), /* no observer subject */
-                                 EmptyString(), /* no source file      */
-                                 EmptyString(), /* no script sample    */
-                                 0);            /* no line number      */
-    }
-  }
-
-#ifdef PR_LOGGING
-  {
-    nsAutoCString spec;
-    aURI->GetSpec(spec);
-    CSPCONTEXTLOG(("nsCSPContext::PermitsFormAction, aUri: %s, isAllowed: %s",
-                  spec.get(),
-                  *outPermitsFormAction ? "allow" : "deny"));
+    CSPCONTEXTLOG(("nsCSPContext::Permits, aUri: %s, aDir: %d, isAllowed: %s",
+                  spec.get(), aDir,
+                  *outPermits ? "allow" : "deny"));
   }
 #endif
 
   return NS_OK;
 }
 
 /* ========== CSPViolationReportListener implementation ========== */
 
--- a/dom/security/nsCSPContext.h
+++ b/dom/security/nsCSPContext.h
@@ -56,16 +56,26 @@ class nsCSPContext : public nsIContentSe
 
   private:
     NS_IMETHODIMP getAllowsInternal(nsContentPolicyType aContentType,
                                     enum CSPKeyword aKeyword,
                                     const nsAString& aNonceOrContent,
                                     bool* outShouldReportViolations,
                                     bool* outIsAllowed) const;
 
+    bool permitsInternal(CSPDirective aDir,
+                         nsIURI* aContentLocation,
+                         nsIURI* aOriginalURI,
+                         const nsAString& aNonce,
+                         bool aWasRedirected,
+                         bool aIsPreload,
+                         bool aSpecific,
+                         bool aSendViolationReports,
+                         bool aSendContentLocationInViolationReports);
+
     nsCOMPtr<nsIURI>                           mReferrer;
     uint64_t                                   mInnerWindowID; // used for web console logging
     nsTArray<nsCSPPolicy*>                     mPolicies;
     nsCOMPtr<nsIURI>                           mSelfURI;
     nsDataHashtable<nsCStringHashKey, int16_t> mShouldLoadCache;
     nsCOMPtr<nsILoadGroup>                     mCallingChannelLoadGroup;
     nsWeakPtr                                  mLoadingContext;
 };
--- a/dom/security/nsCSPParser.cpp
+++ b/dom/security/nsCSPParser.cpp
@@ -891,17 +891,17 @@ nsCSPParser::reportURIList(nsTArray<nsCS
 void
 nsCSPParser::directiveValue(nsTArray<nsCSPBaseSrc*>& outSrcs)
 {
   CSPPARSERLOG(("nsCSPParser::directiveValue"));
 
   // The tokenzier already generated an array in the form of
   // [ name, src, src, ... ], no need to parse again, but
   // special case handling in case the directive is report-uri.
-  if (CSP_IsDirective(mCurDir[0], CSP_REPORT_URI)) {
+  if (CSP_IsDirective(mCurDir[0], nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE)) {
     reportURIList(outSrcs);
     return;
   }
   // Otherwise just forward to sourceList
   sourceList(outSrcs);
 }
 
 // directive-name = 1*( ALPHA / DIGIT / "-" )
@@ -919,32 +919,32 @@ nsCSPParser::directiveName()
                              params, ArrayLength(params));
     return nullptr;
   }
 
   // The directive 'reflected-xss' is part of CSP 1.1, see:
   // http://www.w3.org/TR/2014/WD-CSP11-20140211/#reflected-xss
   // Currently we are not supporting that directive, hence we log a
   // warning to the console and ignore the directive including its values.
-  if (CSP_IsDirective(mCurToken, CSP_REFLECTED_XSS)) {
+  if (CSP_IsDirective(mCurToken, nsIContentSecurityPolicy::REFLECTED_XSS_DIRECTIVE)) {
     const char16_t* params[] = { mCurToken.get() };
     logWarningErrorToConsole(nsIScriptError::warningFlag, "notSupportingDirective",
                              params, ArrayLength(params));
     return nullptr;
   }
 
   // Make sure the directive does not already exist
   // (see http://www.w3.org/TR/CSP11/#parsing)
-  if (mPolicy->directiveExists(CSP_DirectiveToEnum(mCurToken))) {
+  if (mPolicy->hasDirective(CSP_StringToCSPDirective(mCurToken))) {
     const char16_t* params[] = { mCurToken.get() };
     logWarningErrorToConsole(nsIScriptError::warningFlag, "duplicateDirective",
                              params, ArrayLength(params));
     return nullptr;
   }
-  return new nsCSPDirective(CSP_DirectiveToEnum(mCurToken));
+  return new nsCSPDirective(CSP_StringToCSPDirective(mCurToken));
 }
 
 // directive = *WSP [ directive-name [ WSP directive-value ] ]
 void
 nsCSPParser::directive()
 {
   // Set the directiveName to mCurToken
   // Remember, the directive name is stored at index 0
@@ -1034,17 +1034,17 @@ nsCSPParser::parseContentSecurityPolicy(
   nsCSPParser parser(tokens, aSelfURI, aInnerWindowID);
 
   // Start the parser to generate a new CSPPolicy using the generated tokens.
   nsCSPPolicy* policy = parser.policy();
 
   // Check that report-only policies define a report-uri, otherwise log warning.
   if (aReportOnly) {
     policy->setReportOnlyFlag(true);
-    if (!policy->directiveExists(CSP_REPORT_URI)) {
+    if (!policy->hasDirective(nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE)) {
       nsAutoCString prePath;
       nsresult rv = aSelfURI->GetPrePath(prePath);
       NS_ENSURE_SUCCESS(rv, policy);
       NS_ConvertUTF8toUTF16 unicodePrePath(prePath);
       const char16_t* params[] = { unicodePrePath.get() };
       parser.logWarningErrorToConsole(nsIScriptError::warningFlag, "reportURInotInReportOnlyHeader",
                                       params, ArrayLength(params));
     }
--- a/dom/security/nsCSPUtils.cpp
+++ b/dom/security/nsCSPUtils.cpp
@@ -122,16 +122,69 @@ CSP_LogLocalizedStr(const char16_t* aNam
   nsXPIDLString logMsg;
   CSP_GetLocalizedStr(aName, aParams, aLength, getter_Copies(logMsg));
   CSP_LogMessage(logMsg, aSourceName, aSourceLine,
                  aLineNumber, aColumnNumber, aFlags,
                  aCategory, aInnerWindowID);
 }
 
 /* ===== Helpers ============================ */
+CSPDirective
+CSP_ContentTypeToDirective(nsContentPolicyType aType)
+{
+  switch (aType) {
+    case nsIContentPolicy::TYPE_IMAGE:
+    case nsIContentPolicy::TYPE_IMAGESET:
+      return nsIContentSecurityPolicy::IMG_SRC_DIRECTIVE;
+
+    // BLock XSLT as script, see bug 910139
+    case nsIContentPolicy::TYPE_XSLT:
+    case nsIContentPolicy::TYPE_SCRIPT:
+      return nsIContentSecurityPolicy::SCRIPT_SRC_DIRECTIVE;
+
+    case nsIContentPolicy::TYPE_STYLESHEET:
+      return nsIContentSecurityPolicy::STYLE_SRC_DIRECTIVE;
+
+    case nsIContentPolicy::TYPE_FONT:
+      return nsIContentSecurityPolicy::FONT_SRC_DIRECTIVE;
+
+    case nsIContentPolicy::TYPE_MEDIA:
+      return nsIContentSecurityPolicy::MEDIA_SRC_DIRECTIVE;
+
+    // TYPE_DOCUMENT shouldn't be used since it's specifically whitelisted by
+    // the CSPService, but in case we do want to know which directive to check,
+    // FRAME_SRC is the best fit.
+    case nsIContentPolicy::TYPE_DOCUMENT:
+    case nsIContentPolicy::TYPE_SUBDOCUMENT:
+      return nsIContentSecurityPolicy::FRAME_SRC_DIRECTIVE;
+
+    case nsIContentPolicy::TYPE_WEBSOCKET:
+    case nsIContentPolicy::TYPE_XMLHTTPREQUEST:
+    case nsIContentPolicy::TYPE_BEACON:
+    case nsIContentPolicy::TYPE_FETCH:
+      return nsIContentSecurityPolicy::CONNECT_SRC_DIRECTIVE;
+
+    case nsIContentPolicy::TYPE_OBJECT:
+    case nsIContentPolicy::TYPE_OBJECT_SUBREQUEST:
+      return nsIContentSecurityPolicy::OBJECT_SRC_DIRECTIVE;
+
+    case nsIContentPolicy::TYPE_XBL:
+    case nsIContentPolicy::TYPE_PING:
+    case nsIContentPolicy::TYPE_DTD:
+    case nsIContentPolicy::TYPE_OTHER:
+      return nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE;
+
+    // CSP can not block csp reports, fall through to error
+    case nsIContentPolicy::TYPE_CSP_REPORT:
+    // Fall through to error for all other directives
+    default:
+      MOZ_ASSERT(false, "Can not map nsContentPolicyType to CSPDirective");
+  }
+  return nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE;
+}
 
 nsCSPHostSrc*
 CSP_CreateHostSrcFromURI(nsIURI* aURI)
 {
   // Create the host first
   nsCString host;
   aURI->GetHost(host);
   nsCSPHostSrc *hostsrc = new nsCSPHostSrc(NS_ConvertUTF8toUTF16(host));
@@ -150,31 +203,29 @@ CSP_CreateHostSrcFromURI(nsIURI* aURI)
     hostsrc->setPort(portStr);
   }
   return hostsrc;
 }
 
 bool
 CSP_IsValidDirective(const nsAString& aDir)
 {
-  static_assert(CSP_LAST_DIRECTIVE_VALUE ==
-                (sizeof(CSPStrDirectives) / sizeof(CSPStrDirectives[0])),
-                "CSP_LAST_DIRECTIVE_VALUE does not match length of CSPStrDirectives");
+  uint32_t numDirs = (sizeof(CSPStrDirectives) / sizeof(CSPStrDirectives[0]));
 
-  for (uint32_t i = 0; i < CSP_LAST_DIRECTIVE_VALUE; i++) {
+  for (uint32_t i = 0; i < numDirs; i++) {
     if (aDir.LowerCaseEqualsASCII(CSPStrDirectives[i])) {
       return true;
     }
   }
   return false;
 }
 bool
-CSP_IsDirective(const nsAString& aValue, enum CSPDirective aDir)
+CSP_IsDirective(const nsAString& aValue, CSPDirective aDir)
 {
-  return aValue.LowerCaseEqualsASCII(CSP_EnumToDirective(aDir));
+  return aValue.LowerCaseEqualsASCII(CSP_CSPDirectiveToString(aDir));
 }
 
 bool
 CSP_IsKeyword(const nsAString& aValue, enum CSPKeyword aKey)
 {
   return aValue.LowerCaseEqualsASCII(CSP_EnumToKeyword(aKey));
 }
 
@@ -465,17 +516,17 @@ void
 nsCSPHostSrc::appendPath(const nsAString& aPath)
 {
   mPath.Append(aPath);
   ToLowerCase(mPath);
 }
 
 /* ===== nsCSPKeywordSrc ===================== */
 
-nsCSPKeywordSrc::nsCSPKeywordSrc(CSPKeyword aKeyword)
+nsCSPKeywordSrc::nsCSPKeywordSrc(enum CSPKeyword aKeyword)
 {
   NS_ASSERTION((aKeyword != CSP_SELF),
                "'self' should have been replaced in the parser");
   mKeyword = aKeyword;
 }
 
 nsCSPKeywordSrc::~nsCSPKeywordSrc()
 {
@@ -619,17 +670,17 @@ nsCSPReportURI::toString(nsAString& outS
   if (NS_FAILED(rv)) {
     return;
   }
   outStr.AppendASCII(spec.get());
 }
 
 /* ===== nsCSPDirective ====================== */
 
-nsCSPDirective::nsCSPDirective(enum CSPDirective aDirective)
+nsCSPDirective::nsCSPDirective(CSPDirective aDirective)
 {
   mDirective = aDirective;
 }
 
 nsCSPDirective::~nsCSPDirective()
 {
   for (uint32_t i = 0; i < mSrcs.Length(); i++) {
     delete mSrcs[i];
@@ -675,99 +726,43 @@ nsCSPDirective::allows(enum CSPKeyword a
   }
   return false;
 }
 
 void
 nsCSPDirective::toString(nsAString& outStr) const
 {
   // Append directive name
-  outStr.AppendASCII(CSP_EnumToDirective(mDirective));
+  outStr.AppendASCII(CSP_CSPDirectiveToString(mDirective));
   outStr.AppendASCII(" ");
 
   // Append srcs
   uint32_t length = mSrcs.Length();
   for (uint32_t i = 0; i < length; i++) {
     mSrcs[i]->toString(outStr);
     if (i != (length - 1)) {
       outStr.AppendASCII(" ");
     }
   }
 }
 
-enum CSPDirective
-CSP_ContentTypeToDirective(nsContentPolicyType aType)
-{
-  switch (aType) {
-    case nsIContentPolicy::TYPE_IMAGE:
-    case nsIContentPolicy::TYPE_IMAGESET:
-      return CSP_IMG_SRC;
-
-    case nsIContentPolicy::TYPE_SCRIPT:
-      return CSP_SCRIPT_SRC;
-
-    case nsIContentPolicy::TYPE_STYLESHEET:
-      return CSP_STYLE_SRC;
-
-    case nsIContentPolicy::TYPE_FONT:
-      return CSP_FONT_SRC;
-
-    case nsIContentPolicy::TYPE_MEDIA:
-      return CSP_MEDIA_SRC;
-
-    case nsIContentPolicy::TYPE_SUBDOCUMENT:
-      return CSP_FRAME_SRC;
-
-    // BLock XSLT as script, see bug 910139
-    case nsIContentPolicy::TYPE_XSLT:
-      return CSP_SCRIPT_SRC;
-
-    // TODO(sid): fix this mapping to be more precise (bug 999656)
-    case nsIContentPolicy::TYPE_DOCUMENT:
-      return CSP_FRAME_ANCESTORS;
-
-    case nsIContentPolicy::TYPE_WEBSOCKET:
-    case nsIContentPolicy::TYPE_XMLHTTPREQUEST:
-    case nsIContentPolicy::TYPE_BEACON:
-    case nsIContentPolicy::TYPE_FETCH:
-      return CSP_CONNECT_SRC;
-
-    case nsIContentPolicy::TYPE_OBJECT:
-    case nsIContentPolicy::TYPE_OBJECT_SUBREQUEST:
-      return CSP_OBJECT_SRC;
-
-    case nsIContentPolicy::TYPE_XBL:
-    case nsIContentPolicy::TYPE_PING:
-    case nsIContentPolicy::TYPE_DTD:
-    case nsIContentPolicy::TYPE_OTHER:
-      return CSP_DEFAULT_SRC;
-
-    // CSP can not block csp reports, fall through to error
-    case nsIContentPolicy::TYPE_CSP_REPORT:
-    // Fall through to error for all other directives
-    default:
-      NS_ASSERTION(false, "Can not map nsContentPolicyType to CSPDirective");
-  }
-  return CSP_DEFAULT_SRC;
-}
-
 bool
 nsCSPDirective::restrictsContentType(nsContentPolicyType aContentType) const
 {
   // make sure we do not check for the default src before any other sources
   if (isDefaultDirective()) {
     return false;
   }
   return mDirective == CSP_ContentTypeToDirective(aContentType);
 }
 
 void
 nsCSPDirective::getReportURIs(nsTArray<nsString> &outReportURIs) const
 {
-  NS_ASSERTION((mDirective == CSP_REPORT_URI), "not a report-uri directive");
+  NS_ASSERTION((mDirective == nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE), "not a report-uri directive");
 
   // append uris
   nsString tmpReportURI;
   for (uint32_t i = 0; i < mSrcs.Length(); i++) {
     tmpReportURI.Truncate();
     mSrcs[i]->toString(tmpReportURI);
     outReportURIs.AppendElement(tmpReportURI);
   }
@@ -786,118 +781,72 @@ nsCSPPolicy::~nsCSPPolicy()
   CSPUTILSLOG(("nsCSPPolicy::~nsCSPPolicy"));
 
   for (uint32_t i = 0; i < mDirectives.Length(); i++) {
     delete mDirectives[i];
   }
 }
 
 bool
-nsCSPPolicy::permits(nsContentPolicyType aContentType,
+nsCSPPolicy::permits(CSPDirective aDir,
+                     nsIURI* aUri,
+                     bool aSpecific) const
+{
+  nsString outp;
+  return this->permits(aDir, aUri, EmptyString(), false, aSpecific, outp);
+}
+
+bool
+nsCSPPolicy::permits(CSPDirective aDir,
                      nsIURI* aUri,
                      const nsAString& aNonce,
                      bool aWasRedirected,
+                     bool aSpecific,
                      nsAString& outViolatedDirective) const
 {
 #ifdef PR_LOGGING
   {
     nsAutoCString spec;
     aUri->GetSpec(spec);
-    CSPUTILSLOG(("nsCSPPolicy::permits, aContentType: %d, aUri: %s, aNonce: %s",
-                aContentType, spec.get(), NS_ConvertUTF16toUTF8(aNonce).get()));
+    CSPUTILSLOG(("nsCSPPolicy::permits, aUri: %s, aDir: %d, aSpecific: %s",
+                 spec.get(), aDir, aSpecific ? "true" : "false"));
   }
 #endif
 
   NS_ASSERTION(aUri, "permits needs an uri to perform the check!");
 
   nsCSPDirective* defaultDir = nullptr;
 
+  // Try to find a relevant directive
   // These directive arrays are short (1-5 elements), not worth using a hashtable.
-
   for (uint32_t i = 0; i < mDirectives.Length(); i++) {
-    // Check if the directive name matches
-    if (mDirectives[i]->restrictsContentType(aContentType)) {
+    if (mDirectives[i]->equals(aDir)) {
       if (!mDirectives[i]->permits(aUri, aNonce, aWasRedirected)) {
         mDirectives[i]->toString(outViolatedDirective);
         return false;
       }
       return true;
     }
     if (mDirectives[i]->isDefaultDirective()) {
       defaultDir = mDirectives[i];
     }
   }
 
-  // If [frame-ancestors] is not listed explicitly then default to true
-  // without consulting [default-src]
-  // TODO: currently [frame-ancestors] is mapped to TYPE_DOCUMENT (needs to be fixed)
-  if (aContentType == nsIContentPolicy::TYPE_DOCUMENT) {
-    return true;
-  }
-
   // If the above loop runs through, we haven't found a matching directive.
   // Avoid relooping, just store the result of default-src while looping.
-  if (defaultDir) {
+  if (!aSpecific && defaultDir) {
     if (!defaultDir->permits(aUri, aNonce, aWasRedirected)) {
       defaultDir->toString(outViolatedDirective);
       return false;
     }
     return true;
   }
 
-  // unspecified default-src should default to no restrictions
-  // see bug 764937
-  return true;
-}
-
-bool
-nsCSPPolicy::permitsBaseURI(nsIURI* aUri) const
-{
-#ifdef PR_LOGGING
-  {
-    nsAutoCString spec;
-    aUri->GetSpec(spec);
-    CSPUTILSLOG(("nsCSPPolicy::permitsBaseURI, aUri: %s", spec.get()));
-  }
-#endif
-
-  // Try to find a base-uri directive
-  for (uint32_t i = 0; i < mDirectives.Length(); i++) {
-    if (mDirectives[i]->equals(CSP_BASE_URI)) {
-      return mDirectives[i]->permits(aUri);
-    }
-  }
-
-  // base-uri is only enforced if explicitly defined in the
-  // policy - do *not* consult default-src, see:
-  // http://www.w3.org/TR/CSP11/#directive-default-src
-  return true;
-}
-
-bool
-nsCSPPolicy::permitsFormAction(nsIURI* aUri) const
-{
-#ifdef PR_LOGGING
-  {
-    nsAutoCString spec;
-    aUri->GetSpec(spec);
-    CSPUTILSLOG(("nsCSPPolicy::permitsFormAction, aUri: %s", spec.get()));
-  }
-#endif
-
-  // Try to find a form-action directive
-  for (uint32_t i = 0; i < mDirectives.Length(); i++) {
-    if (mDirectives[i]->equals(CSP_FORM_ACTION)) {
-      return mDirectives[i]->permits(aUri);
-    }
-  }
-
-  // form-action is only enforced if explicitly defined in the
-  // policy - do *not* consult default-src, see:
-  // http://www.w3.org/TR/CSP2/#directive-default-src
+  // Nothing restricts this, so we're allowing the load
+  // See bug 764937
   return true;
 }
 
 bool
 nsCSPPolicy::allows(nsContentPolicyType aContentType,
                     enum CSPKeyword aKeyword,
                     const nsAString& aHashOrNonce) const
 {
@@ -953,17 +902,17 @@ nsCSPPolicy::toString(nsAString& outStr)
     mDirectives[i]->toString(outStr);
     if (i != (length - 1)) {
       outStr.AppendASCII("; ");
     }
   }
 }
 
 bool
-nsCSPPolicy::directiveExists(enum CSPDirective aDir) const
+nsCSPPolicy::hasDirective(CSPDirective aDir) const
 {
   for (uint32_t i = 0; i < mDirectives.Length(); i++) {
     if (mDirectives[i]->equals(aDir)) {
       return true;
     }
   }
   return false;
 }
@@ -994,28 +943,28 @@ nsCSPPolicy::getDirectiveStringForConten
     defaultDir->toString(outDirective);
     return;
   }
   NS_ASSERTION(false, "Can not query directive string for contentType!");
   outDirective.AppendASCII("couldNotQueryViolatedDirective");
 }
 
 void
-nsCSPPolicy::getDirectiveAsString(enum CSPDirective aDir, nsAString& outDirective) const
+nsCSPPolicy::getDirectiveAsString(CSPDirective aDir, nsAString& outDirective) const
 {
   for (uint32_t i = 0; i < mDirectives.Length(); i++) {
     if (mDirectives[i]->equals(aDir)) {
       mDirectives[i]->toString(outDirective);
       return;
     }
   }
 }
 
 void
 nsCSPPolicy::getReportURIs(nsTArray<nsString>& outReportURIs) const
 {
   for (uint32_t i = 0; i < mDirectives.Length(); i++) {
-    if (mDirectives[i]->equals(CSP_REPORT_URI)) {
+    if (mDirectives[i]->equals(nsIContentSecurityPolicy::REPORT_URI_DIRECTIVE)) {
       mDirectives[i]->getReportURIs(outReportURIs);
       return;
     }
   }
 }
--- a/dom/security/nsCSPUtils.h
+++ b/dom/security/nsCSPUtils.h
@@ -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/. */
 
 #ifndef nsCSPUtils_h___
 #define nsCSPUtils_h___
 
 #include "nsCOMPtr.h"
 #include "nsIContentPolicy.h"
+#include "nsIContentSecurityPolicy.h"
 #include "nsIURI.h"
 #include "nsString.h"
 #include "nsTArray.h"
 #include "nsUnicharUtils.h"
 #include "prlog.h"
 
 /* =============== Logging =================== */
 
@@ -49,82 +50,55 @@ void CSP_LogMessage(const nsAString& aMe
 #define INLINE_STYLE_VIOLATION_OBSERVER_TOPIC   "violated base restriction: Inline Stylesheets will not apply"
 #define INLINE_SCRIPT_VIOLATION_OBSERVER_TOPIC  "violated base restriction: Inline Scripts will not execute"
 #define EVAL_VIOLATION_OBSERVER_TOPIC           "violated base restriction: Code will not be created from strings"
 #define SCRIPT_NONCE_VIOLATION_OBSERVER_TOPIC   "Inline Script had invalid nonce"
 #define STYLE_NONCE_VIOLATION_OBSERVER_TOPIC    "Inline Style had invalid nonce"
 #define SCRIPT_HASH_VIOLATION_OBSERVER_TOPIC    "Inline Script had invalid hash"
 #define STYLE_HASH_VIOLATION_OBSERVER_TOPIC     "Inline Style had invalid hash"
 
-
-// Please add any new enum items not only to CSPDirective, but also add
-// a string version for every enum >> using the same index << to
-// CSPStrDirectives underneath.
-enum CSPDirective {
-  CSP_DEFAULT_SRC = 0,
-  CSP_SCRIPT_SRC,
-  CSP_OBJECT_SRC,
-  CSP_STYLE_SRC,
-  CSP_IMG_SRC,
-  CSP_MEDIA_SRC,
-  CSP_FRAME_SRC,
-  CSP_FONT_SRC,
-  CSP_CONNECT_SRC,
-  CSP_REPORT_URI,
-  CSP_FRAME_ANCESTORS,
-  CSP_REFLECTED_XSS,
-  CSP_BASE_URI,
-  CSP_FORM_ACTION,
-  // CSP_LAST_DIRECTIVE_VALUE always needs to be the last element in the enum
-  // because we use it to calculate the size for the char* array.
-  CSP_LAST_DIRECTIVE_VALUE
+// these strings map to the CSPDirectives in nsIContentSecurityPolicy
+// NOTE: When implementing a new directive, you will need to add it here but also
+// add a corresponding entry to the constants in nsIContentSecurityPolicy.idl
+static const char* CSPStrDirectives[] = {
+  "-error-",    // NO_DIRECTIVE
+  "default-src",     // DEFAULT_SRC_DIRECTIVE
+  "script-src",      // SCRIPT_SRC_DIRECTIVE
+  "object-src",      // OBJECT_SRC_DIRECTIVE
+  "style-src",       // STYLE_SRC_DIRECTIVE
+  "img-src",         // IMG_SRC_DIRECTIVE
+  "media-src",       // MEDIA_SRC_DIRECTIVE
+  "frame-src",       // FRAME_SRC_DIRECTIVE
+  "font-src",        // FONT_SRC_DIRECTIVE
+  "connect-src",     // CONNECT_SRC_DIRECTIVE
+  "report-uri",      // REPORT_URI_DIRECTIVE
+  "frame-ancestors", // FRAME_ANCESTORS_DIRECTIVE
+  "reflected-xss",   // REFLECTED_XSS_DIRECTIVE
+  "base-uri",        // BASE_URI_DIRECTIVE
+  "form-action"      // FORM_ACTION_DIRECTIVE
 };
 
-static const char* CSPStrDirectives[] = {
-  "default-src",     // CSP_DEFAULT_SRC = 0
-  "script-src",      // CSP_SCRIPT_SRC
-  "object-src",      // CSP_OBJECT_SRC
-  "style-src",       // CSP_STYLE_SRC
-  "img-src",         // CSP_IMG_SRC
-  "media-src",       // CSP_MEDIA_SRC
-  "frame-src",       // CSP_FRAME_SRC
-  "font-src",        // CSP_FONT_SRC
-  "connect-src",     // CSP_CONNECT_SRC
-  "report-uri",      // CSP_REPORT_URI
-  "frame-ancestors", // CSP_FRAME_ANCESTORS
-  "reflected-xss",   // CSP_REFLECTED_XSS
-  "base-uri",        // CSP_BASE_URI
-  "form-action"      // CSP_FORM_ACTION
-};
-
-inline const char* CSP_EnumToDirective(enum CSPDirective aDir)
+inline const char* CSP_CSPDirectiveToString(CSPDirective aDir)
 {
-  // Make sure all elements in enum CSPDirective got added to CSPStrDirectives.
-  static_assert((sizeof(CSPStrDirectives) / sizeof(CSPStrDirectives[0]) ==
-                static_cast<uint32_t>(CSP_LAST_DIRECTIVE_VALUE)),
-                "CSP_LAST_DIRECTIVE_VALUE does not match length of CSPStrDirectives");
   return CSPStrDirectives[static_cast<uint32_t>(aDir)];
 }
 
-inline CSPDirective CSP_DirectiveToEnum(const nsAString& aDir)
+inline CSPDirective CSP_StringToCSPDirective(const nsAString& aDir)
 {
   nsString lowerDir = PromiseFlatString(aDir);
   ToLowerCase(lowerDir);
 
-  static_assert(CSP_LAST_DIRECTIVE_VALUE ==
-                (sizeof(CSPStrDirectives) / sizeof(CSPStrDirectives[0])),
-                "CSP_LAST_DIRECTIVE_VALUE does not match length of CSPStrDirectives");
-
-  for (uint32_t i = 0; i < CSP_LAST_DIRECTIVE_VALUE; i++) {
+  uint32_t numDirs = (sizeof(CSPStrDirectives) / sizeof(CSPStrDirectives[0]));
+  for (uint32_t i = 1; i < numDirs; i++) {
     if (lowerDir.EqualsASCII(CSPStrDirectives[i])) {
       return static_cast<CSPDirective>(i);
     }
   }
-  NS_ASSERTION(false, "Can not convert unknown Directive to Enum");
-  return CSP_LAST_DIRECTIVE_VALUE;
+  NS_ASSERTION(false, "Can not convert unknown Directive to Integer");
+  return nsIContentSecurityPolicy::NO_DIRECTIVE;
 }
 
 // Please add any new enum items not only to CSPKeyword, but also add
 // a string version for every enum >> using the same index << to
 // CSPStrKeywords underneath.
 enum CSPKeyword {
   CSP_SELF = 0,
   CSP_UNSAFE_INLINE,
@@ -177,19 +151,21 @@ inline CSPKeyword CSP_KeywordToEnum(cons
 }
 
 /* =============== Helpers ================== */
 
 class nsCSPHostSrc;
 
 nsCSPHostSrc* CSP_CreateHostSrcFromURI(nsIURI* aURI);
 bool CSP_IsValidDirective(const nsAString& aDir);
-bool CSP_IsDirective(const nsAString& aValue, enum CSPDirective aDir);
+bool CSP_IsDirective(const nsAString& aValue, CSPDirective aDir);
 bool CSP_IsKeyword(const nsAString& aValue, enum CSPKeyword aKey);
 bool CSP_IsQuotelessKeyword(const nsAString& aKey);
+CSPDirective CSP_ContentTypeToDirective(nsContentPolicyType aType);
+
 
 /* =============== nsCSPSrc ================== */
 
 class nsCSPBaseSrc {
   public:
     nsCSPBaseSrc();
     virtual ~nsCSPBaseSrc();
 
@@ -291,80 +267,82 @@ class nsCSPReportURI : public nsCSPBaseS
     nsCOMPtr<nsIURI> mReportURI;
 };
 
 /* =============== nsCSPDirective ============= */
 
 class nsCSPDirective {
   public:
     nsCSPDirective();
-    explicit nsCSPDirective(enum CSPDirective aDirective);
+    explicit nsCSPDirective(CSPDirective aDirective);
     virtual ~nsCSPDirective();
 
     bool permits(nsIURI* aUri, const nsAString& aNonce, bool aWasRedirected) const;
     bool permits(nsIURI* aUri) const;
     bool allows(enum CSPKeyword aKeyword, const nsAString& aHashOrNonce) const;
     void toString(nsAString& outStr) const;
 
     inline void addSrcs(const nsTArray<nsCSPBaseSrc*>& aSrcs)
       { mSrcs = aSrcs; }
 
     bool restrictsContentType(nsContentPolicyType aContentType) const;
 
     inline bool isDefaultDirective() const
-     { return mDirective == CSP_DEFAULT_SRC; }
+     { return mDirective == nsIContentSecurityPolicy::DEFAULT_SRC_DIRECTIVE; }
 
-    inline bool equals(enum CSPDirective aDirective) const
+    inline bool equals(CSPDirective aDirective) const
       { return (mDirective == aDirective); }
 
     void getReportURIs(nsTArray<nsString> &outReportURIs) const;
 
   private:
     CSPDirective            mDirective;
     nsTArray<nsCSPBaseSrc*> mSrcs;
 };
 
 /* =============== nsCSPPolicy ================== */
 
 class nsCSPPolicy {
   public:
     nsCSPPolicy();
     virtual ~nsCSPPolicy();
 
-    bool permits(nsContentPolicyType aContentType,
+    bool permits(CSPDirective aDirective,
                  nsIURI* aUri,
                  const nsAString& aNonce,
                  bool aWasRedirected,
+                 bool aSpecific,
                  nsAString& outViolatedDirective) const;
-    bool permitsBaseURI(nsIURI* aUri) const;
-    bool permitsFormAction(nsIURI* aUri) const;
+    bool permits(CSPDirective aDir,
+                 nsIURI* aUri,
+                 bool aSpecific) const;
     bool allows(nsContentPolicyType aContentType,
                 enum CSPKeyword aKeyword,
                 const nsAString& aHashOrNonce) const;
     bool allows(nsContentPolicyType aContentType,
                 enum CSPKeyword aKeyword) const;
     void toString(nsAString& outStr) const;
 
     inline void addDirective(nsCSPDirective* aDir)
       { mDirectives.AppendElement(aDir); }
 
-    bool directiveExists(enum CSPDirective aDir) const;
+    bool hasDirective(CSPDirective aDir) const;
 
     inline void setReportOnlyFlag(bool aFlag)
       { mReportOnly = aFlag; }
 
     inline bool getReportOnlyFlag() const
       { return mReportOnly; }
 
     void getReportURIs(nsTArray<nsString> &outReportURIs) const;
 
     void getDirectiveStringForContentType(nsContentPolicyType aContentType,
                                           nsAString& outDirective) const;
 
-    void getDirectiveAsString(enum CSPDirective aDir, nsAString& outDirective) const;
+    void getDirectiveAsString(CSPDirective aDir, nsAString& outDirective) const;
 
     inline uint32_t getNumDirectives() const
       { return mDirectives.Length(); }
 
   private:
     nsTArray<nsCSPDirective*> mDirectives;
     bool                      mReportOnly;
 };
--- a/dom/vr/VRDevice.h
+++ b/dom/vr/VRDevice.h
@@ -217,17 +217,17 @@ class PositionSensorVRDevice : public VR
 public:
   virtual already_AddRefed<VRPositionState> GetState(double timeOffset) = 0;
 
   virtual void ZeroSensor() = 0;
 
   virtual JSObject* WrapObject(JSContext* aCx) MOZ_OVERRIDE;
 
 protected:
-  PositionSensorVRDevice(nsISupports* aParent)
+  explicit PositionSensorVRDevice(nsISupports* aParent)
     : VRDevice(aParent, VRDevice::PositionSensor)
   { }
 
   virtual ~PositionSensorVRDevice() { }
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/webidl/IDBObjectStore.webidl
+++ b/dom/webidl/IDBObjectStore.webidl
@@ -3,18 +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/.
  *
  * The origin of this IDL file is
  * https://dvcs.w3.org/hg/IndexedDB/raw-file/tip/Overview.html#idl-def-IDBObjectStore
  */
 
 dictionary IDBObjectStoreParameters {
-    // TODO (DOMString or sequence<DOMString>)? keyPath = null;
-    any                                         keyPath = null;
+    (DOMString or sequence<DOMString>)? keyPath = null;
     boolean                             autoIncrement = false;
 };
 
 interface IDBObjectStore {
     readonly    attribute DOMString      name;
 
     [Throws]
     readonly    attribute any            keyPath;
--- a/dom/workers/RuntimeService.cpp
+++ b/dom/workers/RuntimeService.cpp
@@ -21,16 +21,17 @@
 #include "nsPIDOMWindow.h"
 
 #include <algorithm>
 #include "BackgroundChild.h"
 #include "GeckoProfiler.h"
 #include "jsfriendapi.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/CycleCollectedJSRuntime.h"
+#include "mozilla/TimeStamp.h"
 #include "mozilla/dom/asmjscache/AsmJSCache.h"
 #include "mozilla/dom/AtomList.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/ErrorEventBinding.h"
 #include "mozilla/dom/EventTargetBinding.h"
 #include "mozilla/dom/MessageEventBinding.h"
 #include "mozilla/dom/WorkerBinding.h"
 #include "mozilla/dom/ScriptSettings.h"
@@ -40,36 +41,32 @@
 #include "nsContentUtils.h"
 #include "nsCycleCollector.h"
 #include "nsDOMJSUtils.h"
 #include "nsIIPCBackgroundChildCreateCallback.h"
 #include "nsISupportsImpl.h"
 #include "nsLayoutStatics.h"
 #include "nsNetUtil.h"
 #include "nsServiceManagerUtils.h"
-#include "nsThread.h"
 #include "nsThreadUtils.h"
 #include "nsXPCOM.h"
 #include "nsXPCOMPrivate.h"
 #include "OSFileConstants.h"
 #include "xpcpublic.h"
 
 #ifdef MOZ_NUWA_PROCESS
 #include "ipc/Nuwa.h"
 #endif
 
-#ifdef DEBUG
-#include "nsThreadManager.h"
-#endif
-
 #include "Principal.h"
 #include "ServiceWorker.h"
 #include "SharedWorker.h"
 #include "WorkerPrivate.h"
 #include "WorkerRunnable.h"
+#include "WorkerThread.h"
 
 #ifdef ENABLE_TESTS
 #include "BackgroundChildImpl.h"
 #include "mozilla/ipc/PBackgroundChild.h"
 #include "prrng.h"
 #endif
 
 using namespace mozilla;
@@ -85,20 +82,16 @@ using mozilla::Preferences;
 #define WORKER_DEFAULT_RUNTIME_HEAPSIZE 32 * 1024 * 1024
 
 // The size of the generational GC nursery for workers, in bytes.
 #define WORKER_DEFAULT_NURSERY_SIZE 1 * 1024 * 1024
 
 // The size of the worker JS allocation threshold in MB. May be changed via pref.
 #define WORKER_DEFAULT_ALLOCATION_THRESHOLD 30
 
-// The C stack size. We use the same stack size on all platforms for
-// consistency.
-#define WORKER_STACK_SIZE 256 * sizeof(size_t) * 1024
-
 // Half the size of the actual C stack, to be safe.
 #define WORKER_CONTEXT_NATIVE_STACK_LIMIT 128 * sizeof(size_t) * 1024
 
 // The maximum number of threads to use for workers, overridable via pref.
 #define MAX_WORKERS_PER_DOMAIN 10
 
 static_assert(MAX_WORKERS_PER_DOMAIN >= 1,
               "We should allow at least one worker per domain.");
@@ -902,16 +895,68 @@ public:
       nsCycleCollector_collect(nullptr);
     }
   }
 
 private:
   WorkerPrivate* mWorkerPrivate;
 };
 
+#ifdef ENABLE_TESTS
+
+class TestPBackgroundCreateCallback MOZ_FINAL :
+  public nsIIPCBackgroundChildCreateCallback
+{
+public:
+  NS_DECL_ISUPPORTS
+
+  virtual void
+  ActorCreated(PBackgroundChild* aActor) MOZ_OVERRIDE
+  {
+    MOZ_RELEASE_ASSERT(aActor);
+  }
+
+  virtual void
+  ActorFailed() MOZ_OVERRIDE
+  {
+    MOZ_CRASH("TestPBackground() should not fail "
+              "GetOrCreateForCurrentThread()");
+  }
+
+private:
+  ~TestPBackgroundCreateCallback()
+  { }
+};
+
+NS_IMPL_ISUPPORTS(TestPBackgroundCreateCallback,
+                  nsIIPCBackgroundChildCreateCallback);
+
+void
+TestPBackground()
+{
+  using namespace mozilla::ipc;
+
+  if (gTestPBackground) {
+    // Randomize value to validate workers are not cross-posting messages.
+    uint32_t testValue;
+    size_t randomSize = PR_GetRandomNoise(&testValue, sizeof(testValue));
+    MOZ_RELEASE_ASSERT(randomSize == sizeof(testValue));
+    nsCString testStr;
+    testStr.AppendInt(testValue);
+    testStr.AppendInt(reinterpret_cast<int64_t>(PR_GetCurrentThread()));
+    PBackgroundChild* existingBackgroundChild =
+      BackgroundChild::GetForCurrentThread();
+    MOZ_RELEASE_ASSERT(existingBackgroundChild);
+    bool ok = existingBackgroundChild->SendPBackgroundTestConstructor(testStr);
+    MOZ_RELEASE_ASSERT(ok);
+  }
+}
+
+#endif // ENABLE_TESTS
+
 class WorkerBackgroundChildCallback MOZ_FINAL :
   public nsIIPCBackgroundChildCreateCallback
 {
   bool* mDone;
 
 public:
   explicit WorkerBackgroundChildCallback(bool* aDone)
   : mDone(aDone)
@@ -937,42 +982,42 @@ private:
   {
     *mDone = true;
   }
 };
 
 class WorkerThreadPrimaryRunnable MOZ_FINAL : public nsRunnable
 {
   WorkerPrivate* mWorkerPrivate;
-  nsRefPtr<RuntimeService::WorkerThread> mThread;
+  nsRefPtr<WorkerThread> mThread;
   JSRuntime* mParentRuntime;
 
   class FinishedRunnable MOZ_FINAL : public nsRunnable
   {
-    nsRefPtr<RuntimeService::WorkerThread> mThread;
+    nsRefPtr<WorkerThread> mThread;
 
   public:
-    explicit FinishedRunnable(already_AddRefed<RuntimeService::WorkerThread> aThread)
+    explicit FinishedRunnable(already_AddRefed<WorkerThread> aThread)
     : mThread(aThread)
     {
       MOZ_ASSERT(mThread);
     }
 
     NS_DECL_ISUPPORTS_INHERITED
 
   private:
     ~FinishedRunnable()
     { }
 
     NS_DECL_NSIRUNNABLE
   };
 
 public:
   WorkerThreadPrimaryRunnable(WorkerPrivate* aWorkerPrivate,
-                              RuntimeService::WorkerThread* aThread,
+                              WorkerThread* aThread,
                               JSRuntime* aParentRuntime)
   : mWorkerPrivate(aWorkerPrivate), mThread(aThread), mParentRuntime(aParentRuntime)
   {
     MOZ_ASSERT(aWorkerPrivate);
     MOZ_ASSERT(aThread);
   }
 
   NS_DECL_ISUPPORTS_INHERITED
@@ -1073,139 +1118,16 @@ PlatformOverrideChanged(const char* /* a
   RuntimeService* runtime = RuntimeService::GetService();
   if (runtime) {
     runtime->UpdatePlatformOverridePreference(override);
   }
 }
 
 } /* anonymous namespace */
 
-class RuntimeService::WorkerThread MOZ_FINAL : public nsThread
-{
-  class Observer MOZ_FINAL : public nsIThreadObserver
-  {
-    WorkerPrivate* mWorkerPrivate;
-
-  public:
-    explicit Observer(WorkerPrivate* aWorkerPrivate)
-    : mWorkerPrivate(aWorkerPrivate)
-    {
-      MOZ_ASSERT(aWorkerPrivate);
-      aWorkerPrivate->AssertIsOnWorkerThread();
-    }
-
-    NS_DECL_THREADSAFE_ISUPPORTS
-
-  private:
-    ~Observer()
-    {
-      mWorkerPrivate->AssertIsOnWorkerThread();
-    }
-
-    NS_DECL_NSITHREADOBSERVER
-  };
-
-  WorkerPrivate* mWorkerPrivate;
-  nsRefPtr<Observer> mObserver;
-
-#ifdef DEBUG
-  // Protected by nsThread::mLock.
-  bool mAcceptingNonWorkerRunnables;
-#endif
-
-public:
-  static already_AddRefed<WorkerThread>
-  Create();
-
-  void
-  SetWorker(WorkerPrivate* aWorkerPrivate);
-
-  NS_DECL_ISUPPORTS_INHERITED
-
-  NS_IMETHOD
-  Dispatch(nsIRunnable* aRunnable, uint32_t aFlags) MOZ_OVERRIDE;
-
-#ifdef DEBUG
-  bool
-  IsAcceptingNonWorkerRunnables()
-  {
-    MutexAutoLock lock(mLock);
-    return mAcceptingNonWorkerRunnables;
-  }
-
-  void
-  SetAcceptingNonWorkerRunnables(bool aAcceptingNonWorkerRunnables)
-  {
-    MutexAutoLock lock(mLock);
-    mAcceptingNonWorkerRunnables = aAcceptingNonWorkerRunnables;
-  }
-#endif
-
-#ifdef ENABLE_TESTS
-  class TestPBackgroundCreateCallback MOZ_FINAL :
-    public nsIIPCBackgroundChildCreateCallback
-  {
-  public:
-    virtual void ActorCreated(PBackgroundChild* actor) MOZ_OVERRIDE
-    {
-      MOZ_RELEASE_ASSERT(actor);
-    }
-
-    virtual void ActorFailed() MOZ_OVERRIDE
-    {
-      MOZ_CRASH("TestPBackground() should not fail GetOrCreateForCurrentThread()");
-    }
-
-  private:
-    ~TestPBackgroundCreateCallback()
-    { }
-
-  public:
-    NS_DECL_ISUPPORTS;
-  };
-
-  void
-  TestPBackground()
-  {
-    using namespace mozilla::ipc;
-    if (gTestPBackground) {
-      // Randomize value to validate workers are not cross-posting messages.
-      uint32_t testValue;
-      size_t randomSize = PR_GetRandomNoise(&testValue, sizeof(testValue));
-      MOZ_RELEASE_ASSERT(randomSize == sizeof(testValue));
-      nsCString testStr;
-      testStr.AppendInt(testValue);
-      testStr.AppendInt(reinterpret_cast<int64_t>(PR_GetCurrentThread()));
-      PBackgroundChild* existingBackgroundChild =
-        BackgroundChild::GetForCurrentThread();
-      MOZ_RELEASE_ASSERT(existingBackgroundChild);
-      bool ok = existingBackgroundChild->SendPBackgroundTestConstructor(testStr);
-      MOZ_RELEASE_ASSERT(ok);
-    }
-  }
-#endif // #ENABLE_TESTS
-
-private:
-  WorkerThread()
-  : nsThread(nsThread::NOT_MAIN_THREAD, WORKER_STACK_SIZE),
-    mWorkerPrivate(nullptr)
-#ifdef DEBUG
-    , mAcceptingNonWorkerRunnables(true)
-#endif
-  { }
-
-  ~WorkerThread()
-  { }
-};
-
-#ifdef ENABLE_TESTS
-NS_IMPL_ISUPPORTS(RuntimeService::WorkerThread::TestPBackgroundCreateCallback,
-                  nsIIPCBackgroundChildCreateCallback);
-#endif
-
 BEGIN_WORKERS_NAMESPACE
 
 void
 CancelWorkersForWindow(nsPIDOMWindow* aWindow)
 {
   AssertIsOnMainThread();
   RuntimeService* runtime = RuntimeService::GetService();
   if (runtime) {
@@ -1302,16 +1224,22 @@ IsCurrentThreadRunningChromeWorker()
 JSContext*
 GetCurrentThreadJSContext()
 {
   return GetCurrentThreadWorkerPrivate()->GetJSContext();
 }
 
 END_WORKERS_NAMESPACE
 
+struct RuntimeService::IdleThreadInfo
+{
+  nsRefPtr<WorkerThread> mThread;
+  mozilla::TimeStamp mExpirationTime;
+};
+
 // This is only touched on the main thread. Initialized in Init() below.
 JSSettings RuntimeService::sDefaultJSSettings;
 bool RuntimeService::sDefaultPreferences[WORKERPREF_COUNT] = { false };
 
 RuntimeService::RuntimeService()
 : mMutex("RuntimeService::mMutex"), mObserved(false),
   mShuttingDown(false), mNavigatorPropertiesLoaded(false)
 {
@@ -1609,38 +1537,38 @@ RuntimeService::ScheduleWorker(JSContext
     MutexAutoLock lock(mMutex);
     if (!mIdleThreadArray.IsEmpty()) {
       uint32_t index = mIdleThreadArray.Length() - 1;
       mIdleThreadArray[index].mThread.swap(thread);
       mIdleThreadArray.RemoveElementAt(index);
     }
   }
 
+  const WorkerThreadFriendKey friendKey;
+
   if (!thread) {
-    thread = WorkerThread::Create();
+    thread = WorkerThread::Create(friendKey);
     if (!thread) {
       UnregisterWorker(aCx, aWorkerPrivate);
       JS_ReportError(aCx, "Could not create new thread!");
       return false;
     }
   }
 
-  MOZ_ASSERT(thread->IsAcceptingNonWorkerRunnables());
-
   int32_t priority = aWorkerPrivate->IsChromeWorker() ?
                      nsISupportsPriority::PRIORITY_NORMAL :
                      nsISupportsPriority::PRIORITY_LOW;
 
   if (NS_FAILED(thread->SetPriority(priority))) {
     NS_WARNING("Could not set the thread's priority!");
   }
 
   nsCOMPtr<nsIRunnable> runnable =
     new WorkerThreadPrimaryRunnable(aWorkerPrivate, thread, JS_GetParentRuntime(aCx));
-  if (NS_FAILED(thread->Dispatch(runnable, NS_DISPATCH_NORMAL))) {
+  if (NS_FAILED(thread->DispatchPrimaryRunnable(friendKey, runnable))) {
     UnregisterWorker(aCx, aWorkerPrivate);
     JS_ReportError(aCx, "Could not dispatch to thread!");
     return false;
   }
 
   return true;
 }
 
@@ -2382,20 +2310,16 @@ RuntimeService::ForgetSharedWorker(Worke
 }
 
 void
 RuntimeService::NoteIdleThread(WorkerThread* aThread)
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(aThread);
 
-#ifdef DEBUG
-  aThread->SetAcceptingNonWorkerRunnables(true);
-#endif
-
   static TimeDuration timeout =
     TimeDuration::FromSeconds(IDLE_THREAD_TIMEOUT_SEC);
 
   TimeStamp expirationTime = TimeStamp::Now() + timeout;
 
   bool shutdown;
   if (mShuttingDown) {
     shutdown = true;
@@ -2587,155 +2511,16 @@ RuntimeService::JSVersionChanged(const c
 {
   AssertIsOnMainThread();
 
   bool useLatest = Preferences::GetBool(PREF_WORKERS_LATEST_JS_VERSION, false);
   JS::CompartmentOptions& options = sDefaultJSSettings.content.compartmentOptions;
   options.setVersion(useLatest ? JSVERSION_LATEST : JSVERSION_DEFAULT);
 }
 
-// static
-already_AddRefed<RuntimeService::WorkerThread>
-RuntimeService::WorkerThread::Create()
-{
-  MOZ_ASSERT(nsThreadManager::get());
-
-  nsRefPtr<WorkerThread> thread = new WorkerThread();
-  if (NS_FAILED(thread->Init())) {
-    NS_WARNING("Failed to create new thread!");
-    return nullptr;
-  }
-
-  NS_SetThreadName(thread, "DOM Worker");
-
-  return thread.forget();
-}
-
-void
-RuntimeService::WorkerThread::SetWorker(WorkerPrivate* aWorkerPrivate)
-{
-  MOZ_ASSERT(PR_GetCurrentThread() == mThread);
-  MOZ_ASSERT_IF(aWorkerPrivate, !mWorkerPrivate);
-  MOZ_ASSERT_IF(!aWorkerPrivate, mWorkerPrivate);
-
-  // No need to lock here because mWorkerPrivate is only modified on mThread.
-
-  if (mWorkerPrivate) {
-    MOZ_ASSERT(mObserver);
-
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(RemoveObserver(mObserver)));
-
-    mObserver = nullptr;
-    mWorkerPrivate->SetThread(nullptr);
-  }
-
-  mWorkerPrivate = aWorkerPrivate;
-
-  if (mWorkerPrivate) {
-    mWorkerPrivate->SetThread(this);
-
-    nsRefPtr<Observer> observer = new Observer(mWorkerPrivate);
-
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(AddObserver(observer)));
-
-    mObserver.swap(observer);
-  }
-}
-
-NS_IMPL_ISUPPORTS_INHERITED0(RuntimeService::WorkerThread, nsThread)
-
-NS_IMETHODIMP
-RuntimeService::WorkerThread::Dispatch(nsIRunnable* aRunnable, uint32_t aFlags)
-{
-  // May be called on any thread!
-
-#ifdef DEBUG
-  if (PR_GetCurrentThread() == mThread) {
-    MOZ_ASSERT(mWorkerPrivate);
-    mWorkerPrivate->AssertIsOnWorkerThread();
-  }
-  else if (aRunnable && !IsAcceptingNonWorkerRunnables()) {
-    // Only enforce cancelable runnables after we've started the worker loop.
-    nsCOMPtr<nsICancelableRunnable> cancelable = do_QueryInterface(aRunnable);
-    MOZ_ASSERT(cancelable,
-               "Should have been wrapped by the worker's event target!");
-  }
-#endif
-
-  // Workers only support asynchronous dispatch for now.
-  if (NS_WARN_IF(aFlags != NS_DISPATCH_NORMAL)) {
-    return NS_ERROR_UNEXPECTED;