Merge inbound to mozilla-central. a=merge on a CLOSED TREE
authorCiure Andrei <aciure@mozilla.com>
Tue, 20 Feb 2018 12:15:57 +0200
changeset 404453 2c000486eac466da6623e4d7f7f1fd4e318f60e8
parent 404452 ab4200b426149ca70c7e579039a556cf5ee46e7d (current diff)
parent 404427 a35fe8a4a82de45e10627b6fc760dd13d5df64a8 (diff)
child 404454 56a805f8ce42532d6c735e8818d14823f8e9fa0a
child 404483 47ac350879d518a00e64841bde3d7d890bd67983
push id100013
push useraciure@mozilla.com
push dateTue, 20 Feb 2018 10:21:00 +0000
treeherdermozilla-inbound@56a805f8ce42 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone60.0a1
first release with
nightly linux32
2c000486eac4 / 60.0a1 / 20180220103456 / files
nightly linux64
2c000486eac4 / 60.0a1 / 20180220103456 / files
nightly mac
2c000486eac4 / 60.0a1 / 20180220103456 / files
nightly win32
2c000486eac4 / 60.0a1 / 20180220103456 / files
nightly win64
2c000486eac4 / 60.0a1 / 20180220103456 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to mozilla-central. a=merge on a CLOSED TREE
--- a/browser/components/extensions/test/browser/browser-common.ini
+++ b/browser/components/extensions/test/browser/browser-common.ini
@@ -1,9 +1,11 @@
 [DEFAULT]
+prefs =
+    dom.animations-api.core.enabled=true
 support-files =
   head.js
   head_pageAction.js
   head_sessions.js
   head_webNavigation.js
   profilerSymbols.sjs
   context.html
   context_frame.html
@@ -205,8 +207,9 @@ tags = fullscreen
 [browser_ext_windows_create_params.js]
 [browser_ext_windows_create_tabId.js]
 [browser_ext_windows_create_url.js]
 [browser_ext_windows_events.js]
 [browser_ext_windows_size.js]
 skip-if = os == 'mac' # Fails when windows are randomly opened in fullscreen mode
 [browser_ext_windows_update.js]
 tags = fullscreen
+[browser_ext_contentscript_animate.js]
new file mode 100644
--- /dev/null
+++ b/browser/components/extensions/test/browser/browser_ext_contentscript_animate.js
@@ -0,0 +1,95 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+add_task(async function test_animate() {
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "content_scripts": [
+        {
+          "matches": ["http://mochi.test/*"],
+          "js": ["content-script.js"],
+        },
+      ],
+    },
+
+    files: {
+      "content-script.js": function() {
+        let elem = document.getElementsByTagName("body")[0];
+        elem.style.border = "2px solid red";
+
+        let anim = elem.animate({opacity: [1, 0]}, 2000);
+        let frames = anim.effect.getKeyframes();
+        browser.test.assertEq(frames.length, 2,
+                              "frames for Element.animate should be non-zero");
+        browser.test.assertEq(frames[0].opacity, "1",
+                              "first frame opacity for Element.animate should be specified value");
+        browser.test.assertEq(frames[0].computedOffset, 0,
+                              "first frame offset for Element.animate should be 0");
+        browser.test.assertEq(frames[1].opacity, "0",
+                              "last frame opacity for Element.animate should be specified value");
+        browser.test.assertEq(frames[1].computedOffset, 1,
+                              "last frame offset for Element.animate should be 1");
+
+        browser.test.notifyPass("contentScriptAnimate");
+      },
+    },
+  });
+
+  await extension.startup();
+  await extension.awaitFinish("contentScriptAnimate");
+  await extension.unload();
+
+  await BrowserTestUtils.removeTab(tab);
+});
+
+add_task(async function test_KeyframeEffect() {
+  let tab = await BrowserTestUtils.openNewForegroundTab(gBrowser, "http://mochi.test:8888/");
+
+  let extension = ExtensionTestUtils.loadExtension({
+    manifest: {
+      "content_scripts": [
+        {
+          "matches": ["http://mochi.test/*"],
+          "js": ["content-script.js"],
+        },
+      ],
+    },
+
+    files: {
+      "content-script.js": function() {
+        let elem = document.getElementsByTagName("body")[0];
+        elem.style.border = "2px solid red";
+
+        let effect = new KeyframeEffect(elem, [
+          {opacity: 1, offset: 0},
+          {opacity: 0, offset: 1},
+        ], {duration: 1000, fill: "forwards"});
+        let frames = effect.getKeyframes();
+        browser.test.assertEq(frames.length, 2,
+                              "frames for KeyframeEffect ctor should be non-zero");
+        browser.test.assertEq(frames[0].opacity, "1",
+                              "first frame opacity for KeyframeEffect ctor should be specified value");
+        browser.test.assertEq(frames[0].computedOffset, 0,
+                              "first frame offset for KeyframeEffect ctor should be 0");
+        browser.test.assertEq(frames[1].opacity, "0",
+                              "last frame opacity for KeyframeEffect ctor should be specified value");
+        browser.test.assertEq(frames[1].computedOffset, 1,
+                              "last frame offset for KeyframeEffect ctor should be 1");
+
+        let animation = new Animation(effect, document.timeline);
+        animation.play();
+
+        browser.test.notifyPass("contentScriptKeyframeEffect");
+      },
+    },
+  });
+
+  await extension.startup();
+  await extension.awaitFinish("contentScriptKeyframeEffect");
+  await extension.unload();
+
+  await BrowserTestUtils.removeTab(tab);
+});
--- a/dom/animation/AnimationUtils.cpp
+++ b/dom/animation/AnimationUtils.cpp
@@ -44,16 +44,26 @@ AnimationUtils::GetCurrentRealmDocument(
 {
   nsGlobalWindowInner* win = xpc::CurrentWindowOrNull(aCx);
   if (!win) {
     return nullptr;
   }
   return win->GetDoc();
 }
 
+/* static */ nsIDocument*
+AnimationUtils::GetDocumentFromGlobal(JSObject* aGlobalObject)
+{
+  nsGlobalWindowInner* win = xpc::WindowOrNull(aGlobalObject);
+  if (!win) {
+    return nullptr;
+  }
+  return win->GetDoc();
+}
+
 /* static */ bool
 AnimationUtils::IsOffscreenThrottlingEnabled()
 {
   static bool sOffscreenThrottlingEnabled;
   static bool sPrefCached = false;
 
   if (!sPrefCached) {
     sPrefCached = true;
--- a/dom/animation/AnimationUtils.h
+++ b/dom/animation/AnimationUtils.h
@@ -57,16 +57,24 @@ public:
 
   /**
    * Get the document from the JS context to use when parsing CSS properties.
    */
   static nsIDocument*
   GetCurrentRealmDocument(JSContext* aCx);
 
   /**
+   * Get the document from the global object, or nullptr if the document has
+   * no window, to use when constructing DOM object without entering the
+   * target window's compartment (see KeyframeEffect constructor).
+   */
+  static nsIDocument*
+  GetDocumentFromGlobal(JSObject* aGlobalObject);
+
+  /**
    * Checks if offscreen animation throttling is enabled.
    */
   static bool
   IsOffscreenThrottlingEnabled();
 
   /**
    * Returns true if the given EffectSet contains a current effect that animates
    * scale. |aFrame| is used for calculation of scale values.
--- a/dom/animation/KeyframeEffectReadOnly.cpp
+++ b/dom/animation/KeyframeEffectReadOnly.cpp
@@ -898,17 +898,27 @@ template <class KeyframeEffectType, clas
 /* static */ already_AddRefed<KeyframeEffectType>
 KeyframeEffectReadOnly::ConstructKeyframeEffect(
     const GlobalObject& aGlobal,
     const Nullable<ElementOrCSSPseudoElement>& aTarget,
     JS::Handle<JSObject*> aKeyframes,
     const OptionsType& aOptions,
     ErrorResult& aRv)
 {
-  nsIDocument* doc = AnimationUtils::GetCurrentRealmDocument(aGlobal.Context());
+  // We should get the document from `aGlobal` instead of the current Realm
+  // to make this works in Xray case.
+  //
+  // In all non-Xray cases, `aGlobal` matches the current Realm, so this
+  // matches the spec behavior.
+  //
+  // In Xray case, the new objects should be created using the document of
+  // the target global, but KeyframeEffect and KeyframeEffectReadOnly
+  // constructors are called in the caller's compartment to access
+  // `aKeyframes` object.
+  nsIDocument* doc = AnimationUtils::GetDocumentFromGlobal(aGlobal.Get());
   if (!doc) {
     aRv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
 
   TimingParams timingParams =
     TimingParams::FromOptionsUnion(aOptions, doc, aRv);
   if (aRv.Failed()) {
--- a/dom/animation/test/chrome.ini
+++ b/dom/animation/test/chrome.ini
@@ -3,16 +3,17 @@ support-files =
   testcommon.js
   ../../imptests/testharness.js
   ../../imptests/testharnessreport.js
   !/dom/animation/test/chrome/file_animate_xrays.html
 
 [chrome/test_animate_xrays.html]
 # file_animate_xrays.html needs to go in mochitest.ini since it is served
 # over HTTP
+[chrome/test_keyframe_effect_xrays.html]
 [chrome/test_animation_observers_async.html]
 [chrome/test_animation_observers_sync.html]
 [chrome/test_animation_performance_warning.html]
 [chrome/test_animation_properties.html]
 [chrome/test_cssanimation_missing_keyframes.html]
 [chrome/test_generated_content_getAnimations.html]
 [chrome/test_running_on_compositor.html]
 [chrome/test_simulate_compute_values_failure.html]
--- a/dom/animation/test/chrome/file_animate_xrays.html
+++ b/dom/animation/test/chrome/file_animate_xrays.html
@@ -1,19 +1,18 @@
 <!doctype html>
 <html>
 <head>
 <meta charset=utf-8>
 <script>
 Element.prototype.animate = function() {
   throw 'Called animate() as defined in content document';
 }
-// Bug 1211783: Use KeyframeEffect (not KeyframeEffectReadOnly) here
-for (var obj of [KeyframeEffectReadOnly, Animation]) {
-  obj = function() {
-    throw 'Called overridden ' + String(obj) + ' constructor';
+for (let name of ["KeyframeEffect", "KeyframeEffectReadOnly", "Animation"]) {
+  this[name] = function() {
+    throw `Called overridden ${name} constructor`;
   };
 }
 </script>
 <body>
 <div id="target"></div>
 </body>
 </html>
--- a/dom/animation/test/chrome/test_animate_xrays.html
+++ b/dom/animation/test/chrome/test_animate_xrays.html
@@ -1,31 +1,40 @@
 <!doctype html>
 <head>
 <meta charset=utf-8>
 <script type="application/javascript" src="../testharness.js"></script>
 <script type="application/javascript" src="../testharnessreport.js"></script>
 <script type="application/javascript" src="../testcommon.js"></script>
 </head>
 <body>
-<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1045994"
-  target="_blank">Mozilla Bug 1045994</a>
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1414674"
+  target="_blank">Mozilla Bug 1414674</a>
 <div id="log"></div>
 <iframe id="iframe"
   src="http://example.org/tests/dom/animation/test/chrome/file_animate_xrays.html"></iframe>
 <script>
 'use strict';
 
 var win = document.getElementById('iframe').contentWindow;
 
 async_test(function(t) {
   window.addEventListener('load', t.step_func(function() {
     var target = win.document.getElementById('target');
-    var anim = target.animate({ opacity: [ 0, 1 ] }, 100 * MS_PER_SEC);
-    // In the x-ray case, the frames object will be given an opaque wrapper
-    // so it won't be possible to fetch any frames from it.
-    assert_equals(anim.effect.getKeyframes().length, 0);
+    var anim = target.animate({opacity: [ 1, 0 ]}, 100 * MS_PER_SEC);
+    // The frames object should be accessible via x-ray.
+    var frames = anim.effect.getKeyframes();
+    assert_equals(frames.length, 2,
+                  "frames for Element.animate should be non-zero");
+    assert_equals(frames[0].opacity, "1",
+                  "first frame opacity for Element.animate should be specified value");
+    assert_equals(frames[0].computedOffset, 0,
+                  "first frame offset for Element.animate should be 0");
+    assert_equals(frames[1].opacity, "0",
+                  "last frame opacity for Element.animate should be specified value");
+    assert_equals(frames[1].computedOffset, 1,
+                  "last frame offset for Element.animate should be 1");
     t.done();
   }));
 }, 'Calling animate() across x-rays');
 
 </script>
 </body>
new file mode 100644
--- /dev/null
+++ b/dom/animation/test/chrome/test_keyframe_effect_xrays.html
@@ -0,0 +1,45 @@
+<!doctype html>
+<head>
+<meta charset=utf-8>
+<script type="application/javascript" src="../testharness.js"></script>
+<script type="application/javascript" src="../testharnessreport.js"></script>
+<script type="application/javascript" src="../testcommon.js"></script>
+</head>
+<body>
+<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1414674"
+  target="_blank">Mozilla Bug 1414674</a>
+<div id="log"></div>
+<iframe id="iframe"
+  src="http://example.org/tests/dom/animation/test/chrome/file_animate_xrays.html"></iframe>
+<script>
+'use strict';
+
+var win = document.getElementById('iframe').contentWindow;
+
+async_test(function(t) {
+  window.addEventListener('load', t.step_func(function() {
+    var target = win.document.getElementById('target');
+    var effect = new win.KeyframeEffect(target, [
+      {opacity: 1, offset: 0},
+      {opacity: 0, offset: 1},
+    ], {duration: 100 * MS_PER_SEC, fill: "forwards"});
+    // The frames object should be accessible via x-ray.
+    var frames = effect.getKeyframes();
+    assert_equals(frames.length, 2,
+                  "frames for KeyframeEffect ctor should be non-zero");
+    assert_equals(frames[0].opacity, "1",
+                  "first frame opacity for KeyframeEffect ctor should be specified value");
+    assert_equals(frames[0].computedOffset, 0,
+                  "first frame offset for KeyframeEffect ctor should be 0");
+    assert_equals(frames[1].opacity, "0",
+                  "last frame opacity for KeyframeEffect ctor should be specified value");
+    assert_equals(frames[1].computedOffset, 1,
+                  "last frame offset for KeyframeEffect ctor should be 1");
+    var animation = new win.Animation(effect, document.timeline);
+    animation.play();
+    t.done();
+  }));
+}, 'Calling KeyframeEffect() ctor across x-rays');
+
+</script>
+</body>
--- a/dom/base/Element.cpp
+++ b/dom/base/Element.cpp
@@ -3799,32 +3799,33 @@ Element::Animate(const Nullable<ElementO
   nsCOMPtr<nsIGlobalObject> ownerGlobal = referenceElement->GetOwnerGlobal();
   if (!ownerGlobal) {
     aError.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
   GlobalObject global(aContext, ownerGlobal->GetGlobalJSObject());
   MOZ_ASSERT(!global.Failed());
 
-  // Wrap the aKeyframes object for the cross-compartment case.
-  JS::Rooted<JSObject*> keyframes(aContext);
-  keyframes = aKeyframes;
+  // KeyframeEffect constructor doesn't follow the standard Xray calling
+  // convention and needs to be called in caller's compartment.
+  // This should match to RunConstructorInCallerCompartment attribute in
+  // KeyframeEffect.webidl.
+  RefPtr<KeyframeEffect> effect =
+    KeyframeEffect::Constructor(global, aTarget, aKeyframes, aOptions,
+                                aError);
+  if (aError.Failed()) {
+    return nullptr;
+  }
+
+  // Animation constructor follows the standard Xray calling convention and
+  // needs to be called in the target element's compartment.
   Maybe<JSAutoCompartment> ac;
   if (js::GetContextCompartment(aContext) !=
       js::GetObjectCompartment(ownerGlobal->GetGlobalJSObject())) {
     ac.emplace(aContext, ownerGlobal->GetGlobalJSObject());
-    if (!JS_WrapObject(aContext, &keyframes)) {
-      return nullptr;
-    }
-  }
-
-  RefPtr<KeyframeEffect> effect =
-    KeyframeEffect::Constructor(global, aTarget, keyframes, aOptions, aError);
-  if (aError.Failed()) {
-    return nullptr;
   }
 
   AnimationTimeline* timeline = referenceElement->OwnerDoc()->Timeline();
   RefPtr<Animation> animation =
     Animation::Constructor(global, effect,
                            Optional<AnimationTimeline*>(timeline), aError);
   if (aError.Failed()) {
     return nullptr;
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -6389,17 +6389,17 @@ nsGlobalWindowInner::GetController() con
 }
 
 RefPtr<ServiceWorker>
 nsGlobalWindowInner::GetOrCreateServiceWorker(const ServiceWorkerDescriptor& aDescriptor)
 {
   MOZ_ASSERT(NS_IsMainThread());
   RefPtr<ServiceWorker> ref;
   for (auto sw : mServiceWorkerList) {
-    if (sw->MatchesDescriptor(aDescriptor)) {
+    if (sw->Descriptor().Matches(aDescriptor)) {
       ref = sw;
       return ref.forget();
     }
   }
   ref = ServiceWorker::Create(this, aDescriptor);
   return ref.forget();
 }
 
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -7801,17 +7801,20 @@ class CGPerSignatureCall(CGThing):
         # since GlobalObject already contains the context.
         needsCx = needCx(returnType, arguments, self.extendedAttributes,
                          not descriptor.interface.isJSImplemented(), static)
         if needsCx:
             argsPre.append("cx")
 
         needsUnwrap = False
         argsPost = []
-        if isConstructor:
+        runConstructorInCallerCompartment = \
+            descriptor.interface.getExtendedAttribute(
+                'RunConstructorInCallerCompartment')
+        if isConstructor and not runConstructorInCallerCompartment:
             needsUnwrap = True
             needsUnwrappedVar = False
             unwrappedVar = "obj"
             if descriptor.interface.isJSImplemented():
                 # We need the desired proto in our constructor, because the
                 # constructor will actually construct our reflector.
                 argsPost.append("desiredProto")
         elif descriptor.interface.isJSImplemented():
--- a/dom/bindings/parser/WebIDL.py
+++ b/dom/bindings/parser/WebIDL.py
@@ -1738,16 +1738,17 @@ class IDLInterface(IDLInterfaceOrNamespa
                     member.addExtendedAttributes([attr])
             elif (identifier == "NeedResolve" or
                   identifier == "OverrideBuiltins" or
                   identifier == "ChromeOnly" or
                   identifier == "Unforgeable" or
                   identifier == "LegacyEventInit" or
                   identifier == "ProbablyShortLivingWrapper" or
                   identifier == "LegacyUnenumerableNamedProperties" or
+                  identifier == "RunConstructorInCallerCompartment" or
                   identifier == "NonOrdinaryGetPrototypeOf"):
                 # Known extended attributes that do not take values
                 if not attr.noArguments():
                     raise WebIDLError("[%s] must take no arguments" % identifier,
                                       [attr.location])
             elif identifier == "Exposed":
                 convertExposedAttrToGlobalNameSet(attr,
                                                   self._exposureGlobalNames)
--- a/dom/clients/manager/ClientSource.cpp
+++ b/dom/clients/manager/ClientSource.cpp
@@ -564,20 +564,19 @@ ClientSource::PostMessage(const ClientPo
     // Shutting down. Just don't deliver this message.
     ref = ClientOpPromise::CreateAndReject(NS_ERROR_FAILURE, __func__);
     return ref.forget();
   }
 
   RefPtr<ServiceWorkerRegistrationInfo> reg =
     swm->GetRegistration(principal, source.Scope());
   if (reg) {
-    RefPtr<ServiceWorkerInfo> serviceWorker = reg->GetByID(source.Id());
-    if (serviceWorker) {
-      RefPtr<ServiceWorker> instance =
-        globalObject->GetOrCreateServiceWorker(source);
+    RefPtr<ServiceWorker> instance =
+      globalObject->GetOrCreateServiceWorker(source);
+    if (instance) {
       init.mSource.SetValue().SetAsServiceWorker() = instance;
     }
   }
 
   RefPtr<MessageEvent> event =
     MessageEvent::Constructor(target, NS_LITERAL_STRING("message"), init);
   event->SetTrusted(true);
 
--- a/dom/serviceworkers/ServiceWorker.cpp
+++ b/dom/serviceworkers/ServiceWorker.cpp
@@ -51,17 +51,17 @@ ServiceWorker::Create(nsIGlobalObject* a
   }
 
   RefPtr<ServiceWorkerRegistrationInfo> reg =
     swm->GetRegistration(aDescriptor.PrincipalInfo(), aDescriptor.Scope());
   if (!reg) {
     return ref.forget();
   }
 
-  RefPtr<ServiceWorkerInfo> info = reg->GetByID(aDescriptor.Id());
+  RefPtr<ServiceWorkerInfo> info = reg->GetByDescriptor(aDescriptor);
   if (!info) {
     return ref.forget();
   }
 
   ref = new ServiceWorker(aOwner, aDescriptor, info);
   return ref.forget();
 }
 
@@ -136,25 +136,21 @@ ServiceWorker::PostMessage(JSContext* aC
   if (State() == ServiceWorkerState::Redundant) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
   mInner->PostMessage(GetParentObject(), aCx, aMessage, aTransferable, aRv);
 }
 
-bool
-ServiceWorker::MatchesDescriptor(const ServiceWorkerDescriptor& aDescriptor) const
+
+const ServiceWorkerDescriptor&
+ServiceWorker::Descriptor() const
 {
-  // Compare everything in the descriptor except the state.  That is mutable
-  // and may not exactly match.
-  return mDescriptor.PrincipalInfo() == aDescriptor.PrincipalInfo() &&
-         mDescriptor.Scope() == aDescriptor.Scope() &&
-         mDescriptor.ScriptURL() == aDescriptor.ScriptURL() &&
-         mDescriptor.Id() == aDescriptor.Id();
+  return mDescriptor;
 }
 
 void
 ServiceWorker::DisconnectFromOwner()
 {
   nsIGlobalObject* global = GetParentObject();
   if (global) {
     global->RemoveServiceWorker(this);
--- a/dom/serviceworkers/ServiceWorker.h
+++ b/dom/serviceworkers/ServiceWorker.h
@@ -78,18 +78,18 @@ public:
 
   void
   GetScriptURL(nsString& aURL) const;
 
   void
   PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
               const Sequence<JSObject*>& aTransferable, ErrorResult& aRv);
 
-  bool
-  MatchesDescriptor(const ServiceWorkerDescriptor& aDescriptor) const;
+  const ServiceWorkerDescriptor&
+  Descriptor() const;
 
   void
   DisconnectFromOwner() override;
 
 private:
   ServiceWorker(nsIGlobalObject* aWindow,
                 const ServiceWorkerDescriptor& aDescriptor,
                 Inner* aInner);
--- a/dom/serviceworkers/ServiceWorkerDescriptor.cpp
+++ b/dom/serviceworkers/ServiceWorkerDescriptor.cpp
@@ -114,16 +114,25 @@ ServiceWorkerDescriptor::State() const
 }
 
 void
 ServiceWorkerDescriptor::SetState(ServiceWorkerState aState)
 {
   mData->state() = aState;
 }
 
+bool
+ServiceWorkerDescriptor::Matches(const ServiceWorkerDescriptor& aDescriptor) const
+{
+  return Id() == aDescriptor.Id() &&
+         Scope() == aDescriptor.Scope() &&
+         ScriptURL() == aDescriptor.ScriptURL() &&
+         PrincipalInfo() == aDescriptor.PrincipalInfo();
+}
+
 const IPCServiceWorkerDescriptor&
 ServiceWorkerDescriptor::ToIPC() const
 {
   return *mData;
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/serviceworkers/ServiceWorkerDescriptor.h
+++ b/dom/serviceworkers/ServiceWorkerDescriptor.h
@@ -76,16 +76,21 @@ public:
   ScriptURL() const;
 
   ServiceWorkerState
   State() const;
 
   void
   SetState(ServiceWorkerState aState);
 
+  // Try to determine if two workers match each other.  This is less strict
+  // than an operator==() call since it ignores mutable values like State().
+  bool
+  Matches(const ServiceWorkerDescriptor& aDescriptor) const;
+
   // Expose the underlying IPC type so that it can be passed via IPC.
   const IPCServiceWorkerDescriptor&
   ToIPC() const;
 };
 
 } // namespace dom
 } // namespace mozilla
 
--- a/dom/serviceworkers/ServiceWorkerRegistrationImpl.cpp
+++ b/dom/serviceworkers/ServiceWorkerRegistrationImpl.cpp
@@ -985,16 +985,20 @@ WorkerListener::UpdateFound()
 
 // Notification API extension.
 already_AddRefed<Promise>
 ServiceWorkerRegistrationWorkerThread::ShowNotification(JSContext* aCx,
                                                         const nsAString& aTitle,
                                                         const NotificationOptions& aOptions,
                                                         ErrorResult& aRv)
 {
+  if (!mWorkerPrivate) {
+    aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
+    return nullptr;
+  }
 
   // Until Bug 1131324 exposes ServiceWorkerContainer on workers,
   // ShowPersistentNotification() checks for valid active worker while it is
   // also verifying scope so that we block the worker on the main thread only
   // once.
   RefPtr<Promise> p =
     Notification::ShowPersistentNotification(aCx, mWorkerPrivate->GlobalScope(),
                                              mScope, aTitle, aOptions, aRv);
--- a/dom/serviceworkers/ServiceWorkerRegistrationInfo.cpp
+++ b/dom/serviceworkers/ServiceWorkerRegistrationInfo.cpp
@@ -487,28 +487,28 @@ ServiceWorkerRegistrationInfo::GetWaitin
 ServiceWorkerInfo*
 ServiceWorkerRegistrationInfo::GetActive() const
 {
   MOZ_ASSERT(NS_IsMainThread());
   return mActiveWorker;
 }
 
 ServiceWorkerInfo*
-ServiceWorkerRegistrationInfo::GetByID(uint64_t aID) const
+ServiceWorkerRegistrationInfo::GetByDescriptor(const ServiceWorkerDescriptor& aDescriptor) const
 {
-  if (mActiveWorker && mActiveWorker->ID() == aID) {
+  if (mActiveWorker && mActiveWorker->Descriptor().Matches(aDescriptor)) {
     return mActiveWorker;
   }
-  if (mWaitingWorker && mWaitingWorker->ID() == aID) {
+  if (mWaitingWorker && mWaitingWorker->Descriptor().Matches(aDescriptor)) {
     return mWaitingWorker;
   }
-  if (mInstallingWorker && mInstallingWorker->ID() == aID) {
+  if (mInstallingWorker && mInstallingWorker->Descriptor().Matches(aDescriptor)) {
     return mInstallingWorker;
   }
-  if (mEvaluatingWorker && mEvaluatingWorker->ID() == aID) {
+  if (mEvaluatingWorker && mEvaluatingWorker->Descriptor().Matches(aDescriptor)) {
     return mEvaluatingWorker;
   }
   return nullptr;
 }
 
 void
 ServiceWorkerRegistrationInfo::SetEvaluating(ServiceWorkerInfo* aServiceWorker)
 {
--- a/dom/serviceworkers/ServiceWorkerRegistrationInfo.h
+++ b/dom/serviceworkers/ServiceWorkerRegistrationInfo.h
@@ -147,17 +147,17 @@ public:
 
   ServiceWorkerInfo*
   GetWaiting() const;
 
   ServiceWorkerInfo*
   GetActive() const;
 
   ServiceWorkerInfo*
-  GetByID(uint64_t aID) const;
+  GetByDescriptor(const ServiceWorkerDescriptor& aDescriptor) const;
 
   // Set the given worker as the evaluating service worker.  The worker
   // state is not changed.
   void
   SetEvaluating(ServiceWorkerInfo* aServiceWorker);
 
   // Remove an existing evaluating worker, if present.  The worker will
   // be transitioned to the Redundant state.
--- a/dom/webidl/KeyframeEffect.webidl
+++ b/dom/webidl/KeyframeEffect.webidl
@@ -15,17 +15,20 @@ enum IterationCompositeOperation {
   "accumulate"
 };
 
 dictionary KeyframeEffectOptions : AnimationEffectTimingProperties {
   IterationCompositeOperation iterationComposite = "replace";
   CompositeOperation          composite = "replace";
 };
 
+// KeyframeEffectReadOnly should run in the caller's compartment to do custom
+// processing on the `keyframes` object.
 [Func="nsDocument::IsWebAnimationsEnabled",
+ RunConstructorInCallerCompartment,
  Constructor ((Element or CSSPseudoElement)? target,
               object? keyframes,
               optional (unrestricted double or KeyframeEffectOptions) options),
  Constructor (KeyframeEffectReadOnly source)]
 interface KeyframeEffectReadOnly : AnimationEffectReadOnly {
   readonly attribute (Element or CSSPseudoElement)?  target;
   readonly attribute IterationCompositeOperation iterationComposite;
   readonly attribute CompositeOperation          composite;
@@ -49,17 +52,20 @@ dictionary AnimationPropertyDetails {
            DOMString                               warning;
   required sequence<AnimationPropertyValueDetails> values;
 };
 
 partial interface KeyframeEffectReadOnly {
   [ChromeOnly, Throws] sequence<AnimationPropertyDetails> getProperties();
 };
 
+// KeyframeEffect should run in the caller's compartment to do custom
+// processing on the `keyframes` object.
 [Func="nsDocument::IsWebAnimationsEnabled",
+ RunConstructorInCallerCompartment,
  Constructor ((Element or CSSPseudoElement)? target,
               object? keyframes,
               optional (unrestricted double or KeyframeEffectOptions) options),
  Constructor (KeyframeEffectReadOnly source)]
 interface KeyframeEffect : KeyframeEffectReadOnly {
   inherit attribute (Element or CSSPseudoElement)? target;
   [NeedsCallerType]
   inherit attribute IterationCompositeOperation    iterationComposite;
--- a/gfx/2d/RecordedEventImpl.h
+++ b/gfx/2d/RecordedEventImpl.h
@@ -976,17 +976,17 @@ public:
     recordedFontData->SetFontData(aData, aSize, aIndex);
   }
 
   explicit RecordedFontData(UnscaledFont *aUnscaledFont)
     : RecordedEventDerived(FONTDATA)
     , mType(aUnscaledFont->GetType())
     , mData(nullptr)
   {
-    mGetFontFileDataSucceeded = aUnscaledFont->GetFontFileData(&FontDataProc, this);
+    mGetFontFileDataSucceeded = aUnscaledFont->GetFontFileData(&FontDataProc, this) && mData;
   }
 
   ~RecordedFontData();
 
   bool IsValid() const { return mGetFontFileDataSucceeded; }
 
   virtual bool PlayEvent(Translator *aTranslator) const override;
 
@@ -2680,21 +2680,22 @@ RecordedSourceSurfaceCreation::Record(S 
 
 template<class S>
 RecordedSourceSurfaceCreation::RecordedSourceSurfaceCreation(S &aStream)
   : RecordedEventDerived(SOURCESURFACECREATION), mDataOwned(true)
 {
   ReadElement(aStream, mRefPtr);
   ReadElement(aStream, mSize);
   ReadElement(aStream, mFormat);
-  mData = (uint8_t*)new (fallible) char[mSize.width * mSize.height * BytesPerPixel(mFormat)];
+  size_t size = mSize.width * mSize.height * BytesPerPixel(mFormat);
+  mData = new (fallible) uint8_t[size];
   if (!mData) {
-    gfxWarning() << "RecordedSourceSurfaceCreation failed to allocate data";
+    gfxCriticalNote << "RecordedSourceSurfaceCreation failed to allocate data of size " << size;
   } else {
-    aStream.read((char*)mData, mSize.width * mSize.height * BytesPerPixel(mFormat));
+    aStream.read((char*)mData, size);
   }
 }
 
 inline void
 RecordedSourceSurfaceCreation::OutputSimpleEventInfo(std::stringstream &aStringStream) const
 {
   aStringStream << "[" << mRefPtr << "] SourceSurface created (Size: " << mSize.width << "x" << mSize.height << ")";
 }
@@ -2929,16 +2930,20 @@ inline
 RecordedFontData::~RecordedFontData()
 {
   delete[] mData;
 }
 
 inline bool
 RecordedFontData::PlayEvent(Translator *aTranslator) const
 {
+  if (!mData) {
+    return false;
+  }
+
   RefPtr<NativeFontResource> fontResource =
     Factory::CreateNativeFontResource(mData, mFontDetails.size,
                                       aTranslator->GetReferenceDrawTarget()->GetBackendType(),
                                       mType, aTranslator->GetFontContext());
   if (!fontResource) {
     return false;
   }
 
@@ -2962,18 +2967,22 @@ inline void
 RecordedFontData::OutputSimpleEventInfo(std::stringstream &aStringStream) const
 {
   aStringStream << "Font Data of size " << mFontDetails.size;
 }
 
 inline void
 RecordedFontData::SetFontData(const uint8_t *aData, uint32_t aSize, uint32_t aIndex)
 {
-  mData = new uint8_t[aSize];
-  memcpy(mData, aData, aSize);
+  mData = new (fallible) uint8_t[aSize];
+  if (!mData) {
+    gfxCriticalNote << "RecordedFontData failed to allocate data for recording of size " << aSize;
+  } else {
+    memcpy(mData, aData, aSize);
+  }
   mFontDetails.fontDataKey =
     SFNTData::GetUniqueKey(aData, aSize, 0, nullptr);
   mFontDetails.size = aSize;
   mFontDetails.index = aIndex;
 }
 
 inline bool
 RecordedFontData::GetFontDetails(RecordedFontDetails& fontDetails)
@@ -2992,18 +3001,22 @@ template<class S>
 RecordedFontData::RecordedFontData(S &aStream)
   : RecordedEventDerived(FONTDATA)
   , mType(FontType::SKIA)
   , mData(nullptr)
 {
   ReadElement(aStream, mType);
   ReadElement(aStream, mFontDetails.fontDataKey);
   ReadElement(aStream, mFontDetails.size);
-  mData = new uint8_t[mFontDetails.size];
-  aStream.read((char*)mData, mFontDetails.size);
+  mData = new (fallible) uint8_t[mFontDetails.size];
+  if (!mData) {
+    gfxCriticalNote << "RecordedFontData failed to allocate data for playback of size " << mFontDetails.size;
+  } else {
+    aStream.read((char*)mData, mFontDetails.size);
+  }
 }
 
 inline
 RecordedFontDescriptor::~RecordedFontDescriptor()
 {
 }
 
 inline bool