merge mozilla-inbound to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Thu, 24 Mar 2016 16:15:30 +0100
changeset 290223 24c5fbde4488e06ef79905e1c520027cddcd1189
parent 290039 b999fac0569d760e971b6579c420eb887d645433 (current diff)
parent 290222 964a638d77fab2dbe92397c4cc9ea09fb344db06 (diff)
child 290224 50b237b0fbaa4566e92efebb253149e904556de8
child 290249 df7e8620c830db77b0d51b78ae81fcdb3d122d29
child 290337 40ae8489939e21be81751a5e241f6ab7e2b4d9a2
push id30114
push usercbook@mozilla.com
push dateThu, 24 Mar 2016 15:15:54 +0000
treeherdermozilla-central@24c5fbde4488 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
merge mozilla-inbound to mozilla-central a=merge
dom/base/test/chrome/test_mutationobserver_anonymous.html
dom/webidl/Keyframe.webidl
dom/webidl/PropertyIndexedKeyframes.webidl
mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
mobile/android/base/java/org/mozilla/gecko/home/HistoryPanel.java
mobile/android/base/java/org/mozilla/gecko/home/RecentTabsPanel.java
testing/puppeteer/firefox/firefox_puppeteer/testcases/update.py
testing/web-platform/meta/2dcontext/fill-and-stroke-styles/2d.gradient.radial.cone.behind.html.ini
testing/web-platform/meta/2dcontext/fill-and-stroke-styles/2d.gradient.radial.cone.beside.html.ini
testing/web-platform/meta/2dcontext/fill-and-stroke-styles/2d.gradient.radial.cone.shape2.html.ini
testing/web-platform/meta/2dcontext/fill-and-stroke-styles/2d.gradient.radial.equal.html.ini
testing/web-platform/meta/2dcontext/fill-and-stroke-styles/2d.gradient.radial.touch1.html.ini
testing/web-platform/meta/2dcontext/fill-and-stroke-styles/2d.gradient.radial.touch3.html.ini
testing/web-platform/meta/2dcontext/path-objects/2d.path.quadraticCurveTo.ensuresubpath.1.html.ini
testing/web-platform/meta/2dcontext/path-objects/2d.path.quadraticCurveTo.ensuresubpath.2.html.ini
testing/web-platform/meta/2dcontext/path-objects/2d.path.rect.selfintersect.html.ini
testing/web-platform/meta/2dcontext/path-objects/2d.path.rect.zero.6.html.ini
--- a/accessible/tests/mochitest/treeview.js
+++ b/accessible/tests/mochitest/treeview.js
@@ -272,22 +272,16 @@ function treeItem(aText, aOpen, aChildre
   this.colsText = {};
   this.open = aOpen;
   this.value = "true";
   this.cyclerState = 0;
   if (aChildren)
     this.children = aChildren;
 }
 
-function createAtom(aName)
-{
-  return Components.classes["@mozilla.org/atom-service;1"]
-    .getService(Components.interfaces.nsIAtomService).getAtom(aName);
-}
-
 /**
  * Used in conjunction with loadXULTreeAndDoTest and addA11yXULTreeLoadEvent.
  */
 var gXULTreeLoadContext =
 {
   doTestFunc: null,
   treeID: null,
   treeView: null,
--- a/accessible/windows/msaa/AccessibleWrap.cpp
+++ b/accessible/windows/msaa/AccessibleWrap.cpp
@@ -1520,17 +1520,17 @@ AccessibleWrap::GetXPAccessibleFor(const
 
   // If lVal negative then it is treated as child ID and we should look for
   // accessible through whole accessible subtree including subdocuments.
   // Otherwise we treat lVal as index in parent.
   // Convert child ID to unique ID.
   // First handle the case that both this accessible and the id'd one are in
   // this process.
   if (!IsProxy()) {
-    void* uniqueID = reinterpret_cast<void*>(-aVarChild.lVal);
+    void* uniqueID = reinterpret_cast<void*>(intptr_t(-aVarChild.lVal));
 
     DocAccessible* document = Document();
     Accessible* child =
 #ifdef _WIN64
       GetAccessibleInSubtree(document, static_cast<uint32_t>(aVarChild.lVal));
 #else
       document->GetAccessibleByUniqueIDInSubtree(uniqueID);
 #endif
--- a/accessible/xul/XULTreeAccessible.cpp
+++ b/accessible/xul/XULTreeAccessible.cpp
@@ -524,17 +524,17 @@ XULTreeAccessible::GetTreeItemAccessible
   if (aRow < 0 || IsDefunct() || !mTreeView)
     return nullptr;
 
   int32_t rowCount = 0;
   nsresult rv = mTreeView->GetRowCount(&rowCount);
   if (NS_FAILED(rv) || aRow >= rowCount)
     return nullptr;
 
-  void *key = reinterpret_cast<void*>(aRow);
+  void *key = reinterpret_cast<void*>(intptr_t(aRow));
   Accessible* cachedTreeItem = mAccessibleCache.GetWeak(key);
   if (cachedTreeItem)
     return cachedTreeItem;
 
   RefPtr<Accessible> treeItem = CreateTreeItemAccessible(aRow);
   if (treeItem) {
     mAccessibleCache.Put(key, treeItem);
     Document()->BindToDocument(treeItem, nullptr);
@@ -559,17 +559,17 @@ XULTreeAccessible::InvalidateCache(int32
   if (aCount > 0)
     return;
 
   DocAccessible* document = Document();
 
   // Fire destroy event for removed tree items and delete them from caches.
   for (int32_t rowIdx = aRow; rowIdx < aRow - aCount; rowIdx++) {
 
-    void* key = reinterpret_cast<void*>(rowIdx);
+    void* key = reinterpret_cast<void*>(intptr_t(rowIdx));
     Accessible* treeItem = mAccessibleCache.GetWeak(key);
 
     if (treeItem) {
       RefPtr<AccEvent> event =
         new AccEvent(nsIAccessibleEvent::EVENT_HIDE, treeItem);
       nsEventShell::FireEvent(event);
 
       // Unbind from document, shutdown and remove from tree cache.
@@ -585,17 +585,17 @@ XULTreeAccessible::InvalidateCache(int32
   nsresult rv = mTreeView->GetRowCount(&newRowCount);
   if (NS_FAILED(rv))
     return;
 
   int32_t oldRowCount = newRowCount - aCount;
 
   for (int32_t rowIdx = newRowCount; rowIdx < oldRowCount; ++rowIdx) {
 
-    void *key = reinterpret_cast<void*>(rowIdx);
+    void *key = reinterpret_cast<void*>(intptr_t(rowIdx));
     Accessible* treeItem = mAccessibleCache.GetWeak(key);
 
     if (treeItem) {
       // Unbind from document, shutdown and remove from tree cache.
       document->UnbindFromDocument(treeItem);
       mAccessibleCache.Remove(key);
     }
   }
@@ -638,17 +638,17 @@ XULTreeAccessible::TreeViewInvalidated(i
     if (NS_FAILED(rv))
       return;
 
     endCol = colCount - 1;
   }
 
   for (int32_t rowIdx = aStartRow; rowIdx <= endRow; ++rowIdx) {
 
-    void *key = reinterpret_cast<void*>(rowIdx);
+    void *key = reinterpret_cast<void*>(intptr_t(rowIdx));
     Accessible* accessible = mAccessibleCache.GetWeak(key);
 
     if (accessible) {
       RefPtr<XULTreeItemAccessibleBase> treeitemAcc = do_QueryObject(accessible);
       NS_ASSERTION(treeitemAcc, "Wrong accessible at the given key!");
 
       treeitemAcc->RowInvalidated(aStartCol, endCol);
     }
--- a/b2g/common.configure
+++ b/b2g/common.configure
@@ -9,13 +9,21 @@
 option(env='MOZTTDIR', nargs=1, help='Path to truetype fonts for B2G')
 
 @depends('MOZTTDIR')
 def mozttdir(value):
     if value:
         path = value[0]
         if not os.path.isdir(path):
             error('MOZTTDIR "%s" is not a valid directory' % path)
-        set_config('MOZTTDIR', path)
-        set_define('PACKAGE_MOZTT', True)
+        return path
+
+set_config('MOZTTDIR', mozttdir)
+
+@depends('MOZTTDIR')
+def package_moztt(value):
+    if value:
+        return True
+
+set_define('PACKAGE_MOZTT', package_moztt)
 
 
 include('../toolkit/moz.configure')
--- a/browser/components/uitour/UITour.jsm
+++ b/browser/components/uitour/UITour.jsm
@@ -747,22 +747,16 @@ this.UITour = {
     Services.obs.addObserver(this, "message-manager-close", false);
 
     window.addEventListener("SSWindowClosing", this);
   },
 
   handleEvent: function(aEvent) {
     log.debug("handleEvent: type =", aEvent.type, "event =", aEvent);
     switch (aEvent.type) {
-      case "pagehide": {
-        let window = this.getChromeWindow(aEvent.target);
-        this.teardownTourForWindow(window);
-        break;
-      }
-
       case "TabSelect": {
         let window = aEvent.target.ownerDocument.defaultView;
 
         // Teardown the browser of the tab we just switched away from.
         if (aEvent.detail && aEvent.detail.previousTab) {
           let previousTab = aEvent.detail.previousTab;
           let openTourWindows = this.tourBrowsersByWindow.get(window);
           if (openTourWindows.has(previousTab.linkedBrowser)) {
@@ -937,28 +931,16 @@ this.UITour = {
           this.setExpiringTelemetryBucket(pageID, "closed");
         }
       }
     }
 
     this.tourBrowsersByWindow.delete(aWindow);
   },
 
-  getChromeWindow: function(aContentDocument) {
-    return aContentDocument.defaultView
-                           .window
-                           .QueryInterface(Ci.nsIInterfaceRequestor)
-                           .getInterface(Ci.nsIWebNavigation)
-                           .QueryInterface(Ci.nsIDocShellTreeItem)
-                           .rootTreeItem
-                           .QueryInterface(Ci.nsIInterfaceRequestor)
-                           .getInterface(Ci.nsIDOMWindow)
-                           .wrappedJSObject;
-  },
-
   // This function is copied to UITourListener.
   isSafeScheme: function(aURI) {
     let allowedSchemes = new Set(["https", "about"]);
     if (!Services.prefs.getBoolPref("browser.uitour.requireSecure"))
       allowedSchemes.add("http");
 
     if (!allowedSchemes.has(aURI.scheme)) {
       log.error("Unsafe scheme:", aURI.scheme);
--- a/browser/components/uitour/test/browser.ini
+++ b/browser/components/uitour/test/browser.ini
@@ -25,17 +25,16 @@ tag = trackingprotection
 skip-if = os == "linux" # Intermittent failures, bug 951965
 [browser_UITour2.js]
 [browser_UITour3.js]
 skip-if = os == "linux" # Linux: Bug 986760, Bug 989101.
 [browser_UITour_availableTargets.js]
 [browser_UITour_annotation_size_attributes.js]
 [browser_UITour_defaultBrowser.js]
 [browser_UITour_detach_tab.js]
-skip-if = e10s # Bug 1240747 - UITour.jsm not e10s friendly
 [browser_UITour_forceReaderMode.js]
 [browser_UITour_heartbeat.js]
 skip-if = e10s # Bug 1240747 - UITour.jsm not e10s friendly.
 [browser_UITour_loop.js]
 skip-if = true # Bug 1225832 - New Loop architecture is not compatible with test.
 [browser_UITour_loop_panel.js]
 [browser_UITour_modalDialog.js]
 skip-if = os != "mac" # modal dialog disabling only working on OS X.
--- a/browser/components/uitour/test/browser_UITour_detach_tab.js
+++ b/browser/components/uitour/test/browser_UITour_detach_tab.js
@@ -21,59 +21,66 @@ function test() {
 
 /**
  * When tab is changed we're tearing the tour down. So the UITour client has to always be aware of this
  * fact and therefore listens to visibilitychange events.
  * In particular this scenario happens for detaching the tab (ie. moving it to a new window).
  */
 var tests = [
   taskify(function* test_move_tab_to_new_window() {
-    let onVisibilityChange = (aEvent) => {
-      if (!document.hidden && window != UITour.getChromeWindow(aEvent.target)) {
-        gContentAPI.showHighlight("appMenu");
-      }
-    };
+    const myDocIdentifier = "Hello, I'm a unique expando to identify this document.";
 
     let highlight = document.getElementById("UITourHighlight");
     let windowDestroyedDeferred = Promise.defer();
     let onDOMWindowDestroyed = (aWindow) => {
       if (gContentWindow && aWindow == gContentWindow) {
         Services.obs.removeObserver(onDOMWindowDestroyed, "dom-window-destroyed", false);
         windowDestroyedDeferred.resolve();
       }
     };
 
     let browserStartupDeferred = Promise.defer();
     Services.obs.addObserver(function onBrowserDelayedStartup(aWindow) {
       Services.obs.removeObserver(onBrowserDelayedStartup, "browser-delayed-startup-finished");
       browserStartupDeferred.resolve(aWindow);
     }, "browser-delayed-startup-finished", false);
 
-    // NB: we're using this rather than gContentWindow.document because the latter wouldn't
-    // have an XRayWrapper, and we need to compare this to the doc we get using this method
-    // later on...
-    gContentDoc = gBrowser.selectedBrowser.contentDocument;
-    gContentDoc.addEventListener("visibilitychange", onVisibilityChange, false);
+    yield ContentTask.spawn(gBrowser.selectedBrowser, myDocIdentifier, myDocIdentifier => {
+      let onVisibilityChange = () => {
+        if (!content.document.hidden) {
+          let win = Cu.waiveXrays(content);
+          win.Mozilla.UITour.showHighlight("appMenu");
+        }
+      };
+      content.document.addEventListener("visibilitychange", onVisibilityChange);
+      content.document.myExpando = myDocIdentifier;
+    });
     gContentAPI.showHighlight("appMenu");
 
     yield elementVisiblePromise(highlight);
 
-    gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
-
-    gContentWindow = yield browserStartupDeferred.promise;
+    gContentWindow = gBrowser.replaceTabWithWindow(gBrowser.selectedTab);
+    yield browserStartupDeferred.promise;
 
     // This highlight should be shown thanks to the visibilitychange listener.
     let newWindowHighlight = gContentWindow.document.getElementById("UITourHighlight");
     yield elementVisiblePromise(newWindowHighlight);
 
     let selectedTab = gContentWindow.gBrowser.selectedTab;
-    is(selectedTab.linkedBrowser && selectedTab.linkedBrowser.contentDocument, gContentDoc, "Document should be selected in new window");
+    yield ContentTask.spawn(selectedTab.linkedBrowser, myDocIdentifier, myDocIdentifier => {
+      is(content.document.myExpando, myDocIdentifier, "Document should be selected in new window");
+    });
     ok(UITour.tourBrowsersByWindow && UITour.tourBrowsersByWindow.has(gContentWindow), "Window should be known");
     ok(UITour.tourBrowsersByWindow.get(gContentWindow).has(selectedTab.linkedBrowser), "Selected browser should be known");
 
+    // Need this because gContentAPI in e10s land will try to use gTestTab to
+    // spawn a content task, which doesn't work if the tab is dead, for obvious
+    // reasons.
+    gTestTab = gContentWindow.gBrowser.selectedTab;
+
     let shownPromise = promisePanelShown(gContentWindow);
     gContentAPI.showMenu("appMenu");
     yield shownPromise;
 
     isnot(gContentWindow.PanelUI.panel.state, "closed", "Panel should be open");
     ok(gContentWindow.PanelUI.contents.children.length > 0, "Panel contents should have children");
     gContentAPI.hideHighlight();
     gContentAPI.hideMenu("appMenu");
--- a/browser/config/mozconfigs/win32/beta
+++ b/browser/config/mozconfigs/win32/beta
@@ -8,9 +8,11 @@ fi
 . "$topsrcdir/build/mozconfig.win-common"
 . "$topsrcdir/browser/config/mozconfigs/win32/common-opt"
 
 mk_add_options MOZ_PGO=1
 
 ac_add_options --enable-official-branding
 ac_add_options --enable-verify-mar
 
+. "$topsrcdir/build/mozconfig.rust"
+
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/win32/nightly
+++ b/browser/config/mozconfigs/win32/nightly
@@ -1,10 +1,12 @@
 . "$topsrcdir/build/mozconfig.win-common"
 . "$topsrcdir/browser/config/mozconfigs/win32/common-opt"
 
 ac_add_options --enable-profiling
 ac_add_options --enable-verify-mar
 
 ac_add_options --with-branding=browser/branding/nightly
 
+. "$topsrcdir/build/mozconfig.rust"
+
 . "$topsrcdir/build/mozconfig.common.override"
 . "$topsrcdir/build/mozconfig.cache"
--- a/browser/config/mozconfigs/win32/release
+++ b/browser/config/mozconfigs/win32/release
@@ -14,9 +14,11 @@ mk_add_options MOZ_PGO=1
 
 ac_add_options --enable-official-branding
 ac_add_options --enable-verify-mar
 
 # safeguard against someone forgetting to re-set EARLY_BETA_OR_EARLIER in
 # defines.sh during the beta cycle
 export BUILDING_RELEASE=1
 
+. "$topsrcdir/build/mozconfig.rust"
+
 . "$topsrcdir/build/mozconfig.common.override"
--- a/build/moz.configure/checks.configure
+++ b/build/moz.configure/checks.configure
@@ -65,16 +65,21 @@ def check_prog(var, progs, allow_missing
             result = find_program(prog)
             if result:
                 return result
         return not_found
 
     @depends(check)
     @advanced
     def postcheck(value):
-        set_config(var, ':' if value is not_found else value)
         if value is not_found and not allow_missing:
             from mozbuild.shellutil import quote
             error('Cannot find %s (tried: %s)'
                   % (var.lower(), ', '.join(quote(p) for p in progs)))
         return None if value is not_found else value
 
+    @depends(postcheck)
+    def normalized_for_config(value):
+        return ':' if value is None else value
+
+    set_config(var, normalized_for_config)
+
     return postcheck
--- a/build/moz.configure/init.configure
+++ b/build/moz.configure/init.configure
@@ -21,20 +21,16 @@ def check_build_environment(help, dist):
     else:
         dist = os.path.join(topobjdir, 'dist')
 
     result = namespace(
         topsrcdir=topsrcdir,
         topobjdir=topobjdir,
         dist=dist,
     )
-    set_config('TOPSRCDIR', topsrcdir)
-    set_config('TOPOBJDIR', topobjdir)
-    set_config('MOZ_BUILD_ROOT', topobjdir)
-    set_config('DIST', dist)
 
     if help:
         return result
 
     if topsrcdir == topobjdir:
         error(
             '  ***\n'
             '  * Building directly in the main source directory is not allowed.\n'
@@ -64,16 +60,22 @@ def check_build_environment(help, dist):
             '  *     1. cd %s\n'
             '  *     2. gmake distclean\n'
             '  ***'
             % ('\n  '.join(conflict_files), topsrcdir)
         )
 
     return result
 
+set_config('TOPSRCDIR', delayed_getattr(check_build_environment, 'topsrcdir'))
+set_config('TOPOBJDIR', delayed_getattr(check_build_environment, 'topobjdir'))
+set_config('MOZ_BUILD_ROOT', delayed_getattr(check_build_environment,
+                                             'topobjdir'))
+set_config('DIST', delayed_getattr(check_build_environment, 'dist'))
+
 
 option(env='OLD_CONFIGURE', nargs=1, help='Path to the old configure script')
 
 option(env='MOZ_CURRENT_PROJECT', nargs=1, help='Current build project')
 option(env='MOZCONFIG', nargs=1, help='Mozconfig location')
 
 # Read user mozconfig
 # ==============================================================
@@ -124,20 +126,26 @@ def mozconfig(current_project, mozconfig
 def old_configure_assignments(help):
     return []
 
 @depends('--help')
 def extra_old_configure_args(help):
     return []
 
 @template
-def add_old_configure_assignment(var, value):
-    @depends(old_configure_assignments)
+@advanced
+def add_old_configure_assignment(var, value_func):
+    from mozbuild.configure import DummyFunction
+    assert isinstance(value_func, DummyFunction)
+
+    @depends(old_configure_assignments, value_func)
     @advanced
-    def add_assignment(assignments):
+    def add_assignment(assignments, value):
+        if value is None:
+            return
         if value is True:
             assignments.append('%s=1' % var)
         elif value is False:
             assignments.append('%s=' % var)
         else:
             from mozbuild.shellutil import quote
             assignments.append('%s=%s' % (var, quote(value)))
 
@@ -215,20 +223,20 @@ def virtualenv_python(env_python, build_
         # Windows.
         sys.exit(subprocess.call([python] + sys.argv))
 
     # We are now in the virtualenv
     import distutils.sysconfig
     if not distutils.sysconfig.get_python_lib():
         error('Could not determine python site packages directory')
 
-    set_config('PYTHON', python)
-    add_old_configure_assignment('PYTHON', python)
     return python
 
+set_config('PYTHON', virtualenv_python)
+add_old_configure_assignment('PYTHON', virtualenv_python)
 
 # Inject mozconfig options
 # ==============================================================
 @template
 @advanced
 def command_line_helper():
     # This escapes the sandbox. Don't copy this. This is only here because
     # it is a one off and because the required functionality doesn't need
@@ -498,76 +506,116 @@ def target_variables(target):
         os_arch = 'GNU_kFreeBSD'
     elif target.kernel == 'Darwin' or (target.kernel == 'Linux' and
                                        target.os == 'GNU'):
         os_target = target.kernel
         os_arch = target.kernel
     else:
         os_target = target.os
         os_arch = target.kernel
-    add_old_configure_assignment('OS_TARGET', os_target)
-    set_config('OS_TARGET', os_target)
-    add_old_configure_assignment('OS_ARCH', os_arch)
-    set_config('OS_ARCH', os_arch)
 
     if target.os == 'Darwin' and target.cpu == 'x86':
         os_test = 'i386'
     else:
         os_test = target.raw_cpu
-    add_old_configure_assignment('OS_TEST', os_test)
-    set_config('OS_TEST', os_test)
 
-    add_old_configure_assignment('CPU_ARCH', target.cpu)
-    set_config('CPU_ARCH', target.cpu)
+    return namespace(
+        OS_TARGET=os_target,
+        OS_ARCH=os_arch,
+        OS_TEST=os_test,
+        INTEL_ARCHITECTURE=target.cpu in ('x86', 'x86_64') or None,
+    )
 
-    if target.cpu in ('x86', 'x86_64'):
-        set_config('INTEL_ARCHITECTURE', True)
+set_config('OS_TARGET', delayed_getattr(target_variables, 'OS_TARGET'))
+add_old_configure_assignment('OS_TARGET',
+                             delayed_getattr(target_variables, 'OS_TARGET'))
+set_config('OS_ARCH', delayed_getattr(target_variables, 'OS_ARCH'))
+add_old_configure_assignment('OS_ARCH',
+                             delayed_getattr(target_variables, 'OS_ARCH'))
+set_config('OS_TEST', delayed_getattr(target_variables, 'OS_TEST'))
+add_old_configure_assignment('OS_TEST',
+                             delayed_getattr(target_variables, 'OS_TEST'))
+set_config('CPU_ARCH', delayed_getattr(target, 'cpu'))
+add_old_configure_assignment('CPU_ARCH', delayed_getattr(target, 'cpu'))
+set_config('INTEL_ARCHITECTURE', delayed_getattr(target_variables,
+                                                 'INTEL_ARCHITECTURE'))
+set_config('TARGET_CPU', delayed_getattr(target, 'raw_cpu'))
+set_config('TARGET_OS', delayed_getattr(target, 'raw_os'))
 
-    set_config('TARGET_CPU', target.raw_cpu)
-    set_config('TARGET_OS', target.raw_os)
 
 @depends(host)
 def host_variables(host):
     if host.kernel == 'kFreeBSD':
         os_arch = 'GNU_kFreeBSD'
     else:
         os_arch = host.kernel
-    add_old_configure_assignment('HOST_OS_ARCH', os_arch)
-    set_config('HOST_OS_ARCH', os_arch)
+    return namespace(
+        HOST_OS_ARCH=os_arch,
+    )
+
+set_config('HOST_OS_ARCH', delayed_getattr(host_variables, 'HOST_OS_ARCH'))
+add_old_configure_assignment('HOST_OS_ARCH',
+                             delayed_getattr(host_variables, 'HOST_OS_ARCH'))
 
+@depends(target)
+def target_is_windows(target):
+    if target.kernel == 'WINNT':
+        return True
+
+set_define('_WINDOWS', target_is_windows)
+set_define('WIN32', target_is_windows)
+set_define('XP_WIN', target_is_windows)
+set_define('XP_WIN32', target_is_windows)
+
+@depends(target)
+def target_is_unix(target):
+    if target.kernel != 'WINNT':
+        return True
+
+set_define('XP_UNIX', target_is_unix)
 
 @depends(target)
-def target_platform_defines(target):
-    if target.kernel == 'WINNT':
-        set_define('_WINDOWS', True)
-        set_define('WIN32', True)
-        set_define('XP_WIN', True)
-        set_define('XP_WIN32', True)
-    else:
-        set_define('XP_UNIX', True)
+def target_is_darwin(target):
+    if target.kernel == 'Darwin':
+        return True
+
+set_define('XP_DARWIN', target_is_darwin)
+
+@depends(target)
+def target_is_ios(target):
+    if target.kernel == 'Darwin' and target.os == 'iOS':
+        return True
+
+set_define('XP_IOS', target_is_ios)
 
-    if target.kernel == 'Darwin':
-        set_define('XP_DARWIN', True)
-        if target.os == 'iOS':
-            set_define('XP_IOS', True)
-        elif target.os == 'OSX':
-            set_define('XP_MACOSX', True)
-    elif target.kernel == 'Linux':
-        set_define('XP_LINUX', True)
+@depends(target)
+def target_is_osx(target):
+    if target.kernel == 'Darwin' and target.os == 'OSX':
+        return True
+
+set_define('XP_MACOSX', target_is_osx)
 
+@depends(target)
+def target_is_linux(target):
+    if target.kernel == 'Linux':
+        return True
+
+set_define('XP_LINUX', target_is_linux)
 
 # The application/project to build
 # ==============================================================
 option('--enable-application', nargs=1, env='MOZ_BUILD_APP',
        help='Application to build. Same as --enable-project.')
 
 @depends('--enable-application', '--help')
 def application(app, help):
     if app:
-        imply_option(app.format('--enable-project'))
+        return app
+
+imply_option('--enable-project', application)
 
 
 @depends(check_build_environment, '--help')
 def default_project(build_env, help):
     if build_env.topobjdir.endswith('/js/src'):
         return 'js'
     return 'browser'
 
@@ -580,69 +628,79 @@ option('--with-external-source-dir', env
 @depends('--enable-project', '--with-external-source-dir',
          check_build_environment, '--help')
 def include_project_configure(project, external_source_dir, build_env, help):
     if not project:
         error('--enable-project is required.')
 
     base_dir = build_env.topsrcdir
     if external_source_dir:
-        set_config('EXTERNAL_SOURCE_DIR', external_source_dir[0])
-        add_old_configure_assignment('EXTERNAL_SOURCE_DIR',
-                                     external_source_dir[0])
         base_dir = os.path.join(base_dir, external_source_dir[0])
 
     path = os.path.join(base_dir, project[0], 'moz.configure')
     if not os.path.exists(path):
         error('Cannot find project %s' % project[0])
     return path
 
+@depends('--with-external-source-dir')
+def external_source_dir(value):
+    if value:
+        return value[0]
+
+set_config('EXTERNAL_SOURCE_DIR', external_source_dir)
+add_old_configure_assignment('EXTERNAL_SOURCE_DIR', external_source_dir)
+
+
 @depends(include_project_configure, check_build_environment, '--help')
 def build_project(include_project_configure, build_env, help):
     ret = os.path.dirname(os.path.relpath(include_project_configure,
                                           build_env.topsrcdir))
-    set_config('MOZ_BUILD_APP', ret)
-    set_define('MOZ_BUILD_APP', ret)
-    add_old_configure_assignment('MOZ_BUILD_APP', ret)
     return ret
 
+set_config('MOZ_BUILD_APP', build_project)
+set_define('MOZ_BUILD_APP', build_project)
+add_old_configure_assignment('MOZ_BUILD_APP', build_project)
+
 
 # set RELEASE_BUILD and NIGHTLY_BUILD variables depending on the cycle we're in
 # The logic works like this:
 # - if we have "a1" in GRE_MILESTONE, we're building Nightly (define NIGHTLY_BUILD)
 # - otherwise, if we have "a" in GRE_MILESTONE, we're building Nightly or Aurora
 # - otherwise, we're building Release/Beta (define RELEASE_BUILD)
 @depends(check_build_environment)
 @advanced
 def milestone(build_env):
     milestone_path = os.path.join(build_env.topsrcdir,
                                   'config',
                                   'milestone.txt')
     with open(milestone_path, 'r') as fh:
         milestone = fh.read().splitlines()[-1]
 
-    set_config('GRE_MILESTONE', milestone)
-
-    is_nightly = is_release = False
+    is_nightly = is_release = None
 
     if 'a1' in milestone:
-        set_config('NIGHTLY_BUILD', True)
-        set_define('NIGHTLY_BUILD', True)
-        add_old_configure_assignment('NIGHTLY_BUILD', True)
         is_nightly = True
     elif 'a' not in milestone:
-        set_config('RELEASE_BUILD', True)
-        set_define('RELEASE_BUILD', True)
-        add_old_configure_assignment('RELEASE_BUILD', True)
         is_release = True
 
     return namespace(version=milestone,
                      is_nightly=is_nightly,
                      is_release=is_release)
 
+set_config('GRE_MILESTONE', delayed_getattr(milestone, 'version'))
+set_config('NIGHTLY_BUILD', delayed_getattr(milestone, 'is_nightly'))
+set_define('NIGHTLY_BUILD', delayed_getattr(milestone, 'is_nightly'))
+add_old_configure_assignment('NIGHTLY_BUILD',
+                             delayed_getattr(milestone, 'is_nightly'))
+set_config('RELEASE_BUILD', delayed_getattr(milestone, 'is_release'))
+set_define('RELEASE_BUILD', delayed_getattr(milestone, 'is_release'))
+add_old_configure_assignment('RELEASE_BUILD',
+                             delayed_getattr(milestone, 'is_release'))
+
+
 # This is temporary until js/src/configure and configure are merged.
 # Use instead of option() in js/moz.configure
 @template
 def js_option(*args, **kwargs):
     opt = option(*args, **kwargs)
 
     @depends(opt.option, build_project)
     def js_option(value, build_project):
@@ -661,9 +719,11 @@ def gonkdir(help):
 
 include(include_project_configure)
 
 # By now, gonkdir is either the dummy function above or a real function
 # depending on --with-gonk from b2g/moz.configure.
 @depends(gonkdir)
 def gonkdir_for_old_configure(value):
     if value:
-        add_old_configure_assignment('gonkdir', value)
+        return value
+
+add_old_configure_assignment('gonkdir', gonkdir_for_old_configure)
--- a/build/moz.configure/old.configure
+++ b/build/moz.configure/old.configure
@@ -51,19 +51,20 @@ def autoconf(mozconfig, autoconf):
                     'autoconf213'))
 
     if not autoconf:
         error('Could not find autoconf 2.13')
 
     if not os.path.exists(autoconf):
         error('Could not find autoconf 2.13 at %s' % (autoconf,))
 
-    set_config('AUTOCONF', autoconf)
     return autoconf
 
+set_config('AUTOCONF', autoconf)
+
 
 # See comment in mozconfig_options() from build/moz.configure/init.configure
 @template
 @advanced
 def check_mozconfig_variables():
     # This escapes the sandbox. Don't copy this. This is only here because it
     # is a one off until old-configure is gone.
     all_options = depends.__self__._options.itervalues()
@@ -366,17 +367,16 @@ def old_configure_options(*options):
     '--enable-incomplete-external-linkage',
 )
 @advanced
 def old_configure(prepare_configure, extra_old_configure_args, all_options,
                   *options):
     import os
     import subprocess
     import sys
-    import types
     from mozbuild.shellutil import quote
 
     cmd = prepare_configure
 
     # old-configure only supports the options listed in @old_configure_options
     # so we don't need to pass it every single option we've been passed. Only
     # the ones that are not supported by python configure need to.
     cmd += [
@@ -412,17 +412,38 @@ def old_configure(prepare_configure, ext
     for flag in raw_config['flags']:
         if flag not in all_options:
             error('Missing option in `@old_configure_options` in %s: %s'
                   % (__file__, flag))
 
     # If the code execution above fails, we want to keep the file around for
     # debugging.
     os.remove('config.data')
+    return raw_config
 
-    config = {}
+
+# set_config is only available in the global namespace, not directly in
+# @depends functions, but we do need to enumerate the result of
+# old_configure, so we cheat.
+@template
+def set_old_configure_config(name, value):
+    set_config(name, value)
+
+# Same as set_old_configure_config, but for set_define.
+@template
+def set_old_configure_define(name, value):
+    set_define(name, value)
+
+
+@depends(old_configure)
+@advanced
+def post_old_configure(raw_config):
+    import types
+
     for k, v in raw_config['substs']:
-        set_config(k[1:-1], v[1:-1] if isinstance(v, types.StringTypes) else v)
+        set_old_configure_config(
+            k[1:-1], v[1:-1] if isinstance(v, types.StringTypes) else v)
 
     for k, v in dict(raw_config['defines']).iteritems():
-        set_define(k[1:-1], v[1:-1])
+        set_old_configure_define(k[1:-1], v[1:-1])
 
-    set_config('non_global_defines', raw_config['non_global_defines'])
+    set_old_configure_config('non_global_defines',
+                             raw_config['non_global_defines'])
new file mode 100644
--- /dev/null
+++ b/build/moz.configure/toolchain.configure
@@ -0,0 +1,61 @@
+# -*- 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/.
+
+# yasm detection
+# ==============================================================
+yasm = check_prog('YASM', ['yasm'], allow_missing=True)
+
+@depends(yasm)
+@checking('yasm version')
+@advanced
+def yasm_version(yasm):
+    if yasm:
+        import subprocess
+        try:
+            version = Version(subprocess.check_output(
+                [yasm, '--version']
+            ).splitlines()[0].split()[1])
+            return version
+        except subprocess.CalledProcessError as e:
+            error('Failed to get yasm version: %s' % e.message)
+
+# Until we move all the yasm consumers out of old-configure.
+# bug 1257904
+add_old_configure_assignment('_YASM_MAJOR_VERSION',
+                             delayed_getattr(yasm_version, 'major'))
+add_old_configure_assignment('_YASM_MINOR_VERSION',
+                             delayed_getattr(yasm_version, 'minor'))
+
+@depends(yasm, target)
+def yasm_asflags(yasm, target):
+    if yasm:
+        asflags = {
+            ('OSX', 'x86'): '-f macho32',
+            ('OSX', 'x86_64'): '-f macho64',
+            ('WINNT', 'x86'): '-f win32',
+            ('WINNT', 'x86_64'): '-f x64',
+        }.get((target.os, target.cpu), None)
+        if asflags is None:
+            # We're assuming every x86 platform we support that's
+            # not Windows or Mac is ELF.
+            if target.cpu == 'x86':
+                asflags = '-f elf32'
+            elif target.cpu == 'x86_64':
+                asflags = '-f elf64'
+        if asflags:
+            asflags += ' -rnasm -pnasm'
+        return asflags
+
+set_config('YASM_ASFLAGS', yasm_asflags)
+
+@depends(yasm_asflags)
+def have_yasm(value):
+    if value:
+        return True
+
+set_config('HAVE_YASM', have_yasm)
+# Until the YASM variable is not necessary in old-configure.
+add_old_configure_assignment('YASM', have_yasm)
--- a/build/moz.configure/util.configure
+++ b/build/moz.configure/util.configure
@@ -56,36 +56,16 @@ def find_program(file):
         return os.path.abspath(file) if os.path.isfile(file) else None
     from which import which, WhichError
     try:
         return normsep(which(file))
     except WhichError:
         return None
 
 
-@depends('--help')
-def _defines(help):
-    ret = {}
-    set_config('DEFINES', ret)
-    return ret
-
-
-@template
-def set_define(name, value):
-    @depends(_defines)
-    @advanced
-    def _add_define(defines):
-        from mozbuild.configure import ConfigureError
-        if name in defines:
-            raise ConfigureError("'%s' is already defined" % name)
-        defines[name] = value
-
-del _defines
-
-
 @template
 def unique_list(l):
     result = []
     for i in l:
         if l not in result:
             result.append(i)
     return result
 
@@ -121,8 +101,26 @@ def deprecated_option(*args, **kwargs):
 
 
 # from mozbuild.util import ReadOnlyNamespace as namespace
 @template
 @advanced
 def namespace(**kwargs):
     from mozbuild.util import ReadOnlyNamespace
     return ReadOnlyNamespace(**kwargs)
+
+
+# Some @depends function return namespaces, and one could want to use one
+# specific attribute from such a namespace as a "value" given to functions
+# such as `set_config`. But those functions do not take immediate values.
+# The `delayed_getattr` function allows access to attributes from the result
+# of a @depends function in a non-immediate manner.
+#   @depends('--option')
+#   def option(value)
+#       return namespace(foo=value)
+#   set_config('FOO', delayed_getattr(option, 'foo')
+@template
+def delayed_getattr(func, key):
+    @depends(func)
+    @advanced
+    def result(value):
+        return getattr(value, key)
+    return result
new file mode 100644
--- /dev/null
+++ b/dom/animation/ComputedTiming.h
@@ -0,0 +1,74 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_ComputedTiming_h
+#define mozilla_ComputedTiming_h
+
+#include "mozilla/dom/Nullable.h"
+#include "mozilla/StickyTimeDuration.h"
+
+// X11 has a #define for None
+#ifdef None
+#undef None
+#endif
+#include "mozilla/dom/AnimationEffectReadOnlyBinding.h" // FillMode
+
+namespace mozilla {
+
+/**
+ * Stores the results of calculating the timing properties of an animation
+ * at a given sample time.
+ */
+struct ComputedTiming
+{
+  // The total duration of the animation including all iterations.
+  // Will equal StickyTimeDuration::Forever() if the animation repeats
+  // indefinitely.
+  StickyTimeDuration  mActiveDuration;
+  // The effect end time in local time (i.e. an offset from the effect's
+  // start time). Will equal StickyTimeDuration::Forever() if the animation
+  // plays indefinitely.
+  StickyTimeDuration  mEndTime;
+  // Progress towards the end of the current iteration. If the effect is
+  // being sampled backwards, this will go from 1.0 to 0.0.
+  // Will be null if the animation is neither animating nor
+  // filling at the sampled time.
+  Nullable<double>    mProgress;
+  // Zero-based iteration index (meaningless if mProgress is null).
+  uint64_t            mCurrentIteration = 0;
+  // Unlike TimingParams::mIterations, this value is
+  // guaranteed to be in the range [0, Infinity].
+  double              mIterations = 1.0;
+  double              mIterationStart = 0.0;
+  StickyTimeDuration  mDuration;
+
+  // This is the computed fill mode so it is never auto
+  dom::FillMode       mFill = dom::FillMode::None;
+  bool FillsForwards() const {
+    MOZ_ASSERT(mFill != dom::FillMode::Auto,
+               "mFill should not be Auto in ComputedTiming.");
+    return mFill == dom::FillMode::Both ||
+           mFill == dom::FillMode::Forwards;
+  }
+  bool FillsBackwards() const {
+    MOZ_ASSERT(mFill != dom::FillMode::Auto,
+               "mFill should not be Auto in ComputedTiming.");
+    return mFill == dom::FillMode::Both ||
+           mFill == dom::FillMode::Backwards;
+  }
+
+  enum class AnimationPhase {
+    Null,   // Not sampled (null sample time)
+    Before, // Sampled prior to the start of the active interval
+    Active, // Sampled within the active interval
+    After   // Sampled after (or at) the end of the active interval
+  };
+  AnimationPhase      mPhase = AnimationPhase::Null;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ComputedTiming_h
--- a/dom/animation/KeyframeEffect.cpp
+++ b/dom/animation/KeyframeEffect.cpp
@@ -3,30 +3,28 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/KeyframeEffect.h"
 
 #include "mozilla/dom/AnimatableBinding.h"
 #include "mozilla/dom/KeyframeEffectBinding.h"
-#include "mozilla/dom/PropertyIndexedKeyframesBinding.h"
 #include "mozilla/AnimationUtils.h"
 #include "mozilla/EffectCompositor.h"
 #include "mozilla/FloatingPoint.h"
 #include "mozilla/LookAndFeel.h" // For LookAndFeel::GetInt
+#include "mozilla/KeyframeUtils.h"
 #include "mozilla/StyleAnimationValue.h"
 #include "Layers.h" // For Layer
-#include "nsCSSParser.h"
+#include "nsComputedDOMStyle.h" // nsComputedDOMStyle::GetStyleContextForElement
 #include "nsCSSPropertySet.h"
 #include "nsCSSProps.h" // For nsCSSProps::PropHasFlags
-#include "nsCSSPseudoElements.h"
-#include "nsCSSValue.h"
+#include "nsCSSPseudoElements.h" // For CSSPseudoElementType
 #include "nsDOMMutationObserver.h" // For nsAutoAnimationMutationBatch
-#include "nsStyleUtil.h"
 #include <algorithm> // For std::max
 
 namespace mozilla {
 
 // Helper functions for generating a ComputedTimingProperties dictionary
 static void
 GetComputedTimingDictionary(const ComputedTiming& aComputedTiming,
                             const Nullable<TimeDuration>& aLocalTime,
@@ -703,23 +701,42 @@ KeyframeEffectReadOnly::ConstructKeyfram
     pseudoType = target.GetAsCSSPseudoElement().GetType();
   }
 
   if (!targetElement->GetComposedDoc()) {
     aRv.Throw(NS_ERROR_DOM_ANIM_TARGET_NOT_IN_DOC_ERR);
     return nullptr;
   }
 
-  InfallibleTArray<AnimationProperty> animationProperties;
-  BuildAnimationPropertyList(aGlobal.Context(), targetElement, pseudoType,
-                             aFrames, animationProperties, aRv);
-
+  nsTArray<Keyframe> keyframes =
+    KeyframeUtils::GetKeyframesFromObject(aGlobal.Context(), aFrames, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
+  KeyframeUtils::ApplyDistributeSpacing(keyframes);
+
+  RefPtr<nsStyleContext> styleContext;
+  nsIPresShell* shell = doc->GetShell();
+  if (shell && targetElement) {
+    nsIAtom* pseudo =
+      pseudoType < CSSPseudoElementType::Count ?
+      nsCSSPseudoElements::GetPseudoAtom(pseudoType) : nullptr;
+    styleContext =
+      nsComputedDOMStyle::GetStyleContextForElement(targetElement, pseudo,
+                                                    shell);
+  }
+
+  nsTArray<AnimationProperty> animationProperties;
+  if (styleContext) {
+    animationProperties =
+      KeyframeUtils::GetAnimationPropertiesFromKeyframes(styleContext,
+                                                         targetElement,
+                                                         pseudoType,
+                                                         keyframes);
+  }
 
   RefPtr<KeyframeEffectType> effect =
     new KeyframeEffectType(targetElement->OwnerDoc(), targetElement,
                            pseudoType, timingParams);
   effect->mProperties = Move(animationProperties);
   return effect.forget();
 }
 
@@ -801,17 +818,17 @@ enum class ValuePosition
   First,  // value at 0 used for reverse filling
   Left,   // value coming in to a given offset
   Right,  // value coming out from a given offset
   Last    // value at 1 used for forward filling
 };
 
 /**
  * A single value in a keyframe animation, used by GetFrames to produce a
- * minimal set of Keyframe objects.
+ * minimal set of keyframe objects.
  */
 struct OrderedKeyframeValueEntry : KeyframeValue
 {
   float mOffset;
   const Maybe<ComputedTimingFunction>* mTimingFunction;
   ValuePosition mPosition;
 
   bool SameKeyframe(const OrderedKeyframeValueEntry& aOther) const
@@ -863,911 +880,16 @@ struct OrderedKeyframeValueEntry : Keyfr
 
       // Last, by property IDL name.
       return nsCSSProps::PropertyIDLNameSortPosition(aLhs.mProperty) <
              nsCSSProps::PropertyIDLNameSortPosition(aRhs.mProperty);
     }
   };
 };
 
-/**
- * Data for a segment in a keyframe animation of a given property
- * whose value is a StyleAnimationValue.
- *
- * KeyframeValueEntry is used in BuildAnimationPropertyListFromKeyframeSequence
- * to gather data for each individual segment described by an author-supplied
- * an IDL sequence<Keyframe> value so that they can be parsed into mProperties.
- */
-struct KeyframeValueEntry : KeyframeValue
-{
-  float mOffset;
-  Maybe<ComputedTimingFunction> mTimingFunction;
-
-  struct PropertyOffsetComparator
-  {
-    static bool Equals(const KeyframeValueEntry& aLhs,
-                       const KeyframeValueEntry& aRhs)
-    {
-      return aLhs.mProperty == aRhs.mProperty &&
-             aLhs.mOffset == aRhs.mOffset;
-    }
-    static bool LessThan(const KeyframeValueEntry& aLhs,
-                         const KeyframeValueEntry& aRhs)
-    {
-      // First, sort by property IDL name.
-      int32_t order = nsCSSProps::PropertyIDLNameSortPosition(aLhs.mProperty) -
-                      nsCSSProps::PropertyIDLNameSortPosition(aRhs.mProperty);
-      if (order != 0) {
-        return order < 0;
-      }
-
-      // Then, by offset.
-      return aLhs.mOffset < aRhs.mOffset;
-    }
-  };
-};
-
-/**
- * A property-values pair obtained from the open-ended properties
- * discovered on a Keyframe or PropertyIndexedKeyframes object.
- *
- * Single values (as required by Keyframe, and as also supported
- * on PropertyIndexedKeyframes) are stored as the only element in
- * mValues.
- */
-struct PropertyValuesPair
-{
-  nsCSSProperty mProperty;
-  nsTArray<nsString> mValues;
-
-  class PropertyPriorityComparator
-  {
-  public:
-    PropertyPriorityComparator()
-      : mSubpropertyCountInitialized(false) {}
-
-    bool Equals(const PropertyValuesPair& aLhs,
-                const PropertyValuesPair& aRhs) const
-    {
-      return aLhs.mProperty == aRhs.mProperty;
-    }
-
-    bool LessThan(const PropertyValuesPair& aLhs,
-                  const PropertyValuesPair& aRhs) const
-    {
-      bool isShorthandLhs = nsCSSProps::IsShorthand(aLhs.mProperty);
-      bool isShorthandRhs = nsCSSProps::IsShorthand(aRhs.mProperty);
-
-      if (isShorthandLhs) {
-        if (isShorthandRhs) {
-          // First, sort shorthands by the number of longhands they have.
-          uint32_t subpropCountLhs = SubpropertyCount(aLhs.mProperty);
-          uint32_t subpropCountRhs = SubpropertyCount(aRhs.mProperty);
-          if (subpropCountLhs != subpropCountRhs) {
-            return subpropCountLhs < subpropCountRhs;
-          }
-          // Otherwise, sort by IDL name below.
-        } else {
-          // Put longhands before shorthands.
-          return false;
-        }
-      } else {
-        if (isShorthandRhs) {
-          // Put longhands before shorthands.
-          return true;
-        }
-      }
-      // For two longhand properties, or two shorthand with the same number
-      // of longhand components, sort by IDL name.
-      return nsCSSProps::PropertyIDLNameSortPosition(aLhs.mProperty) <
-             nsCSSProps::PropertyIDLNameSortPosition(aRhs.mProperty);
-    }
-
-    uint32_t SubpropertyCount(nsCSSProperty aProperty) const
-    {
-      if (!mSubpropertyCountInitialized) {
-        PodZero(&mSubpropertyCount);
-        mSubpropertyCountInitialized = true;
-      }
-      if (mSubpropertyCount[aProperty] == 0) {
-        uint32_t count = 0;
-        CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(
-            p, aProperty, nsCSSProps::eEnabledForAllContent) {
-          ++count;
-        }
-        mSubpropertyCount[aProperty] = count;
-      }
-      return mSubpropertyCount[aProperty];
-    }
-
-  private:
-    // Cache of shorthand subproperty counts.
-    mutable RangedArray<
-      uint32_t,
-      eCSSProperty_COUNT_no_shorthands,
-      eCSSProperty_COUNT - eCSSProperty_COUNT_no_shorthands> mSubpropertyCount;
-    mutable bool mSubpropertyCountInitialized;
-  };
-};
-
-/**
- * The result of parsing a JS object as a Keyframe dictionary
- * and getting its property-value pairs from its open-ended
- * properties.
- */
-struct OffsetIndexedKeyframe
-{
-  binding_detail::FastKeyframe mKeyframeDict;
-  nsTArray<PropertyValuesPair> mPropertyValuePairs;
-};
-
-/**
- * An additional property (for a property-values pair) found on a Keyframe
- * or PropertyIndexedKeyframes object.
- */
-struct AdditionalProperty
-{
-  nsCSSProperty mProperty;
-  size_t mJsidIndex;        // Index into |ids| in GetPropertyValuesPairs.
-
-  struct PropertyComparator
-  {
-    bool Equals(const AdditionalProperty& aLhs,
-                const AdditionalProperty& aRhs) const
-    {
-      return aLhs.mProperty == aRhs.mProperty;
-    }
-    bool LessThan(const AdditionalProperty& aLhs,
-                  const AdditionalProperty& aRhs) const
-    {
-      return nsCSSProps::PropertyIDLNameSortPosition(aLhs.mProperty) <
-             nsCSSProps::PropertyIDLNameSortPosition(aRhs.mProperty);
-    }
-  };
-};
-
-/**
- * Converts aValue to DOMString and appends it to aValues.
- */
-static bool
-AppendValueAsString(JSContext* aCx,
-                    nsTArray<nsString>& aValues,
-                    JS::Handle<JS::Value> aValue)
-{
-  return ConvertJSValueToString(aCx, aValue, eStringify, eStringify,
-                                *aValues.AppendElement());
-}
-
-// For the aAllowList parameter of AppendStringOrStringSequence and
-// GetPropertyValuesPairs.
-enum class ListAllowance { eDisallow, eAllow };
-
-/**
- * Converts aValue to DOMString, if aAllowLists is eDisallow, or
- * to (DOMString or sequence<DOMString>) if aAllowLists is aAllow.
- * The resulting strings are appended to aValues.
- */
-static bool
-AppendStringOrStringSequenceToArray(JSContext* aCx,
-                                    JS::Handle<JS::Value> aValue,
-                                    ListAllowance aAllowLists,
-                                    nsTArray<nsString>& aValues)
-{
-  if (aAllowLists == ListAllowance::eAllow && aValue.isObject()) {
-    // The value is an object, and we want to allow lists; convert
-    // aValue to (DOMString or sequence<DOMString>).
-    JS::ForOfIterator iter(aCx);
-    if (!iter.init(aValue, JS::ForOfIterator::AllowNonIterable)) {
-      return false;
-    }
-    if (iter.valueIsIterable()) {
-      // If the object is iterable, convert it to sequence<DOMString>.
-      JS::Rooted<JS::Value> element(aCx);
-      for (;;) {
-        bool done;
-        if (!iter.next(&element, &done)) {
-          return false;
-        }
-        if (done) {
-          break;
-        }
-        if (!AppendValueAsString(aCx, aValues, element)) {
-          return false;
-        }
-      }
-      return true;
-    }
-  }
-
-  // Either the object is not iterable, or aAllowLists doesn't want
-  // a list; convert it to DOMString.
-  if (!AppendValueAsString(aCx, aValues, aValue)) {
-    return false;
-  }
-
-  return true;
-}
-
-/**
- * Reads the property-values pairs from the specified JS object.
- *
- * @param aObject The JS object to look at.
- * @param aAllowLists If eAllow, values will be converted to
- *   (DOMString or sequence<DOMString); if eDisallow, values
- *   will be converted to DOMString.
- * @param aResult The array into which the enumerated property-values
- *   pairs will be stored.
- * @return false on failure or JS exception thrown while interacting
- *   with aObject; true otherwise.
- */
-static bool
-GetPropertyValuesPairs(JSContext* aCx,
-                       JS::Handle<JSObject*> aObject,
-                       ListAllowance aAllowLists,
-                       nsTArray<PropertyValuesPair>& aResult)
-{
-  nsTArray<AdditionalProperty> properties;
-
-  // Iterate over all the properties on aObject and append an
-  // entry to properties for them.
-  //
-  // We don't compare the jsids that we encounter with those for
-  // the explicit dictionary members, since we know that none
-  // of the CSS property IDL names clash with them.
-  JS::Rooted<JS::IdVector> ids(aCx, JS::IdVector(aCx));
-  if (!JS_Enumerate(aCx, aObject, &ids)) {
-    return false;
-  }
-  for (size_t i = 0, n = ids.length(); i < n; i++) {
-    nsAutoJSString propName;
-    if (!propName.init(aCx, ids[i])) {
-      return false;
-    }
-    nsCSSProperty property =
-      nsCSSProps::LookupPropertyByIDLName(propName,
-                                          nsCSSProps::eEnabledForAllContent);
-    if (property != eCSSProperty_UNKNOWN &&
-        (nsCSSProps::IsShorthand(property) ||
-         nsCSSProps::kAnimTypeTable[property] != eStyleAnimType_None)) {
-      // Only need to check for longhands being animatable, as the
-      // StyleAnimationValue::ComputeValues calls later on will check for
-      // a shorthand's components being animatable.
-      AdditionalProperty* p = properties.AppendElement();
-      p->mProperty = property;
-      p->mJsidIndex = i;
-    }
-  }
-
-  // Sort the entries by IDL name and then get each value and
-  // convert it either to a DOMString or to a
-  // (DOMString or sequence<DOMString>), depending on aAllowLists,
-  // and build up aResult.
-  properties.Sort(AdditionalProperty::PropertyComparator());
-
-  for (AdditionalProperty& p : properties) {
-    JS::Rooted<JS::Value> value(aCx);
-    if (!JS_GetPropertyById(aCx, aObject, ids[p.mJsidIndex], &value)) {
-      return false;
-    }
-    PropertyValuesPair* pair = aResult.AppendElement();
-    pair->mProperty = p.mProperty;
-    if (!AppendStringOrStringSequenceToArray(aCx, value, aAllowLists,
-                                             pair->mValues)) {
-      return false;
-    }
-  }
-
-  return true;
-}
-
-/**
- * Converts a JS object wrapped by the given JS::ForIfIterator to an
- * IDL sequence<Keyframe> and stores the resulting OffsetIndexedKeyframe
- * objects in aResult.
- */
-static bool
-ConvertKeyframeSequence(JSContext* aCx,
-                        JS::ForOfIterator& aIterator,
-                        nsTArray<OffsetIndexedKeyframe>& aResult)
-{
-  JS::Rooted<JS::Value> value(aCx);
-  for (;;) {
-    bool done;
-    if (!aIterator.next(&value, &done)) {
-      return false;
-    }
-    if (done) {
-      break;
-    }
-    // Each value found when iterating the object must be an object
-    // or null/undefined (which gets treated as a default {} dictionary
-    // value).
-    if (!value.isObject() && !value.isNullOrUndefined()) {
-      ThrowErrorMessage(aCx, MSG_NOT_OBJECT,
-                        "Element of sequence<Keyframes> argument");
-      return false;
-    }
-    // Convert the JS value into a Keyframe dictionary value.
-    OffsetIndexedKeyframe* keyframe = aResult.AppendElement();
-    if (!keyframe->mKeyframeDict.Init(
-          aCx, value, "Element of sequence<Keyframes> argument")) {
-      return false;
-    }
-    // Look for additional property-values pairs on the object.
-    if (value.isObject()) {
-      JS::Rooted<JSObject*> object(aCx, &value.toObject());
-      if (!GetPropertyValuesPairs(aCx, object,
-                                  ListAllowance::eDisallow,
-                                  keyframe->mPropertyValuePairs)) {
-        return false;
-      }
-    }
-  }
-  return true;
-}
-
-/**
- * Checks that the given keyframes are loosely ordered (each keyframe's
- * offset that is not null is greater than or equal to the previous
- * non-null offset) and that all values are within the range [0.0, 1.0].
- *
- * @return true if the keyframes' offsets are correctly ordered and
- *   within range; false otherwise.
- */
-static bool
-HasValidOffsets(const nsTArray<OffsetIndexedKeyframe>& aKeyframes)
-{
-  double offset = 0.0;
-  for (const OffsetIndexedKeyframe& keyframe : aKeyframes) {
-    if (!keyframe.mKeyframeDict.mOffset.IsNull()) {
-      double thisOffset = keyframe.mKeyframeDict.mOffset.Value();
-      if (thisOffset < offset || thisOffset > 1.0f) {
-        return false;
-      }
-      offset = thisOffset;
-    }
-  }
-  return true;
-}
-
-/**
- * Fills in any null offsets for the given keyframes by applying the
- * "distribute" spacing algorithm.
- *
- * http://w3c.github.io/web-animations/#distribute-keyframe-spacing-mode
- */
-static void
-ApplyDistributeSpacing(nsTArray<OffsetIndexedKeyframe>& aKeyframes)
-{
-  // If the first or last keyframes have an unspecified offset,
-  // fill them in with 0% and 100%.  If there is only a single keyframe,
-  // then it gets 100%.
-  if (aKeyframes.LastElement().mKeyframeDict.mOffset.IsNull()) {
-    aKeyframes.LastElement().mKeyframeDict.mOffset.SetValue(1.0);
-  }
-  if (aKeyframes[0].mKeyframeDict.mOffset.IsNull()) {
-    aKeyframes[0].mKeyframeDict.mOffset.SetValue(0.0);
-  }
-
-  // Fill in remaining missing offsets.
-  size_t i = 0;
-  while (i < aKeyframes.Length() - 1) {
-    MOZ_ASSERT(!aKeyframes[i].mKeyframeDict.mOffset.IsNull());
-    double start = aKeyframes[i].mKeyframeDict.mOffset.Value();
-    size_t j = i + 1;
-    while (aKeyframes[j].mKeyframeDict.mOffset.IsNull()) {
-      ++j;
-    }
-    double end = aKeyframes[j].mKeyframeDict.mOffset.Value();
-    size_t n = j - i;
-    for (size_t k = 1; k < n; ++k) {
-      double offset = start + double(k) / n * (end - start);
-      aKeyframes[i + k].mKeyframeDict.mOffset.SetValue(offset);
-    }
-    i = j;
-  }
-}
-
-/**
- * Splits out each property's keyframe animation segment information
- * from the OffsetIndexedKeyframe objects into an array of KeyframeValueEntry.
- *
- * The easing string value in OffsetIndexedKeyframe objects is parsed
- * into a ComputedTimingFunction value in the corresponding KeyframeValueEntry
- * objects.
- *
- * @param aTarget The target of the animation.
- * @param aPseudoType The pseudo type of the target if it is a pseudo element.
- * @param aKeyframes The keyframes to read.
- * @param aResult The array to append the resulting KeyframeValueEntry
- *   objects to.
- */
-static void
-GenerateValueEntries(Element* aTarget,
-                     CSSPseudoElementType aPseudoType,
-                     nsTArray<OffsetIndexedKeyframe>& aKeyframes,
-                     nsTArray<KeyframeValueEntry>& aResult,
-                     ErrorResult& aRv)
-{
-  nsCSSPropertySet properties;              // All properties encountered.
-  nsCSSPropertySet propertiesWithFromValue; // Those with a defined 0% value.
-  nsCSSPropertySet propertiesWithToValue;   // Those with a defined 100% value.
-
-  for (OffsetIndexedKeyframe& keyframe : aKeyframes) {
-    Maybe<ComputedTimingFunction> easing =
-      TimingParams::ParseEasing(keyframe.mKeyframeDict.mEasing,
-                                aTarget->OwnerDoc(), aRv);
-    if (aRv.Failed()) {
-      return;
-    }
-    float offset = float(keyframe.mKeyframeDict.mOffset.Value());
-    // We ignore keyframe.mKeyframeDict.mComposite since we don't support
-    // composite modes on keyframes yet.
-
-    // keyframe.mPropertyValuePairs is currently sorted by CSS property IDL
-    // name, since that was the order we read the properties from the JS
-    // object.  Re-sort the list so that longhand properties appear before
-    // shorthands, and with shorthands all appearing in increasing order of
-    // number of components.  For two longhand properties, or two shorthands
-    // with the same number of components, sort by IDL name.
-    //
-    // Example orderings that result from this:
-    //
-    //   margin-left, margin
-    //
-    // and:
-    //
-    //   border-top-color, border-color, border-top, border
-    //
-    // This allows us to prioritize values specified by longhands (or smaller
-    // shorthand subsets) when longhands and shorthands are both specified
-    // on the one keyframe.
-    keyframe.mPropertyValuePairs.Sort(
-        PropertyValuesPair::PropertyPriorityComparator());
-
-    nsCSSPropertySet propertiesOnThisKeyframe;
-    for (const PropertyValuesPair& pair : keyframe.mPropertyValuePairs) {
-      MOZ_ASSERT(pair.mValues.Length() == 1,
-                 "ConvertKeyframeSequence should have parsed single "
-                 "DOMString values from the property-values pairs");
-      // Parse the property's string value and produce a KeyframeValueEntry (or
-      // more than one, for shorthands) for it.
-      nsTArray<PropertyStyleAnimationValuePair> values;
-      if (StyleAnimationValue::ComputeValues(pair.mProperty,
-                                             nsCSSProps::eEnabledForAllContent,
-                                             aTarget,
-                                             aPseudoType,
-                                             pair.mValues[0],
-                                             /* aUseSVGMode */ false,
-                                             values)) {
-        for (auto& value : values) {
-          // If we already got a value for this property on the keyframe,
-          // skip this one.
-          if (propertiesOnThisKeyframe.HasProperty(value.mProperty)) {
-            continue;
-          }
-
-          KeyframeValueEntry* entry = aResult.AppendElement();
-          entry->mOffset = offset;
-          entry->mProperty = value.mProperty;
-          entry->mValue = value.mValue;
-          entry->mTimingFunction = easing;
-
-          if (offset == 0.0) {
-            propertiesWithFromValue.AddProperty(value.mProperty);
-          } else if (offset == 1.0) {
-            propertiesWithToValue.AddProperty(value.mProperty);
-          }
-          propertiesOnThisKeyframe.AddProperty(value.mProperty);
-          properties.AddProperty(value.mProperty);
-        }
-      }
-    }
-  }
-
-  // We don't support additive segments and so can't support missing properties
-  // using their underlying value in 0% and 100% keyframes.  Throw an exception
-  // until we do support this.
-  if (!propertiesWithFromValue.Equals(properties) ||
-      !propertiesWithToValue.Equals(properties)) {
-    aRv.Throw(NS_ERROR_DOM_ANIM_MISSING_PROPS_ERR);
-    return;
-  }
-}
-
-/**
- * Builds an array of AnimationProperty objects to represent the keyframe
- * animation segments in aEntries.
- */
-static void
-BuildSegmentsFromValueEntries(nsTArray<KeyframeValueEntry>& aEntries,
-                              nsTArray<AnimationProperty>& aResult)
-{
-  if (aEntries.IsEmpty()) {
-    return;
-  }
-
-  // Sort the KeyframeValueEntry objects so that all entries for a given
-  // property are together, and the entries are sorted by offset otherwise.
-  std::stable_sort(aEntries.begin(), aEntries.end(),
-                   &KeyframeValueEntry::PropertyOffsetComparator::LessThan);
-
-  MOZ_ASSERT(aEntries[0].mOffset == 0.0f);
-  MOZ_ASSERT(aEntries.LastElement().mOffset == 1.0f);
-
-  // For a given index i, we want to generate a segment from aEntries[i]
-  // to aEntries[j], if:
-  //
-  //   * j > i,
-  //   * aEntries[i + 1]'s offset/property is different from aEntries[i]'s, and
-  //   * aEntries[j - 1]'s offset/property is different from aEntries[j]'s.
-  //
-  // That will eliminate runs of same offset/property values where there's no
-  // point generating zero length segments in the middle of the animation.
-  //
-  // Additionally we need to generate a zero length segment at offset 0 and at
-  // offset 1, if we have multiple values for a given property at that offset,
-  // since we need to retain the very first and very last value so they can
-  // be used for reverse and forward filling.
-
-  nsCSSProperty lastProperty = eCSSProperty_UNKNOWN;
-  AnimationProperty* animationProperty = nullptr;
-
-  size_t i = 0, n = aEntries.Length();
-
-  while (i + 1 < n) {
-    // Starting from i, determine the next [i, j] interval from which to
-    // generate a segment.
-    size_t j;
-    if (aEntries[i].mOffset == 0.0f && aEntries[i + 1].mOffset == 0.0f) {
-      // We need to generate an initial zero-length segment.
-      MOZ_ASSERT(aEntries[i].mProperty == aEntries[i + 1].mProperty);
-      j = i + 1;
-      while (aEntries[j + 1].mOffset == 0.0f) {
-        MOZ_ASSERT(aEntries[j].mProperty == aEntries[j + 1].mProperty);
-        ++j;
-      }
-    } else if (aEntries[i].mOffset == 1.0f) {
-      if (aEntries[i + 1].mOffset == 1.0f) {
-        // We need to generate a final zero-length segment.
-        MOZ_ASSERT(aEntries[i].mProperty == aEntries[i].mProperty);
-        j = i + 1;
-        while (j + 1 < n && aEntries[j + 1].mOffset == 1.0f) {
-          MOZ_ASSERT(aEntries[j].mProperty == aEntries[j + 1].mProperty);
-          ++j;
-        }
-      } else {
-        // New property.
-        MOZ_ASSERT(aEntries[i + 1].mOffset == 0.0f);
-        MOZ_ASSERT(aEntries[i].mProperty != aEntries[i + 1].mProperty);
-        ++i;
-        continue;
-      }
-    } else {
-      while (aEntries[i].mOffset == aEntries[i + 1].mOffset &&
-             aEntries[i].mProperty == aEntries[i + 1].mProperty) {
-        ++i;
-      }
-      j = i + 1;
-    }
-
-    // If we've moved on to a new property, create a new AnimationProperty
-    // to insert segments into.
-    if (aEntries[i].mProperty != lastProperty) {
-      MOZ_ASSERT(aEntries[i].mOffset == 0.0f);
-      animationProperty = aResult.AppendElement();
-      animationProperty->mProperty = aEntries[i].mProperty;
-      lastProperty = aEntries[i].mProperty;
-    }
-
-    MOZ_ASSERT(animationProperty, "animationProperty should be valid pointer.");
-
-    // Now generate the segment.
-    AnimationPropertySegment* segment =
-      animationProperty->mSegments.AppendElement();
-    segment->mFromKey   = aEntries[i].mOffset;
-    segment->mToKey     = aEntries[j].mOffset;
-    segment->mFromValue = aEntries[i].mValue;
-    segment->mToValue   = aEntries[j].mValue;
-    segment->mTimingFunction = aEntries[i].mTimingFunction;
-
-    i = j;
-  }
-}
-
-/**
- * Converts a JS object to an IDL sequence<Keyframe> and builds an
- * array of AnimationProperty objects for the keyframe animation
- * that it specifies.
- *
- * @param aTarget The target of the animation.
- * @param aIterator An already-initialized ForOfIterator for the JS
- *   object to iterate over as a sequence.
- * @param aResult The array into which the resulting AnimationProperty
- *   objects will be appended.
- */
-static void
-BuildAnimationPropertyListFromKeyframeSequence(
-    JSContext* aCx,
-    Element* aTarget,
-    CSSPseudoElementType aPseudoType,
-    JS::ForOfIterator& aIterator,
-    nsTArray<AnimationProperty>& aResult,
-    ErrorResult& aRv)
-{
-  // Convert the object in aIterator to sequence<Keyframe>, producing
-  // an array of OffsetIndexedKeyframe objects.
-  AutoTArray<OffsetIndexedKeyframe,4> keyframes;
-  if (!ConvertKeyframeSequence(aCx, aIterator, keyframes)) {
-    aRv.Throw(NS_ERROR_FAILURE);
-    return;
-  }
-
-  // If the sequence<> had zero elements, we won't generate any
-  // keyframes.
-  if (keyframes.IsEmpty()) {
-    return;
-  }
-
-  // Check that the keyframes are loosely sorted and with values all
-  // between 0% and 100%.
-  if (!HasValidOffsets(keyframes)) {
-    aRv.ThrowTypeError<MSG_INVALID_KEYFRAME_OFFSETS>();
-    return;
-  }
-
-  // Fill in 0%/100% values if the first/element keyframes don't have
-  // a specified offset, and evenly space those that have a missing
-  // offset.  (We don't support paced spacing yet.)
-  ApplyDistributeSpacing(keyframes);
-
-  // Convert the OffsetIndexedKeyframes into a list of KeyframeValueEntry
-  // objects.
-  nsTArray<KeyframeValueEntry> entries;
-  GenerateValueEntries(aTarget, aPseudoType, keyframes, entries, aRv);
-  if (aRv.Failed()) {
-    return;
-  }
-
-  // Finally, build an array of AnimationProperty objects in aResult
-  // corresponding to the entries.
-  BuildSegmentsFromValueEntries(entries, aResult);
-}
-
-/**
- * Converts a JS object to an IDL PropertyIndexedKeyframes and builds an
- * array of AnimationProperty objects for the keyframe animation
- * that it specifies.
- *
- * @param aTarget The target of the animation.
- * @param aValue The JS object.
- * @param aResult The array into which the resulting AnimationProperty
- *   objects will be appended.
- */
-static void
-BuildAnimationPropertyListFromPropertyIndexedKeyframes(
-    JSContext* aCx,
-    Element* aTarget,
-    CSSPseudoElementType aPseudoType,
-    JS::Handle<JS::Value> aValue,
-    InfallibleTArray<AnimationProperty>& aResult,
-    ErrorResult& aRv)
-{
-  MOZ_ASSERT(aValue.isObject());
-
-  // Convert the object to a PropertyIndexedKeyframes dictionary to
-  // get its explicit dictionary members.
-  binding_detail::FastPropertyIndexedKeyframes keyframes;
-  if (!keyframes.Init(aCx, aValue, "PropertyIndexedKeyframes argument",
-                      false)) {
-    aRv.Throw(NS_ERROR_FAILURE);
-    return;
-  }
-
-  Maybe<ComputedTimingFunction> easing =
-    TimingParams::ParseEasing(keyframes.mEasing, aTarget->OwnerDoc(), aRv);
-
-  // We ignore easing.mComposite since we don't support composite modes on
-  // keyframes yet.
-
-  // Get all the property--value-list pairs off the object.
-  JS::Rooted<JSObject*> object(aCx, &aValue.toObject());
-  nsTArray<PropertyValuesPair> propertyValuesPairs;
-  if (!GetPropertyValuesPairs(aCx, object, ListAllowance::eAllow,
-                              propertyValuesPairs)) {
-    aRv.Throw(NS_ERROR_FAILURE);
-    return;
-  }
-
-  // We must keep track of which properties we've already generated
-  // an AnimationProperty since the author could have specified both a
-  // shorthand and one of its component longhands on the
-  // PropertyIndexedKeyframes.
-  nsCSSPropertySet properties;
-
-  // Create AnimationProperty objects for each PropertyValuesPair, applying
-  // the "distribute" spacing algorithm to the segments.
-  for (const PropertyValuesPair& pair : propertyValuesPairs) {
-    size_t count = pair.mValues.Length();
-    if (count == 0) {
-      // No animation values for this property.
-      continue;
-    }
-    if (count == 1) {
-      // We don't support additive segments and so can't support an
-      // animation that goes from the underlying value to this
-      // specified value.  Throw an exception until we do support this.
-      aRv.Throw(NS_ERROR_DOM_ANIM_MISSING_PROPS_ERR);
-      return;
-    }
-
-    // If we find an invalid value, we don't create a segment for it, but
-    // we adjust the surrounding segments so that the timing of the segments
-    // is the same as if we did support it.  For example, animating with
-    // values ["red", "green", "yellow", "invalid", "blue"] will generate
-    // segments with this timing:
-    //
-    //   0.00 -> 0.25 : red -> green
-    //   0.25 -> 0.50 : green -> yellow
-    //   0.50 -> 1.00 : yellow -> blue
-    //
-    // With future spec clarifications we might decide to preserve the invalid
-    // value on the segment and make the animation code deal with the invalid
-    // value instead.
-    nsTArray<PropertyStyleAnimationValuePair> fromValues;
-    float fromKey = 0.0f;
-    if (!StyleAnimationValue::ComputeValues(pair.mProperty,
-                                            nsCSSProps::eEnabledForAllContent,
-                                            aTarget,
-                                            aPseudoType,
-                                            pair.mValues[0],
-                                            /* aUseSVGMode */ false,
-                                            fromValues)) {
-      // We need to throw for an invalid first value, since that would imply an
-      // additive animation, which we don't support yet.
-      aRv.Throw(NS_ERROR_DOM_ANIM_MISSING_PROPS_ERR);
-      return;
-    }
-
-    if (fromValues.IsEmpty()) {
-      // All longhand components of a shorthand pair.mProperty must be disabled.
-      continue;
-    }
-
-    // Create AnimationProperty objects for each property that had a
-    // value computed.  When pair.mProperty is a longhand, it is just
-    // that property.  When pair.mProperty is a shorthand, we'll have
-    // one property per longhand component.
-    nsTArray<size_t> animationPropertyIndexes;
-    animationPropertyIndexes.SetLength(fromValues.Length());
-    for (size_t i = 0, n = fromValues.Length(); i < n; ++i) {
-      nsCSSProperty p = fromValues[i].mProperty;
-      bool found = false;
-      if (properties.HasProperty(p)) {
-        // We have already dealt with this property.  Look up and
-        // overwrite the old AnimationProperty object.
-        for (size_t j = 0, m = aResult.Length(); j < m; ++j) {
-          if (aResult[j].mProperty == p) {
-            aResult[j].mSegments.Clear();
-            animationPropertyIndexes[i] = j;
-            found = true;
-            break;
-          }
-        }
-        MOZ_ASSERT(found, "properties is inconsistent with aResult");
-      }
-      if (!found) {
-        // This is the first time we've encountered this property.
-        animationPropertyIndexes[i] = aResult.Length();
-        AnimationProperty* animationProperty = aResult.AppendElement();
-        animationProperty->mProperty = p;
-        properties.AddProperty(p);
-      }
-    }
-
-    double portion = 1.0 / (count - 1);
-    for (size_t i = 0; i < count - 1; ++i) {
-      nsTArray<PropertyStyleAnimationValuePair> toValues;
-      float toKey = (i + 1) * portion;
-      if (!StyleAnimationValue::ComputeValues(pair.mProperty,
-                                              nsCSSProps::eEnabledForAllContent,
-                                              aTarget,
-                                              aPseudoType,
-                                              pair.mValues[i + 1],
-                                              /* aUseSVGMode */ false,
-                                              toValues)) {
-        if (i + 1 == count - 1) {
-          // We need to throw for an invalid last value, since that would
-          // imply an additive animation, which we don't support yet.
-          aRv.Throw(NS_ERROR_DOM_ANIM_MISSING_PROPS_ERR);
-          return;
-        }
-        // Otherwise, skip the segment.
-        continue;
-      }
-      MOZ_ASSERT(toValues.Length() == fromValues.Length(),
-                 "should get the same number of properties as the last time "
-                 "we called ComputeValues for pair.mProperty");
-      for (size_t j = 0, n = toValues.Length(); j < n; ++j) {
-        size_t index = animationPropertyIndexes[j];
-        AnimationPropertySegment* segment =
-          aResult[index].mSegments.AppendElement();
-        segment->mFromKey = fromKey;
-        segment->mFromValue = fromValues[j].mValue;
-        segment->mToKey = toKey;
-        segment->mToValue = toValues[j].mValue;
-        segment->mTimingFunction = easing;
-      }
-      fromValues = Move(toValues);
-      fromKey = toKey;
-    }
-  }
-}
-
-/**
- * Converts a JS value to an IDL
- * (PropertyIndexedKeyframes or sequence<Keyframe>) value and builds an
- * array of AnimationProperty objects for the keyframe animation
- * that it specifies.
- *
- * @param aTarget The target of the animation, used to resolve style
- *   for a property's underlying value if needed.
- * @param aFrames The JS value, provided as an optional IDL |object?| value,
- *   that is the keyframe list specification.
- * @param aResult The array into which the resulting AnimationProperty
- *   objects will be appended.
- */
-/* static */ void
-KeyframeEffectReadOnly::BuildAnimationPropertyList(
-    JSContext* aCx,
-    Element* aTarget,
-    CSSPseudoElementType aPseudoType,
-    JS::Handle<JSObject*> aFrames,
-    InfallibleTArray<AnimationProperty>& aResult,
-    ErrorResult& aRv)
-{
-  MOZ_ASSERT(aResult.IsEmpty());
-
-  // A frame list specification in the IDL is:
-  //
-  // (PropertyIndexedKeyframes or sequence<Keyframe> or SharedKeyframeList)
-  //
-  // We don't support SharedKeyframeList yet, but we do the other two.  We
-  // manually implement the parts of JS-to-IDL union conversion algorithm
-  // from the Web IDL spec, since we have to represent this an object? so
-  // we can look at the open-ended set of properties on a
-  // PropertyIndexedKeyframes or Keyframe.
-
-  if (!aFrames) {
-    // The argument was explicitly null.  In this case, the default dictionary
-    // value for PropertyIndexedKeyframes would result in no keyframes.
-    return;
-  }
-
-  // At this point we know we have an object.  We try to convert it to a
-  // sequence<Keyframe> first, and if that fails due to not being iterable,
-  // we try to convert it to PropertyIndexedKeyframes.
-  JS::Rooted<JS::Value> objectValue(aCx, JS::ObjectValue(*aFrames));
-  JS::ForOfIterator iter(aCx);
-  if (!iter.init(objectValue, JS::ForOfIterator::AllowNonIterable)) {
-    aRv.Throw(NS_ERROR_FAILURE);
-    return;
-  }
-
-  if (iter.valueIsIterable()) {
-    BuildAnimationPropertyListFromKeyframeSequence(aCx, aTarget, aPseudoType,
-                                                   iter, aResult, aRv);
-  } else {
-    BuildAnimationPropertyListFromPropertyIndexedKeyframes(aCx, aTarget,
-                                                           aPseudoType,
-                                                           objectValue, aResult,
-                                                           aRv);
-  }
-}
-
 /* static */ already_AddRefed<KeyframeEffectReadOnly>
 KeyframeEffectReadOnly::Constructor(
     const GlobalObject& aGlobal,
     const Nullable<ElementOrCSSPseudoElement>& aTarget,
     JS::Handle<JSObject*> aFrames,
     const UnrestrictedDoubleOrKeyframeEffectOptions& aOptions,
     ErrorResult& aRv)
 {
@@ -1941,26 +1063,25 @@ KeyframeEffectReadOnly::GetFrames(JSCont
   }
 
   entries.Sort(OrderedKeyframeValueEntry::ForKeyframeGenerationComparator());
 
   for (size_t i = 0, n = entries.Length(); i < n; ) {
     OrderedKeyframeValueEntry* entry = &entries[i];
     OrderedKeyframeValueEntry* previousEntry = nullptr;
 
-    // Create a JS object with the explicit ComputedKeyframe dictionary members.
-    ComputedKeyframe keyframeDict;
+    // Create a JS object with the BaseComputedKeyframe dictionary members.
+    BaseComputedKeyframe keyframeDict;
     keyframeDict.mOffset.SetValue(entry->mOffset);
     keyframeDict.mComputedOffset.Construct(entry->mOffset);
     if (entry->mTimingFunction && entry->mTimingFunction->isSome()) {
       // If null, leave easing as its default "linear".
       keyframeDict.mEasing.Truncate();
       entry->mTimingFunction->value().AppendToString(keyframeDict.mEasing);
     }
-    keyframeDict.mComposite.SetValue(CompositeOperation::Replace);
 
     JS::Rooted<JS::Value> keyframeJSValue(aCx);
     if (!ToJSValue(aCx, keyframeDict, &keyframeJSValue)) {
       aRv.Throw(NS_ERROR_FAILURE);
       return;
     }
 
     JS::Rooted<JSObject*> keyframe(aCx, &keyframeJSValue.toObject());
--- a/dom/animation/KeyframeEffect.h
+++ b/dom/animation/KeyframeEffect.h
@@ -3,36 +3,39 @@
 /* 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_KeyframeEffect_h
 #define mozilla_dom_KeyframeEffect_h
 
 #include "nsAutoPtr.h"
+#include "nsCSSProperty.h"
+#include "nsCSSValue.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsIDocument.h"
+#include "nsTArray.h"
 #include "nsWrapperCache.h"
 #include "mozilla/AnimationPerformanceWarning.h"
 #include "mozilla/Attributes.h"
-#include "mozilla/ComputedTimingFunction.h" // ComputedTimingFunction
-#include "mozilla/LayerAnimationInfo.h"     // LayerAnimations::kRecords
+#include "mozilla/ComputedTiming.h"
+#include "mozilla/ComputedTimingFunction.h"
+#include "mozilla/LayerAnimationInfo.h" // LayerAnimations::kRecords
+#include "mozilla/Maybe.h"
 #include "mozilla/NonOwningAnimationTarget.h"
-#include "mozilla/OwningNonNull.h"          // OwningNonNull<...>
+#include "mozilla/OwningNonNull.h"      // OwningNonNull<...>
 #include "mozilla/StickyTimeDuration.h"
 #include "mozilla/StyleAnimationValue.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/TimingParams.h"
 #include "mozilla/dom/AnimationEffectReadOnly.h"
 #include "mozilla/dom/AnimationEffectTimingReadOnly.h"
 #include "mozilla/dom/Element.h"
-#include "mozilla/dom/KeyframeBinding.h"
 #include "mozilla/dom/Nullable.h"
 
-
 struct JSContext;
 class nsCSSPropertySet;
 class nsIContent;
 class nsIDocument;
 class nsIFrame;
 class nsPresContext;
 
 namespace mozilla {
@@ -46,64 +49,58 @@ class OwningElementOrCSSPseudoElement;
 class UnrestrictedDoubleOrKeyframeAnimationOptions;
 class UnrestrictedDoubleOrKeyframeEffectOptions;
 enum class IterationCompositeOperation : uint32_t;
 enum class CompositeOperation : uint32_t;
 struct AnimationPropertyDetails;
 }
 
 /**
- * Stores the results of calculating the timing properties of an animation
- * at a given sample time.
+ * A property-value pair specified on a keyframe.
  */
-struct ComputedTiming
+struct PropertyValuePair
 {
-  // The total duration of the animation including all iterations.
-  // Will equal StickyTimeDuration::Forever() if the animation repeats
-  // indefinitely.
-  StickyTimeDuration  mActiveDuration;
-  // The effect end time in local time (i.e. an offset from the effect's
-  // start time). Will equal StickyTimeDuration::Forever() if the animation
-  // plays indefinitely.
-  StickyTimeDuration  mEndTime;
-  // Progress towards the end of the current iteration. If the effect is
-  // being sampled backwards, this will go from 1.0 to 0.0.
-  // Will be null if the animation is neither animating nor
-  // filling at the sampled time.
-  Nullable<double>    mProgress;
-  // Zero-based iteration index (meaningless if mProgress is null).
-  uint64_t            mCurrentIteration = 0;
-  // Unlike TimingParams::mIterations, this value is
-  // guaranteed to be in the range [0, Infinity].
-  double              mIterations = 1.0;
-  double              mIterationStart = 0.0;
-  StickyTimeDuration  mDuration;
+  nsCSSProperty mProperty;
+  // The specified value for the property. For shorthand properties or invalid
+  // property values, we store the specified property value as a token stream
+  // (string).
+  nsCSSValue    mValue;
+};
 
-  // This is the computed fill mode so it is never auto
-  dom::FillMode       mFill = dom::FillMode::None;
-  bool FillsForwards() const {
-    MOZ_ASSERT(mFill != dom::FillMode::Auto,
-               "mFill should not be Auto in ComputedTiming.");
-    return mFill == dom::FillMode::Both ||
-           mFill == dom::FillMode::Forwards;
-  }
-  bool FillsBackwards() const {
-    MOZ_ASSERT(mFill != dom::FillMode::Auto,
-               "mFill should not be Auto in ComputedTiming.");
-    return mFill == dom::FillMode::Both ||
-           mFill == dom::FillMode::Backwards;
+/**
+ * A single keyframe.
+ *
+ * This is the canonical form in which keyframe effects are stored and
+ * corresponds closely to the type of objects returned via the getFrames() API.
+ *
+ * Before computing an output animation value, however, we flatten these frames
+ * down to a series of per-property value arrays where we also resolve any
+ * overlapping shorthands/longhands, convert specified CSS values to computed
+ * values, etc.
+ *
+ * When the target element or style context changes, however, we rebuild these
+ * per-property arrays from the original list of keyframes objects. As a result,
+ * these objects represent the master definition of the effect's values.
+ */
+struct Keyframe
+{
+  Keyframe() = default;
+  Keyframe(Keyframe&& aOther)
+    : mOffset(aOther.mOffset)
+    , mComputedOffset(aOther.mComputedOffset)
+    , mTimingFunction(Move(aOther.mTimingFunction))
+    , mPropertyValues(Move(aOther.mPropertyValues))
+  {
   }
 
-  enum class AnimationPhase {
-    Null,   // Not sampled (null sample time)
-    Before, // Sampled prior to the start of the active interval
-    Active, // Sampled within the active interval
-    After   // Sampled after (or at) the end of the active interval
-  };
-  AnimationPhase      mPhase = AnimationPhase::Null;
+  Maybe<double>                 mOffset;
+  double                        mComputedOffset = 0.0;
+  Maybe<ComputedTimingFunction> mTimingFunction; // Nothing() here means
+                                                 // "linear"
+  nsTArray<PropertyValuePair>   mPropertyValues;
 };
 
 struct AnimationPropertySegment
 {
   float mFromKey, mToKey;
   StyleAnimationValue mFromValue, mToValue;
   Maybe<ComputedTimingFunction> mTimingFunction;
 
@@ -355,24 +352,16 @@ protected:
   // (b) It is "relevant" (i.e. yet to finish but not idle, or finished but
   //     filling forwards)
   //
   // As a result, we need to make sure this gets called whenever anything
   // changes with regards to this effects's timing including changes to the
   // owning Animation's timing.
   void UpdateTargetRegistration();
 
-  static void BuildAnimationPropertyList(
-    JSContext* aCx,
-    Element* aTarget,
-    CSSPseudoElementType aPseudoType,
-    JS::Handle<JSObject*> aFrames,
-    InfallibleTArray<AnimationProperty>& aResult,
-    ErrorResult& aRv);
-
   nsCOMPtr<Element> mTarget;
   RefPtr<Animation> mAnimation;
 
   OwningNonNull<AnimationEffectTimingReadOnly> mTiming;
   CSSPseudoElementType mPseudoType;
 
   InfallibleTArray<AnimationProperty> mProperties;
 
new file mode 100644
--- /dev/null
+++ b/dom/animation/KeyframeUtils.cpp
@@ -0,0 +1,1139 @@
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/KeyframeUtils.h"
+
+#include "mozilla/AnimationUtils.h"
+#include "mozilla/ErrorResult.h"
+#include "mozilla/Move.h"
+#include "mozilla/TimingParams.h"
+#include "mozilla/dom/BaseKeyframeTypesBinding.h" // For FastBaseKeyframe etc.
+#include "mozilla/dom/Element.h"
+#include "mozilla/dom/KeyframeEffect.h"
+#include "mozilla/dom/KeyframeEffectBinding.h"
+#include "jsapi.h" // For ForOfIterator etc.
+#include "nsClassHashtable.h"
+#include "nsCSSParser.h"
+#include "nsCSSProps.h"
+#include "nsCSSPseudoElements.h" // For CSSPseudoElementType
+#include "nsTArray.h"
+#include <algorithm> // For std::stable_sort
+
+namespace mozilla {
+
+// ------------------------------------------------------------------
+//
+// Internal data types
+//
+// ------------------------------------------------------------------
+
+// For the aAllowList parameter of AppendStringOrStringSequence and
+// GetPropertyValuesPairs.
+enum class ListAllowance { eDisallow, eAllow };
+
+/**
+ * A comparator to sort nsCSSProperty values such that longhands are sorted
+ * before shorthands, and shorthands with less components are sorted before
+ * shorthands with more components.
+ *
+ * Using this allows us to prioritize values specified by longhands (or smaller
+ * shorthand subsets) when longhands and shorthands are both specified
+ * on the one keyframe.
+ *
+ * Example orderings that result from this:
+ *
+ *   margin-left, margin
+ *
+ * and:
+ *
+ *   border-top-color, border-color, border-top, border
+ */
+class PropertyPriorityComparator
+{
+public:
+  PropertyPriorityComparator()
+    : mSubpropertyCountInitialized(false) {}
+
+  bool Equals(nsCSSProperty aLhs, nsCSSProperty aRhs) const
+  {
+    return aLhs == aRhs;
+  }
+
+  bool LessThan(nsCSSProperty aLhs,
+                nsCSSProperty aRhs) const
+  {
+    bool isShorthandLhs = nsCSSProps::IsShorthand(aLhs);
+    bool isShorthandRhs = nsCSSProps::IsShorthand(aRhs);
+
+    if (isShorthandLhs) {
+      if (isShorthandRhs) {
+        // First, sort shorthands by the number of longhands they have.
+        uint32_t subpropCountLhs = SubpropertyCount(aLhs);
+        uint32_t subpropCountRhs = SubpropertyCount(aRhs);
+        if (subpropCountLhs != subpropCountRhs) {
+          return subpropCountLhs < subpropCountRhs;
+        }
+        // Otherwise, sort by IDL name below.
+      } else {
+        // Put longhands before shorthands.
+        return false;
+      }
+    } else {
+      if (isShorthandRhs) {
+        // Put longhands before shorthands.
+        return true;
+      }
+    }
+    // For two longhand properties, or two shorthand with the same number
+    // of longhand components, sort by IDL name.
+    return nsCSSProps::PropertyIDLNameSortPosition(aLhs) <
+           nsCSSProps::PropertyIDLNameSortPosition(aRhs);
+  }
+
+  uint32_t SubpropertyCount(nsCSSProperty aProperty) const
+  {
+    if (!mSubpropertyCountInitialized) {
+      PodZero(&mSubpropertyCount);
+      mSubpropertyCountInitialized = true;
+    }
+    if (mSubpropertyCount[aProperty] == 0) {
+      uint32_t count = 0;
+      CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(
+          p, aProperty, nsCSSProps::eEnabledForAllContent) {
+        ++count;
+      }
+      mSubpropertyCount[aProperty] = count;
+    }
+    return mSubpropertyCount[aProperty];
+  }
+
+private:
+  // Cache of shorthand subproperty counts.
+  mutable RangedArray<
+    uint32_t,
+    eCSSProperty_COUNT_no_shorthands,
+    eCSSProperty_COUNT - eCSSProperty_COUNT_no_shorthands> mSubpropertyCount;
+  mutable bool mSubpropertyCountInitialized;
+};
+
+/**
+ * Adaptor for PropertyPriorityComparator to sort objects which have
+ * a mProperty member.
+ */
+template <typename T>
+class TPropertyPriorityComparator : PropertyPriorityComparator
+{
+public:
+  bool Equals(const T& aLhs, const T& aRhs) const
+  {
+    return PropertyPriorityComparator::Equals(aLhs.mProperty, aRhs.mProperty);
+  }
+  bool LessThan(const T& aLhs, const T& aRhs) const
+  {
+    return PropertyPriorityComparator::LessThan(aLhs.mProperty, aRhs.mProperty);
+  }
+};
+
+/**
+ * Iterator to walk through a PropertyValuePair array using the ordering
+ * provided by PropertyPriorityComparator.
+ */
+class PropertyPriorityIterator
+{
+public:
+  explicit PropertyPriorityIterator(
+    const nsTArray<PropertyValuePair>& aProperties)
+    : mProperties(aProperties)
+  {
+    mSortedPropertyIndices.SetCapacity(mProperties.Length());
+    for (size_t i = 0, len = mProperties.Length(); i < len; ++i) {
+      PropertyAndIndex propertyIndex = { mProperties[i].mProperty, i };
+      mSortedPropertyIndices.AppendElement(propertyIndex);
+    }
+    mSortedPropertyIndices.Sort(PropertyAndIndex::Comparator());
+  }
+
+  class Iter
+  {
+  public:
+    explicit Iter(const PropertyPriorityIterator& aParent)
+      : mParent(aParent)
+      , mIndex(0) { }
+
+    static Iter EndIter(const PropertyPriorityIterator &aParent)
+    {
+      Iter iter(aParent);
+      iter.mIndex = aParent.mSortedPropertyIndices.Length();
+      return iter;
+    }
+
+    bool operator!=(const Iter& aOther) const
+    {
+      return mIndex != aOther.mIndex;
+    }
+
+    Iter& operator++()
+    {
+      MOZ_ASSERT(mIndex + 1 <= mParent.mSortedPropertyIndices.Length(),
+                 "Should not seek past end iterator");
+      mIndex++;
+      return *this;
+    }
+
+    const PropertyValuePair& operator*()
+    {
+      MOZ_ASSERT(mIndex < mParent.mSortedPropertyIndices.Length(),
+                 "Should not try to dereference an end iterator");
+      return mParent.mProperties[mParent.mSortedPropertyIndices[mIndex].mIndex];
+    }
+
+  private:
+    const PropertyPriorityIterator& mParent;
+    size_t mIndex;
+  };
+
+  Iter begin() { return Iter(*this); }
+  Iter end()   { return Iter::EndIter(*this); }
+
+private:
+  struct PropertyAndIndex
+  {
+    nsCSSProperty mProperty;
+    size_t mIndex; // Index of mProperty within mProperties
+
+    typedef TPropertyPriorityComparator<PropertyAndIndex> Comparator;
+  };
+
+  const nsTArray<PropertyValuePair>& mProperties;
+  nsTArray<PropertyAndIndex> mSortedPropertyIndices;
+};
+
+/**
+ * A property-values pair obtained from the open-ended properties
+ * discovered on a regular keyframe or property-indexed keyframe object.
+ *
+ * Single values (as required by a regular keyframe, and as also supported
+ * on property-indexed keyframes) are stored as the only element in
+ * mValues.
+ */
+struct PropertyValuesPair
+{
+  nsCSSProperty mProperty;
+  nsTArray<nsString> mValues;
+
+  typedef TPropertyPriorityComparator<PropertyValuesPair> Comparator;
+};
+
+/**
+ * An additional property (for a property-values pair) found on a
+ * BaseKeyframe or BasePropertyIndexedKeyframe object.
+ */
+struct AdditionalProperty
+{
+  nsCSSProperty mProperty;
+  size_t mJsidIndex;        // Index into |ids| in GetPropertyValuesPairs.
+
+  struct PropertyComparator
+  {
+    bool Equals(const AdditionalProperty& aLhs,
+                const AdditionalProperty& aRhs) const
+    {
+      return aLhs.mProperty == aRhs.mProperty;
+    }
+    bool LessThan(const AdditionalProperty& aLhs,
+                  const AdditionalProperty& aRhs) const
+    {
+      return nsCSSProps::PropertyIDLNameSortPosition(aLhs.mProperty) <
+             nsCSSProps::PropertyIDLNameSortPosition(aRhs.mProperty);
+    }
+  };
+};
+
+/**
+ * Data for a segment in a keyframe animation of a given property
+ * whose value is a StyleAnimationValue.
+ *
+ * KeyframeValueEntry is used in GetAnimationPropertiesFromKeyframes
+ * to gather data for each individual segment.
+ */
+struct KeyframeValueEntry
+{
+  nsCSSProperty mProperty;
+  StyleAnimationValue mValue;
+  float mOffset;
+  Maybe<ComputedTimingFunction> mTimingFunction;
+
+  struct PropertyOffsetComparator
+  {
+    static bool Equals(const KeyframeValueEntry& aLhs,
+                       const KeyframeValueEntry& aRhs)
+    {
+      return aLhs.mProperty == aRhs.mProperty &&
+             aLhs.mOffset == aRhs.mOffset;
+    }
+    static bool LessThan(const KeyframeValueEntry& aLhs,
+                         const KeyframeValueEntry& aRhs)
+    {
+      // First, sort by property IDL name.
+      int32_t order = nsCSSProps::PropertyIDLNameSortPosition(aLhs.mProperty) -
+                      nsCSSProps::PropertyIDLNameSortPosition(aRhs.mProperty);
+      if (order != 0) {
+        return order < 0;
+      }
+
+      // Then, by offset.
+      return aLhs.mOffset < aRhs.mOffset;
+    }
+  };
+};
+
+class ComputedOffsetComparator
+{
+public:
+  static bool Equals(const Keyframe& aLhs, const Keyframe& aRhs)
+  {
+    return aLhs.mComputedOffset == aRhs.mComputedOffset;
+  }
+
+  static bool LessThan(const Keyframe& aLhs, const Keyframe& aRhs)
+  {
+    return aLhs.mComputedOffset < aRhs.mComputedOffset;
+  }
+};
+
+
+// ------------------------------------------------------------------
+//
+// Internal helper method declarations
+//
+// ------------------------------------------------------------------
+
+static void
+GetKeyframeListFromKeyframeSequence(JSContext* aCx,
+                                    JS::ForOfIterator& aIterator,
+                                    nsTArray<Keyframe>& aResult,
+                                    ErrorResult& aRv);
+
+static bool
+ConvertKeyframeSequence(JSContext* aCx,
+                        JS::ForOfIterator& aIterator,
+                        nsTArray<Keyframe>& aResult);
+
+static bool
+GetPropertyValuesPairs(JSContext* aCx,
+                       JS::Handle<JSObject*> aObject,
+                       ListAllowance aAllowLists,
+                       nsTArray<PropertyValuesPair>& aResult);
+
+static bool
+AppendStringOrStringSequenceToArray(JSContext* aCx,
+                                    JS::Handle<JS::Value> aValue,
+                                    ListAllowance aAllowLists,
+                                    nsTArray<nsString>& aValues);
+
+static bool
+AppendValueAsString(JSContext* aCx,
+                    nsTArray<nsString>& aValues,
+                    JS::Handle<JS::Value> aValue);
+
+static PropertyValuePair
+MakePropertyValuePair(nsCSSProperty aProperty, const nsAString& aStringValue,
+                      nsCSSParser& aParser, nsIDocument* aDocument);
+
+static bool
+HasValidOffsets(const nsTArray<Keyframe>& aKeyframes);
+
+static void
+BuildSegmentsFromValueEntries(nsTArray<KeyframeValueEntry>& aEntries,
+                              nsTArray<AnimationProperty>& aResult);
+
+static void
+GetKeyframeListFromPropertyIndexedKeyframe(JSContext* aCx,
+                                           JS::Handle<JS::Value> aValue,
+                                           nsTArray<Keyframe>& aResult,
+                                           ErrorResult& aRv);
+
+static bool
+RequiresAdditiveAnimation(const nsTArray<Keyframe>& aKeyframes,
+                          nsIDocument* aDocument);
+
+
+// ------------------------------------------------------------------
+//
+// Public API
+//
+// ------------------------------------------------------------------
+
+/* static */ nsTArray<Keyframe>
+KeyframeUtils::GetKeyframesFromObject(JSContext* aCx,
+                                      JS::Handle<JSObject*> aFrames,
+                                      ErrorResult& aRv)
+{
+  MOZ_ASSERT(!aRv.Failed());
+
+  nsTArray<Keyframe> keyframes;
+
+  if (!aFrames) {
+    // The argument was explicitly null meaning no keyframes.
+    return keyframes;
+  }
+
+  // At this point we know we have an object. We try to convert it to a
+  // sequence of keyframes first, and if that fails due to not being iterable,
+  // we try to convert it to a property-indexed keyframe.
+  JS::Rooted<JS::Value> objectValue(aCx, JS::ObjectValue(*aFrames));
+  JS::ForOfIterator iter(aCx);
+  if (!iter.init(objectValue, JS::ForOfIterator::AllowNonIterable)) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return keyframes;
+  }
+
+  if (iter.valueIsIterable()) {
+    GetKeyframeListFromKeyframeSequence(aCx, iter, keyframes, aRv);
+  } else {
+    GetKeyframeListFromPropertyIndexedKeyframe(aCx, objectValue, keyframes,
+                                               aRv);
+  }
+
+  if (aRv.Failed()) {
+    MOZ_ASSERT(keyframes.IsEmpty(),
+               "Should not set any keyframes when there is an error");
+    return keyframes;
+  }
+
+  // We currently don't support additive animation. However, Web Animations
+  // says that if you don't have a keyframe at offset 0 or 1, then you should
+  // synthesize one using an additive zero value when you go to compose style.
+  // Until we implement additive animations we just throw if we encounter any
+  // set of keyframes that would put us in that situation.
+
+  nsIDocument* doc = AnimationUtils::GetCurrentRealmDocument(aCx);
+  if (!doc) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    keyframes.Clear();
+    return keyframes;
+  }
+
+  if (RequiresAdditiveAnimation(keyframes, doc)) {
+    aRv.Throw(NS_ERROR_DOM_ANIM_MISSING_PROPS_ERR);
+    keyframes.Clear();
+  }
+
+  return keyframes;
+}
+
+/* static */ void
+KeyframeUtils::ApplyDistributeSpacing(nsTArray<Keyframe>& aKeyframes)
+{
+  if (aKeyframes.IsEmpty()) {
+    return;
+  }
+
+  // If the first or last keyframes have an unspecified offset,
+  // fill them in with 0% and 100%.  If there is only a single keyframe,
+  // then it gets 100%.
+  Keyframe& lastElement = aKeyframes.LastElement();
+  lastElement.mComputedOffset = lastElement.mOffset.valueOr(1.0);
+  if (aKeyframes.Length() > 1) {
+    Keyframe& firstElement = aKeyframes[0];
+    firstElement.mComputedOffset = firstElement.mOffset.valueOr(0.0);
+  }
+
+  // Fill in remaining missing offsets.
+  size_t i = 0;
+  while (i < aKeyframes.Length() - 1) {
+    double start = aKeyframes[i].mComputedOffset;
+    size_t j = i + 1;
+    while (aKeyframes[j].mOffset.isNothing() && j < aKeyframes.Length() - 1) {
+      ++j;
+    }
+    double end = aKeyframes[j].mOffset.valueOr(1.0);
+    size_t n = j - i;
+    for (size_t k = 1; k < n; ++k) {
+      double offset = start + double(k) / n * (end - start);
+      aKeyframes[i + k].mComputedOffset = offset;
+    }
+    i = j;
+    aKeyframes[j].mComputedOffset = end;
+  }
+}
+
+/* static */ nsTArray<AnimationProperty>
+KeyframeUtils::GetAnimationPropertiesFromKeyframes(
+    nsStyleContext* aStyleContext,
+    dom::Element* aElement,
+    CSSPseudoElementType aPseudoType,
+    const nsTArray<Keyframe>& aFrames)
+{
+  nsTArray<KeyframeValueEntry> entries;
+
+  for (const Keyframe& frame : aFrames) {
+    nsCSSPropertySet propertiesOnThisKeyframe;
+    for (const PropertyValuePair& pair :
+           PropertyPriorityIterator(frame.mPropertyValues)) {
+      // We currently store invalid longhand values on keyframes as a token
+      // stream so if we see one of them, just keep moving.
+      if (!nsCSSProps::IsShorthand(pair.mProperty) &&
+          pair.mValue.GetUnit() == eCSSUnit_TokenStream) {
+        continue;
+      }
+
+      // Expand each value into the set of longhands and produce
+      // a KeyframeValueEntry for each value.
+      nsTArray<PropertyStyleAnimationValuePair> values;
+
+      // For shorthands, we store the string as a token stream so we need to
+      // extract that first.
+      if (nsCSSProps::IsShorthand(pair.mProperty)) {
+        nsCSSValueTokenStream* tokenStream = pair.mValue.GetTokenStreamValue();
+        if (!StyleAnimationValue::ComputeValues(pair.mProperty,
+              nsCSSProps::eEnabledForAllContent, aElement, aStyleContext,
+              tokenStream->mTokenStream, /* aUseSVGMode */ false, values)) {
+          continue;
+        }
+      } else {
+        if (!StyleAnimationValue::ComputeValues(pair.mProperty,
+              nsCSSProps::eEnabledForAllContent, aElement, aStyleContext,
+              pair.mValue, /* aUseSVGMode */ false, values)) {
+          continue;
+        }
+        MOZ_ASSERT(values.Length() == 1,
+                   "Longhand properties should produce a single"
+                   " StyleAnimationValue");
+
+        // 'visibility' requires special handling that is unique to CSS
+        // Transitions/CSS Animations/Web Animations (i.e. not SMIL) so we
+        // apply that here.
+        //
+        // Bug 1259285 - Move this code to StyleAnimationValue
+        if (pair.mProperty == eCSSProperty_visibility) {
+          MOZ_ASSERT(values[0].mValue.GetUnit() ==
+                      StyleAnimationValue::eUnit_Enumerated,
+                    "unexpected unit");
+          values[0].mValue.SetIntValue(values[0].mValue.GetIntValue(),
+                                       StyleAnimationValue::eUnit_Visibility);
+        }
+      }
+
+      for (auto& value : values) {
+        // If we already got a value for this property on the keyframe,
+        // skip this one.
+        if (propertiesOnThisKeyframe.HasProperty(value.mProperty)) {
+          continue;
+        }
+
+        KeyframeValueEntry* entry = entries.AppendElement();
+        entry->mOffset = frame.mComputedOffset;
+        entry->mProperty = value.mProperty;
+        entry->mValue = value.mValue;
+        entry->mTimingFunction = frame.mTimingFunction;
+
+        propertiesOnThisKeyframe.AddProperty(value.mProperty);
+      }
+    }
+  }
+
+  nsTArray<AnimationProperty> result;
+  BuildSegmentsFromValueEntries(entries, result);
+
+  return result;
+}
+
+
+// ------------------------------------------------------------------
+//
+// Internal helpers
+//
+// ------------------------------------------------------------------
+
+/**
+ * Converts a JS object to an IDL sequence<Keyframe>.
+ *
+ * @param aCx The JSContext corresponding to |aIterator|.
+ * @param aIterator An already-initialized ForOfIterator for the JS
+ *   object to iterate over as a sequence.
+ * @param aResult The array into which the resulting Keyframe objects will be
+ *   appended.
+ * @param aRv Out param to store any errors thrown by this function.
+ */
+static void
+GetKeyframeListFromKeyframeSequence(JSContext* aCx,
+                                    JS::ForOfIterator& aIterator,
+                                    nsTArray<Keyframe>& aResult,
+                                    ErrorResult& aRv)
+{
+  MOZ_ASSERT(!aRv.Failed());
+  MOZ_ASSERT(aResult.IsEmpty());
+
+  // Convert the object in aIterator to a sequence of keyframes producing
+  // an array of Keyframe objects.
+  if (!ConvertKeyframeSequence(aCx, aIterator, aResult)) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    aResult.Clear();
+    return;
+  }
+
+  // If the sequence<> had zero elements, we won't generate any
+  // keyframes.
+  if (aResult.IsEmpty()) {
+    return;
+  }
+
+  // Check that the keyframes are loosely sorted and with values all
+  // between 0% and 100%.
+  if (!HasValidOffsets(aResult)) {
+    aRv.ThrowTypeError<dom::MSG_INVALID_KEYFRAME_OFFSETS>();
+    aResult.Clear();
+    return;
+  }
+}
+
+/**
+ * Converts a JS object wrapped by the given JS::ForIfIterator to an
+ * IDL sequence<Keyframe> and stores the resulting Keyframe objects in
+ * aResult.
+ */
+static bool
+ConvertKeyframeSequence(JSContext* aCx,
+                        JS::ForOfIterator& aIterator,
+                        nsTArray<Keyframe>& aResult)
+{
+  nsIDocument* doc = AnimationUtils::GetCurrentRealmDocument(aCx);
+  if (!doc) {
+    return false;
+  }
+
+  JS::Rooted<JS::Value> value(aCx);
+  nsCSSParser parser(doc->CSSLoader());
+
+  for (;;) {
+    bool done;
+    if (!aIterator.next(&value, &done)) {
+      return false;
+    }
+    if (done) {
+      break;
+    }
+    // Each value found when iterating the object must be an object
+    // or null/undefined (which gets treated as a default {} dictionary
+    // value).
+    if (!value.isObject() && !value.isNullOrUndefined()) {
+      dom::ThrowErrorMessage(aCx, dom::MSG_NOT_OBJECT,
+                             "Element of sequence<Keyframe> argument");
+      return false;
+    }
+
+    // Convert the JS value into a BaseKeyframe dictionary value.
+    dom::binding_detail::FastBaseKeyframe keyframeDict;
+    if (!keyframeDict.Init(aCx, value,
+                           "Element of sequence<Keyframe> argument")) {
+      return false;
+    }
+
+    Keyframe* keyframe = aResult.AppendElement(fallible);
+    if (!keyframe) {
+      return false;
+    }
+    if (!keyframeDict.mOffset.IsNull()) {
+      keyframe->mOffset.emplace(keyframeDict.mOffset.Value());
+    }
+
+    ErrorResult rv;
+    keyframe->mTimingFunction =
+      TimingParams::ParseEasing(keyframeDict.mEasing, doc, rv);
+    if (rv.MaybeSetPendingException(aCx)) {
+      return false;
+    }
+
+    // Look for additional property-values pairs on the object.
+    nsTArray<PropertyValuesPair> propertyValuePairs;
+    if (value.isObject()) {
+      JS::Rooted<JSObject*> object(aCx, &value.toObject());
+      if (!GetPropertyValuesPairs(aCx, object,
+                                  ListAllowance::eDisallow,
+                                  propertyValuePairs)) {
+        return false;
+      }
+    }
+
+    for (PropertyValuesPair& pair : propertyValuePairs) {
+      MOZ_ASSERT(pair.mValues.Length() == 1);
+      keyframe->mPropertyValues.AppendElement(
+        MakePropertyValuePair(pair.mProperty, pair.mValues[0], parser, doc));
+    }
+  }
+
+  return true;
+}
+
+/**
+ * Reads the property-values pairs from the specified JS object.
+ *
+ * @param aObject The JS object to look at.
+ * @param aAllowLists If eAllow, values will be converted to
+ *   (DOMString or sequence<DOMString); if eDisallow, values
+ *   will be converted to DOMString.
+ * @param aResult The array into which the enumerated property-values
+ *   pairs will be stored.
+ * @return false on failure or JS exception thrown while interacting
+ *   with aObject; true otherwise.
+ */
+static bool
+GetPropertyValuesPairs(JSContext* aCx,
+                       JS::Handle<JSObject*> aObject,
+                       ListAllowance aAllowLists,
+                       nsTArray<PropertyValuesPair>& aResult)
+{
+  nsTArray<AdditionalProperty> properties;
+
+  // Iterate over all the properties on aObject and append an
+  // entry to properties for them.
+  //
+  // We don't compare the jsids that we encounter with those for
+  // the explicit dictionary members, since we know that none
+  // of the CSS property IDL names clash with them.
+  JS::Rooted<JS::IdVector> ids(aCx, JS::IdVector(aCx));
+  if (!JS_Enumerate(aCx, aObject, &ids)) {
+    return false;
+  }
+  for (size_t i = 0, n = ids.length(); i < n; i++) {
+    nsAutoJSString propName;
+    if (!propName.init(aCx, ids[i])) {
+      return false;
+    }
+    nsCSSProperty property =
+      nsCSSProps::LookupPropertyByIDLName(propName,
+                                          nsCSSProps::eEnabledForAllContent);
+    if (property != eCSSProperty_UNKNOWN &&
+        (nsCSSProps::IsShorthand(property) ||
+         nsCSSProps::kAnimTypeTable[property] != eStyleAnimType_None)) {
+      // Only need to check for longhands being animatable, as the
+      // StyleAnimationValue::ComputeValues calls later on will check for
+      // a shorthand's components being animatable.
+      AdditionalProperty* p = properties.AppendElement();
+      p->mProperty = property;
+      p->mJsidIndex = i;
+    }
+  }
+
+  // Sort the entries by IDL name and then get each value and
+  // convert it either to a DOMString or to a
+  // (DOMString or sequence<DOMString>), depending on aAllowLists,
+  // and build up aResult.
+  properties.Sort(AdditionalProperty::PropertyComparator());
+
+  for (AdditionalProperty& p : properties) {
+    JS::Rooted<JS::Value> value(aCx);
+    if (!JS_GetPropertyById(aCx, aObject, ids[p.mJsidIndex], &value)) {
+      return false;
+    }
+    PropertyValuesPair* pair = aResult.AppendElement();
+    pair->mProperty = p.mProperty;
+    if (!AppendStringOrStringSequenceToArray(aCx, value, aAllowLists,
+                                             pair->mValues)) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+/**
+ * Converts aValue to DOMString, if aAllowLists is eDisallow, or
+ * to (DOMString or sequence<DOMString>) if aAllowLists is aAllow.
+ * The resulting strings are appended to aValues.
+ */
+static bool
+AppendStringOrStringSequenceToArray(JSContext* aCx,
+                                    JS::Handle<JS::Value> aValue,
+                                    ListAllowance aAllowLists,
+                                    nsTArray<nsString>& aValues)
+{
+  if (aAllowLists == ListAllowance::eAllow && aValue.isObject()) {
+    // The value is an object, and we want to allow lists; convert
+    // aValue to (DOMString or sequence<DOMString>).
+    JS::ForOfIterator iter(aCx);
+    if (!iter.init(aValue, JS::ForOfIterator::AllowNonIterable)) {
+      return false;
+    }
+    if (iter.valueIsIterable()) {
+      // If the object is iterable, convert it to sequence<DOMString>.
+      JS::Rooted<JS::Value> element(aCx);
+      for (;;) {
+        bool done;
+        if (!iter.next(&element, &done)) {
+          return false;
+        }
+        if (done) {
+          break;
+        }
+        if (!AppendValueAsString(aCx, aValues, element)) {
+          return false;
+        }
+      }
+      return true;
+    }
+  }
+
+  // Either the object is not iterable, or aAllowLists doesn't want
+  // a list; convert it to DOMString.
+  if (!AppendValueAsString(aCx, aValues, aValue)) {
+    return false;
+  }
+
+  return true;
+}
+
+/**
+ * Converts aValue to DOMString and appends it to aValues.
+ */
+static bool
+AppendValueAsString(JSContext* aCx,
+                    nsTArray<nsString>& aValues,
+                    JS::Handle<JS::Value> aValue)
+{
+  return ConvertJSValueToString(aCx, aValue, dom::eStringify, dom::eStringify,
+                                *aValues.AppendElement());
+}
+
+/**
+ * Construct a PropertyValuePair parsing the given string into a suitable
+ * nsCSSValue object.
+ *
+ * @param aProperty The CSS property.
+ * @param aStringValue The property value to parse.
+ * @param aParser The CSS parser object to use.
+ * @param aDocument The document to use when parsing.
+ * @return The constructed PropertyValuePair object.
+ */
+static PropertyValuePair
+MakePropertyValuePair(nsCSSProperty aProperty, const nsAString& aStringValue,
+                      nsCSSParser& aParser, nsIDocument* aDocument)
+{
+  MOZ_ASSERT(aDocument);
+
+  nsCSSValue value;
+  if (!nsCSSProps::IsShorthand(aProperty)) {
+    aParser.ParseLonghandProperty(aProperty,
+                                  aStringValue,
+                                  aDocument->GetDocumentURI(),
+                                  aDocument->GetDocumentURI(),
+                                  aDocument->NodePrincipal(),
+                                  value);
+  }
+
+  if (value.GetUnit() == eCSSUnit_Null) {
+    // Either we have a shorthand, or we failed to parse a longhand.
+    // In either case, store the string value as a token stream.
+    nsCSSValueTokenStream* tokenStream = new nsCSSValueTokenStream;
+    tokenStream->mTokenStream = aStringValue;
+    // By leaving mShorthandPropertyID as unknown, we ensure that when
+    // we call nsCSSValue::AppendToString we get back the string stored
+    // in mTokenStream.
+    MOZ_ASSERT(tokenStream->mShorthandPropertyID == eCSSProperty_UNKNOWN,
+               "The shorthand property of a token stream should be initialized"
+               " to unknown");
+    value.SetTokenStreamValue(tokenStream);
+  }
+
+  return { aProperty, value };
+}
+
+/**
+ * Checks that the given keyframes are loosely ordered (each keyframe's
+ * offset that is not null is greater than or equal to the previous
+ * non-null offset) and that all values are within the range [0.0, 1.0].
+ *
+ * @return true if the keyframes' offsets are correctly ordered and
+ *   within range; false otherwise.
+ */
+static bool
+HasValidOffsets(const nsTArray<Keyframe>& aKeyframes)
+{
+  double offset = 0.0;
+  for (const Keyframe& keyframe : aKeyframes) {
+    if (keyframe.mOffset) {
+      double thisOffset = keyframe.mOffset.value();
+      if (thisOffset < offset || thisOffset > 1.0f) {
+        return false;
+      }
+      offset = thisOffset;
+    }
+  }
+  return true;
+}
+
+/**
+ * Builds an array of AnimationProperty objects to represent the keyframe
+ * animation segments in aEntries.
+ */
+static void
+BuildSegmentsFromValueEntries(nsTArray<KeyframeValueEntry>& aEntries,
+                              nsTArray<AnimationProperty>& aResult)
+{
+  if (aEntries.IsEmpty()) {
+    return;
+  }
+
+  // Sort the KeyframeValueEntry objects so that all entries for a given
+  // property are together, and the entries are sorted by offset otherwise.
+  std::stable_sort(aEntries.begin(), aEntries.end(),
+                   &KeyframeValueEntry::PropertyOffsetComparator::LessThan);
+
+  MOZ_ASSERT(aEntries[0].mOffset == 0.0f);
+  MOZ_ASSERT(aEntries.LastElement().mOffset == 1.0f);
+
+  // For a given index i, we want to generate a segment from aEntries[i]
+  // to aEntries[j], if:
+  //
+  //   * j > i,
+  //   * aEntries[i + 1]'s offset/property is different from aEntries[i]'s, and
+  //   * aEntries[j - 1]'s offset/property is different from aEntries[j]'s.
+  //
+  // That will eliminate runs of same offset/property values where there's no
+  // point generating zero length segments in the middle of the animation.
+  //
+  // Additionally we need to generate a zero length segment at offset 0 and at
+  // offset 1, if we have multiple values for a given property at that offset,
+  // since we need to retain the very first and very last value so they can
+  // be used for reverse and forward filling.
+
+  nsCSSProperty lastProperty = eCSSProperty_UNKNOWN;
+  AnimationProperty* animationProperty = nullptr;
+
+  size_t i = 0, n = aEntries.Length();
+
+  while (i + 1 < n) {
+    // Starting from i, determine the next [i, j] interval from which to
+    // generate a segment.
+    size_t j;
+    if (aEntries[i].mOffset == 0.0f && aEntries[i + 1].mOffset == 0.0f) {
+      // We need to generate an initial zero-length segment.
+      MOZ_ASSERT(aEntries[i].mProperty == aEntries[i + 1].mProperty);
+      j = i + 1;
+      while (aEntries[j + 1].mOffset == 0.0f) {
+        MOZ_ASSERT(aEntries[j].mProperty == aEntries[j + 1].mProperty);
+        ++j;
+      }
+    } else if (aEntries[i].mOffset == 1.0f) {
+      if (aEntries[i + 1].mOffset == 1.0f) {
+        // We need to generate a final zero-length segment.
+        MOZ_ASSERT(aEntries[i].mProperty == aEntries[i].mProperty);
+        j = i + 1;
+        while (j + 1 < n && aEntries[j + 1].mOffset == 1.0f) {
+          MOZ_ASSERT(aEntries[j].mProperty == aEntries[j + 1].mProperty);
+          ++j;
+        }
+      } else {
+        // New property.
+        MOZ_ASSERT(aEntries[i + 1].mOffset == 0.0f);
+        MOZ_ASSERT(aEntries[i].mProperty != aEntries[i + 1].mProperty);
+        ++i;
+        continue;
+      }
+    } else {
+      while (aEntries[i].mOffset == aEntries[i + 1].mOffset &&
+             aEntries[i].mProperty == aEntries[i + 1].mProperty) {
+        ++i;
+      }
+      j = i + 1;
+    }
+
+    // If we've moved on to a new property, create a new AnimationProperty
+    // to insert segments into.
+    if (aEntries[i].mProperty != lastProperty) {
+      MOZ_ASSERT(aEntries[i].mOffset == 0.0f);
+      animationProperty = aResult.AppendElement();
+      animationProperty->mProperty = aEntries[i].mProperty;
+      lastProperty = aEntries[i].mProperty;
+    }
+
+    MOZ_ASSERT(animationProperty, "animationProperty should be valid pointer.");
+
+    // Now generate the segment.
+    AnimationPropertySegment* segment =
+      animationProperty->mSegments.AppendElement();
+    segment->mFromKey   = aEntries[i].mOffset;
+    segment->mToKey     = aEntries[j].mOffset;
+    segment->mFromValue = aEntries[i].mValue;
+    segment->mToValue   = aEntries[j].mValue;
+    segment->mTimingFunction = aEntries[i].mTimingFunction;
+
+    i = j;
+  }
+}
+
+/**
+ * Converts a JS object representing a property-indexed keyframe into
+ * an array of Keyframe objects.
+ *
+ * @param aCx The JSContext for |aValue|.
+ * @param aValue The JS object.
+ * @param aResult The array into which the resulting AnimationProperty
+ *   objects will be appended.
+ * @param aRv Out param to store any errors thrown by this function.
+ */
+static void
+GetKeyframeListFromPropertyIndexedKeyframe(JSContext* aCx,
+                                           JS::Handle<JS::Value> aValue,
+                                           nsTArray<Keyframe>& aResult,
+                                           ErrorResult& aRv)
+{
+  MOZ_ASSERT(aValue.isObject());
+  MOZ_ASSERT(aResult.IsEmpty());
+  MOZ_ASSERT(!aRv.Failed());
+
+  // Convert the object to a property-indexed keyframe dictionary to
+  // get its explicit dictionary members.
+  dom::binding_detail::FastBasePropertyIndexedKeyframe keyframeDict;
+  if (!keyframeDict.Init(aCx, aValue, "BasePropertyIndexedKeyframe argument",
+                         false)) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  // Get the document to use for parsing CSS properties.
+  nsIDocument* doc = AnimationUtils::GetCurrentRealmDocument(aCx);
+  if (!doc) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  Maybe<ComputedTimingFunction> easing =
+    TimingParams::ParseEasing(keyframeDict.mEasing, doc, aRv);
+  if (aRv.Failed()) {
+    return;
+  }
+
+  // Get all the property--value-list pairs off the object.
+  JS::Rooted<JSObject*> object(aCx, &aValue.toObject());
+  nsTArray<PropertyValuesPair> propertyValuesPairs;
+  if (!GetPropertyValuesPairs(aCx, object, ListAllowance::eAllow,
+                              propertyValuesPairs)) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  // Create a set of keyframes for each property.
+  nsCSSParser parser(doc->CSSLoader());
+  nsClassHashtable<nsFloatHashKey, Keyframe> processedKeyframes;
+  for (const PropertyValuesPair& pair : propertyValuesPairs) {
+    size_t count = pair.mValues.Length();
+    if (count == 0) {
+      // No animation values for this property.
+      continue;
+    }
+    if (count == 1) {
+      // We don't support additive values and so can't support an
+      // animation that goes from the underlying value to this
+      // specified value.  Throw an exception until we do support this.
+      aRv.Throw(NS_ERROR_DOM_ANIM_MISSING_PROPS_ERR);
+      return;
+    }
+
+    size_t n = pair.mValues.Length() - 1;
+    size_t i = 0;
+
+    for (const nsString& stringValue : pair.mValues) {
+      double offset = i++ / double(n);
+      Keyframe* keyframe = processedKeyframes.LookupOrAdd(offset);
+      if (keyframe->mPropertyValues.IsEmpty()) {
+        keyframe->mTimingFunction = easing;
+        keyframe->mComputedOffset = offset;
+      }
+      keyframe->mPropertyValues.AppendElement(
+        MakePropertyValuePair(pair.mProperty, stringValue, parser, doc));
+    }
+  }
+
+  aResult.SetCapacity(processedKeyframes.Count());
+  for (auto iter = processedKeyframes.Iter(); !iter.Done(); iter.Next()) {
+    aResult.AppendElement(Move(*iter.UserData()));
+  }
+
+  aResult.Sort(ComputedOffsetComparator());
+}
+
+/**
+ * Returns true if the supplied set of keyframes has keyframe values for
+ * any property for which it does not also supply a value for the 0% and 100%
+ * offsets. In this case we are supposed to synthesize an additive zero value
+ * but since we don't support additive animation yet we can't support this
+ * case. We try to detect that here so we can throw an exception. The check is
+ * not entirely accurate but should detect most common cases.
+ *
+ * @param aKeyframes The set of keyframes to analyze.
+ * @param aDocument The document to use when parsing keyframes so we can
+ *   try to detect where we have an invalid value at 0%/100%.
+ */
+static bool
+RequiresAdditiveAnimation(const nsTArray<Keyframe>& aKeyframes,
+                          nsIDocument* aDocument)
+{
+  // We are looking to see if that every property referenced in |aKeyframes|
+  // has a valid property at offset 0.0 and 1.0. The check as to whether a
+  // property is valid or not, however, is not precise. We only check if the
+  // property can be parsed, NOT whether it can also be converted to a
+  // StyleAnimationValue since doing that requires a target element bound to
+  // a document which we might not always have at the point where we want to
+  // perform this check.
+  //
+  // This is only a temporary measure until we implement additive animation.
+  // So as long as this check catches most cases, and we don't do anything
+  // horrible in one of the cases we can't detect, it should be sufficient.
+
+  nsCSSPropertySet properties;              // All properties encountered.
+  nsCSSPropertySet propertiesWithFromValue; // Those with a defined 0% value.
+  nsCSSPropertySet propertiesWithToValue;   // Those with a defined 100% value.
+
+  auto addToPropertySets = [&](nsCSSProperty aProperty, double aOffset) {
+    properties.AddProperty(aProperty);
+    if (aOffset == 0.0) {
+      propertiesWithFromValue.AddProperty(aProperty);
+    } else if (aOffset == 1.0) {
+      propertiesWithToValue.AddProperty(aProperty);
+    }
+  };
+
+  for (size_t i = 0, len = aKeyframes.Length(); i < len; i++) {
+    const Keyframe& frame = aKeyframes[i];
+
+    // We won't have called ApplyDistributeSpacing when this is called so
+    // we can't use frame.mComputedOffset. Instead we do a rough version
+    // of that algorithm that substitutes null offsets with 0.0 for the first
+    // frame, 1.0 for the last frame, and 0.5 for everything else.
+    double computedOffset = i == len - 1
+                            ? 1.0
+                            : i == 0 ? 0.0 : 0.5;
+    double offsetToUse = frame.mOffset
+                         ? frame.mOffset.value()
+                         : computedOffset;
+
+    for (const PropertyValuePair& pair : frame.mPropertyValues) {
+      if (nsCSSProps::IsShorthand(pair.mProperty)) {
+        nsCSSValueTokenStream* tokenStream = pair.mValue.GetTokenStreamValue();
+        nsCSSParser parser(aDocument->CSSLoader());
+        if (!parser.IsValueValidForProperty(pair.mProperty,
+                                            tokenStream->mTokenStream)) {
+          continue;
+        }
+        CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(
+            prop, pair.mProperty, nsCSSProps::eEnabledForAllContent) {
+          addToPropertySets(*prop, offsetToUse);
+        }
+      } else {
+        if (pair.mValue.GetUnit() == eCSSUnit_TokenStream) {
+          continue;
+        }
+        addToPropertySets(pair.mProperty, offsetToUse);
+      }
+    }
+  }
+
+  return !propertiesWithFromValue.Equals(properties) ||
+         !propertiesWithToValue.Equals(properties);
+}
+
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/animation/KeyframeUtils.h
@@ -0,0 +1,85 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_KeyframeUtils_h
+#define mozilla_KeyframeUtils_h
+
+#include "nsTArrayForwardDeclare.h" // For nsTArray
+#include "js/RootingAPI.h" // For JS::Handle
+
+struct JSContext;
+class JSObject;
+
+namespace mozilla {
+struct AnimationProperty;
+enum class CSSPseudoElementType : uint8_t;
+class ErrorResult;
+struct Keyframe;
+
+namespace dom {
+class Element;
+} // namespace dom
+} // namespace mozilla
+
+
+namespace mozilla {
+
+/**
+ * Utility methods for processing keyframes.
+ */
+class KeyframeUtils
+{
+public:
+  /**
+   * Converts a JS value representing a property-indexed keyframe or a sequence
+   * of keyframes to an array of Keyframe objects.
+   *
+   * @param aCx The JSContext that corresponds to |aFrames|.
+   * @param aFrames The JS value, provided as an optional IDL |object?| value,
+   *   that is the keyframe list specification.
+   * @param aRv (out) Out-param to hold any error returned by this function.
+   *   Must be initially empty.
+   * @return The set of processed keyframes. If an error occurs, aRv will be
+   *   filled-in with the appropriate error code and an empty array will be
+   *   returned.
+   */
+  static nsTArray<Keyframe>
+  GetKeyframesFromObject(JSContext* aCx,
+                         JS::Handle<JSObject*> aFrames,
+                         ErrorResult& aRv);
+
+  /**
+   * Fills in the mComputedOffset member of each keyframe in the given array
+   * using the "distribute" spacing algorithm.
+   *
+   * http://w3c.github.io/web-animations/#distribute-keyframe-spacing-mode
+   *
+   * @param keyframes The set of keyframes to adjust.
+   */
+  static void ApplyDistributeSpacing(nsTArray<Keyframe>& aKeyframes);
+
+  /**
+   * Converts an array of Keyframe objects into an array of AnimationProperty
+   * objects. This involves expanding shorthand properties into longhand
+   * properties, creating an array of computed values for each longhand
+   * property and determining the offset and timing function to use for each
+   * value.
+   *
+   * @param aStyleContext The style context to use when computing values.
+   * @param aFrames The input keyframes.
+   * @return The set of animation properties. If an error occurs, the returned
+   *   array will be empty.
+   */
+  static nsTArray<AnimationProperty>
+  GetAnimationPropertiesFromKeyframes(nsStyleContext* aStyleContext,
+                                      dom::Element* aElement,
+                                      CSSPseudoElementType aPseudoType,
+                                      const nsTArray<Keyframe>& aFrames);
+};
+
+} // namespace mozilla
+
+#endif // mozilla_KeyframeUtils_h
--- a/dom/animation/TimingParams.cpp
+++ b/dom/animation/TimingParams.cpp
@@ -1,18 +1,22 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/TimingParams.h"
 
+#include "mozilla/AnimationUtils.h"
+#include "mozilla/dom/AnimatableBinding.h"
+#include "mozilla/dom/KeyframeEffectBinding.h"
 #include "nsCSSParser.h" // For nsCSSParser
 #include "nsIDocument.h"
+#include "nsRuleNode.h"
 
 namespace mozilla {
 
 template <class OptionsType>
 static const dom::AnimationEffectTimingProperties&
 GetTimingProperties(const OptionsType& aOptions);
 
 template <>
--- a/dom/animation/TimingParams.h
+++ b/dom/animation/TimingParams.h
@@ -7,16 +7,17 @@
 #ifndef mozilla_TimingParams_h
 #define mozilla_TimingParams_h
 
 #include "nsStringFwd.h"
 #include "mozilla/dom/Nullable.h"
 #include "mozilla/dom/UnionTypes.h" // For OwningUnrestrictedDoubleOrString
 #include "mozilla/ComputedTimingFunction.h"
 #include "mozilla/Maybe.h"
+#include "mozilla/StickyTimeDuration.h"
 #include "mozilla/TimeStamp.h" // for TimeDuration
 
 // X11 has a #define for None
 #ifdef None
 #undef None
 #endif
 #include "mozilla/dom/AnimationEffectReadOnlyBinding.h" // for FillMode
                                                         // and PlaybackDirection
--- a/dom/animation/moz.build
+++ b/dom/animation/moz.build
@@ -18,19 +18,21 @@ EXPORTS.mozilla.dom += [
     'KeyframeEffect.h',
 ]
 
 EXPORTS.mozilla += [
     'AnimationComparator.h',
     'AnimationPerformanceWarning.h',
     'AnimationUtils.h',
     'AnimValuesStyleRule.h',
+    'ComputedTiming.h',
     'ComputedTimingFunction.h',
     'EffectCompositor.h',
     'EffectSet.h',
+    'KeyframeUtils.h',
     'NonOwningAnimationTarget.h',
     'PendingAnimationTracker.h',
     'PseudoElementHashEntry.h',
     'TimingParams.h',
 ]
 
 UNIFIED_SOURCES += [
     'Animation.cpp',
@@ -42,16 +44,17 @@ UNIFIED_SOURCES += [
     'AnimationUtils.cpp',
     'AnimValuesStyleRule.cpp',
     'ComputedTimingFunction.cpp',
     'CSSPseudoElement.cpp',
     'DocumentTimeline.cpp',
     'EffectCompositor.cpp',
     'EffectSet.cpp',
     'KeyframeEffect.cpp',
+    'KeyframeUtils.cpp',
     'PendingAnimationTracker.cpp',
     'TimingParams.cpp',
 ]
 
 LOCAL_INCLUDES += [
     '/dom/base',
     '/layout/base',
     '/layout/style',
--- a/dom/animation/test/css-animations/file_keyframeeffect-getframes.html
+++ b/dom/animation/test/css-animations/file_keyframeeffect-getframes.html
@@ -168,19 +168,19 @@ test(function(t) {
   var div = addDiv(t);
 
   div.style.animation = 'anim-simple 100s';
   var frames = getFrames(div);
 
   assert_equals(frames.length, 2, "number of frames");
 
   var expected = [
-    { offset: 0, computedOffset: 0, easing: "ease", composite: "replace",
+    { offset: 0, computedOffset: 0, easing: "ease",
       color: "rgb(0, 0, 0)" },
-    { offset: 1, computedOffset: 1, easing: "linear", composite: "replace",
+    { offset: 1, computedOffset: 1, easing: "linear",
       color: "rgb(255, 255, 255)" },
   ];
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
 }, 'KeyframeEffectReadOnly.getFrames() returns expected frames for a simple ' +
    'animation');
@@ -239,20 +239,20 @@ test(function(t) {
   var div = addDiv(t);
 
   div.style.animation = 'anim-simple-shorthand 100s';
   var frames = getFrames(div);
 
   assert_equals(frames.length, 2, "number of frames");
 
   var expected = [
-    { offset: 0, computedOffset: 0, easing: "ease", composite: "replace",
+    { offset: 0, computedOffset: 0, easing: "ease",
       marginTop: "8px", marginRight: "8px",
       marginBottom: "8px", marginLeft: "8px" },
-    { offset: 1, computedOffset: 1, easing: "linear", composite: "replace",
+    { offset: 1, computedOffset: 1, easing: "linear",
       marginTop: "16px", marginRight: "16px",
       marginBottom: "16px", marginLeft: "16px" },
   ];
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
 }, 'KeyframeEffectReadOnly.getFrames() returns expected frames for a simple ' +
@@ -263,19 +263,19 @@ test(function(t) {
 
   div.style.animation = 'anim-omit-to 100s';
   div.style.color = 'white';
   var frames = getFrames(div);
 
   assert_equals(frames.length, 2, "number of frames");
 
   var expected = [
-    { offset: 0, computedOffset: 0, easing: "ease", composite: "replace",
+    { offset: 0, computedOffset: 0, easing: "ease",
       color: "rgb(0, 0, 255)" },
-    { offset: 1, computedOffset: 1, easing: "linear", composite: "replace",
+    { offset: 1, computedOffset: 1, easing: "linear",
       color: "rgb(255, 255, 255)" },
   ];
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
 }, 'KeyframeEffectReadOnly.getFrames() returns expected frames for an ' +
    'animation with a 0% keyframe and no 100% keyframe');
@@ -285,19 +285,19 @@ test(function(t) {
 
   div.style.animation = 'anim-omit-from 100s';
   div.style.color = 'white';
   var frames = getFrames(div);
 
   assert_equals(frames.length, 2, "number of frames");
 
   var expected = [
-    { offset: 0, computedOffset: 0, easing: "ease", composite: "replace",
+    { offset: 0, computedOffset: 0, easing: "ease",
       color: "rgb(255, 255, 255)" },
-    { offset: 1, computedOffset: 1, easing: "linear", composite: "replace",
+    { offset: 1, computedOffset: 1, easing: "linear",
       color: "rgb(0, 0, 255)" },
   ];
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
 }, 'KeyframeEffectReadOnly.getFrames() returns expected frames for an ' +
    'animation with a 100% keyframe and no 0% keyframe');
@@ -307,21 +307,21 @@ test(function(t) {
 
   div.style.animation = 'anim-omit-from-to 100s';
   div.style.color = 'white';
   var frames = getFrames(div);
 
   assert_equals(frames.length, 3, "number of frames");
 
   var expected = [
-    { offset: 0, computedOffset: 0, easing: "ease", composite: "replace",
+    { offset: 0, computedOffset: 0, easing: "ease",
       color: "rgb(255, 255, 255)" },
-    { offset: 0.5, computedOffset: 0.5, easing: "ease", composite: "replace",
+    { offset: 0.5, computedOffset: 0.5, easing: "ease",
       color: "rgb(0, 0, 255)" },
-    { offset: 1, computedOffset: 1, easing: "linear", composite: "replace",
+    { offset: 1, computedOffset: 1, easing: "linear",
       color: "rgb(255, 255, 255)" },
   ];
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
 }, 'KeyframeEffectReadOnly.getFrames() returns expected frames for an ' +
    'animation with no 0% or 100% keyframe but with a 50% keyframe');
@@ -330,23 +330,23 @@ test(function(t) {
   var div = addDiv(t);
 
   div.style.animation = 'anim-different-props 100s';
   var frames = getFrames(div);
 
   assert_equals(frames.length, 4, "number of frames");
 
   var expected = [
-    { offset: 0, computedOffset: 0, easing: "ease", composite: "replace",
+    { offset: 0, computedOffset: 0, easing: "ease",
       color: "rgb(0, 0, 0)", marginTop: "8px" },
-    { offset: 0.25, computedOffset: 0.25, easing: "ease", composite: "replace",
+    { offset: 0.25, computedOffset: 0.25, easing: "ease",
       color: "rgb(0, 0, 255)" },
-    { offset: 0.75, computedOffset: 0.75, easing: "ease", composite: "replace",
+    { offset: 0.75, computedOffset: 0.75, easing: "ease",
       marginTop: "12px" },
-    { offset: 1, computedOffset: 1, easing: "linear", composite: "replace",
+    { offset: 1, computedOffset: 1, easing: "linear",
       color: "rgb(255, 255, 255)", marginTop: "16px" },
   ];
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
 }, 'KeyframeEffectReadOnly.getFrames() returns expected frames for an ' +
    'animation with different properties on different keyframes, all ' +
@@ -356,23 +356,23 @@ test(function(t) {
   var div = addDiv(t);
 
   div.style.animation = 'anim-different-props-and-easing 100s';
   var frames = getFrames(div);
 
   assert_equals(frames.length, 4, "number of frames");
 
   var expected = [
-    { offset: 0, computedOffset: 0, easing: "linear", composite: "replace",
+    { offset: 0, computedOffset: 0, easing: "linear",
       color: "rgb(0, 0, 0)", marginTop: "8px" },
-    { offset: 0.25, computedOffset: 0.25, easing: "step-end", composite: "replace",
+    { offset: 0.25, computedOffset: 0.25, easing: "step-end",
       color: "rgb(0, 0, 255)" },
-    { offset: 0.75, computedOffset: 0.75, easing: "ease-in", composite: "replace",
+    { offset: 0.75, computedOffset: 0.75, easing: "ease-in",
       marginTop: "12px" },
-    { offset: 1, computedOffset: 1, easing: "linear", composite: "replace",
+    { offset: 1, computedOffset: 1, easing: "linear",
       color: "rgb(255, 255, 255)", marginTop: "16px" },
   ];
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
 }, 'KeyframeEffectReadOnly.getFrames() returns expected frames for an ' +
    'animation with different properties on different keyframes, with ' +
@@ -382,19 +382,19 @@ test(function(t) {
   var div = addDiv(t);
 
   div.style.animation = 'anim-merge-offset 100s';
   var frames = getFrames(div);
 
   assert_equals(frames.length, 2, "number of frames");
 
   var expected = [
-    { offset: 0, computedOffset: 0, easing: "ease", composite: "replace",
+    { offset: 0, computedOffset: 0, easing: "ease",
       color: "rgb(0, 0, 0)", marginTop: "8px" },
-    { offset: 1, computedOffset: 1, easing: "linear", composite: "replace",
+    { offset: 1, computedOffset: 1, easing: "linear",
       color: "rgb(255, 255, 255)", marginTop: "16px" },
   ];
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
 }, 'KeyframeEffectReadOnly.getFrames() returns expected frames for an ' +
    'animation with multiple keyframes for the same time, and all with ' +
@@ -404,21 +404,21 @@ test(function(t) {
   var div = addDiv(t);
 
   div.style.animation = 'anim-merge-offset-and-easing 100s';
   var frames = getFrames(div);
 
   assert_equals(frames.length, 3, "number of frames");
 
   var expected = [
-    { offset: 0, computedOffset: 0, easing: "linear", composite: "replace",
+    { offset: 0, computedOffset: 0, easing: "linear",
       marginTop: "8px", paddingLeft: "2px" },
-    { offset: 0, computedOffset: 0, easing: "step-end", composite: "replace",
+    { offset: 0, computedOffset: 0, easing: "step-end",
       color: "rgb(0, 0, 0)", fontSize: "16px" },
-    { offset: 1, computedOffset: 1, easing: "linear", composite: "replace",
+    { offset: 1, computedOffset: 1, easing: "linear",
       color: "rgb(255, 255, 255)", fontSize: "32px", marginTop: "16px",
       paddingLeft: "4px" },
   ];
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
 }, 'KeyframeEffectReadOnly.getFrames() returns expected frames for an ' +
@@ -429,25 +429,25 @@ test(function(t) {
   var div = addDiv(t);
 
   div.style.animation = 'anim-no-merge-equiv-easing 100s';
   var frames = getFrames(div);
 
   assert_equals(frames.length, 5, "number of frames");
 
   var expected = [
-    { offset: 0, computedOffset: 0, easing: "step-end", composite: "replace",
+    { offset: 0, computedOffset: 0, easing: "step-end",
       marginRight: "0px" },
-    { offset: 0, computedOffset: 0, easing: "steps(1)", composite: "replace",
+    { offset: 0, computedOffset: 0, easing: "steps(1)",
       marginBottom: "0px" },
-    { offset: 0, computedOffset: 0, easing: "steps(1, end)", composite: "replace",
+    { offset: 0, computedOffset: 0, easing: "steps(1, end)",
       marginTop: "0px" },
-    { offset: 0.5, computedOffset: 0.5, easing: "step-end", composite: "replace",
+    { offset: 0.5, computedOffset: 0.5, easing: "step-end",
       marginTop: "10px", marginRight: "10px", marginBottom: "10px" },
-    { offset: 1, computedOffset: 1, easing: "linear", composite: "replace",
+    { offset: 1, computedOffset: 1, easing: "linear",
       marginTop: "20px", marginRight: "20px", marginBottom: "20px" },
   ];
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
 }, 'KeyframeEffectReadOnly.getFrames() returns expected frames for an ' +
    'animation with multiple keyframes for the same time and with ' +
--- a/dom/animation/test/css-transitions/file_keyframeeffect-getframes.html
+++ b/dom/animation/test/css-transitions/file_keyframeeffect-getframes.html
@@ -26,20 +26,18 @@ test(function(t) {
   div.style.transition = 'left 100s';
   div.style.left = '100px';
 
   var frames = getFrames(div);
   
   assert_equals(frames.length, 2, "number of frames");
 
   var expected = [
-    { offset: 0, computedOffset: 0, easing: "ease", composite: "replace",
-      left: "0px" },
-    { offset: 1, computedOffset: 1, easing: "linear", composite: "replace",
-      left: "100px" },
+    { offset: 0, computedOffset: 0, easing: "ease", left: "0px" },
+    { offset: 1, computedOffset: 1, easing: "linear", left: "100px" },
   ];
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
 }, 'KeyframeEffectReadOnly.getFrames() returns expected frames for a simple ' +
    'transition');
 
@@ -51,20 +49,18 @@ test(function(t) {
   div.style.transition = 'left 100s steps(2,end)';
   div.style.left = '100px';
 
   var frames = getFrames(div);
   
   assert_equals(frames.length, 2, "number of frames");
 
   var expected = [
-    { offset: 0, computedOffset: 0, easing: "steps(2, end)", composite: "replace",
-      left: "0px" },
-    { offset: 1, computedOffset: 1, easing: "linear", composite: "replace",
-      left: "100px" },
+    { offset: 0, computedOffset: 0, easing: "steps(2, end)", left: "0px" },
+    { offset: 1, computedOffset: 1, easing: "linear", left: "100px" },
   ];
 
   for (var i = 0; i < frames.length; i++) {
     assert_frames_equal(frames[i], expected[i], "ComputedKeyframe #" + i);
   }
 }, 'KeyframeEffectReadOnly.getFrames() returns expected frames for a simple ' +
    'transition with a non-default easing function');
 
--- a/dom/base/Console.cpp
+++ b/dom/base/Console.cpp
@@ -5,16 +5,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/Console.h"
 #include "mozilla/dom/ConsoleBinding.h"
 
 #include "mozilla/dom/BlobBinding.h"
 #include "mozilla/dom/Exceptions.h"
 #include "mozilla/dom/File.h"
+#include "mozilla/dom/FunctionBinding.h"
 #include "mozilla/dom/StructuredCloneHolder.h"
 #include "mozilla/dom/ToJSValue.h"
 #include "mozilla/Maybe.h"
 #include "nsCycleCollectionParticipant.h"
 #include "nsDocument.h"
 #include "nsDOMNavigationTiming.h"
 #include "nsGlobalWindow.h"
 #include "nsJSUtils.h"
@@ -50,16 +51,19 @@
 // The maximum stacktrace depth when populating the stacktrace array used for
 // console.trace().
 #define DEFAULT_MAX_STACKTRACE_DEPTH 200
 
 // This tags are used in the Structured Clone Algorithm to move js values from
 // worker thread to main thread
 #define CONSOLE_TAG_BLOB   JS_SCTAG_USER_MIN
 
+// This value is taken from ConsoleAPIStorage.js
+#define STORAGE_MAX_EVENTS 200
+
 using namespace mozilla::dom::exceptions;
 using namespace mozilla::dom::workers;
 
 namespace mozilla {
 namespace dom {
 
 struct
 ConsoleStructuredCloneData
@@ -90,38 +94,44 @@ public:
     , mStopTimerStatus(false)
     , mCountValue(MAX_PAGE_COUNTERS)
     , mIDType(eUnknown)
     , mOuterIDNumber(0)
     , mInnerIDNumber(0)
 #ifdef DEBUG
     , mOwningThread(PR_GetCurrentThread())
 #endif
-  { }
-
-  void
+  {}
+
+  bool
   Initialize(JSContext* aCx, Console::MethodName aName,
              const nsAString& aString,
              const Sequence<JS::Value>& aArguments,
              Console* aConsole)
   {
     AssertIsOnOwningThread();
     MOZ_ASSERT(aConsole);
 
-    aConsole->RegisterConsoleCallData(this);
-    mConsole = aConsole;
+    // We must be registered before doing any JS operation otherwise it can
+    // happen that mCopiedArguments are not correctly traced.
+    aConsole->StoreCallData(this);
 
     mMethodName = aName;
     mMethodString = aString;
 
+    mGlobal = JS::CurrentGlobalOrNull(aCx);
+
     for (uint32_t i = 0; i < aArguments.Length(); ++i) {
-      if (!mCopiedArguments.AppendElement(aArguments[i])) {
-        return;
+      if (NS_WARN_IF(!mCopiedArguments.AppendElement(aArguments[i]))) {
+        aConsole->UnstoreCallData(this);
+        return false;
       }
     }
+
+    return true;
   }
 
   void
   SetIDs(uint64_t aOuterID, uint64_t aInnerID)
   {
     MOZ_ASSERT(mIDType == eUnknown);
 
     mOuterIDNumber = aOuterID;
@@ -134,80 +144,87 @@ public:
   {
     MOZ_ASSERT(mIDType == eUnknown);
 
     mOuterIDString = aOuterID;
     mInnerIDString = aInnerID;
     mIDType = eString;
   }
 
-  void
-  CleanupJSObjects()
+  bool
+  PopulateArgumentsSequence(Sequence<JS::Value>& aSequence) const
   {
     AssertIsOnOwningThread();
-    mCopiedArguments.Clear();
-
-    if (mConsole) {
-      mConsole->UnregisterConsoleCallData(this);
-      mConsole = nullptr;
+
+    for (uint32_t i = 0; i < mCopiedArguments.Length(); ++i) {
+      if (NS_WARN_IF(!aSequence.AppendElement(mCopiedArguments[i],
+                                              fallible))) {
+        return false;
+      }
     }
+
+    return true;
   }
 
   void
   Trace(const TraceCallbacks& aCallbacks, void* aClosure)
   {
     AssertIsOnOwningThread();
 
     ConsoleCallData* tmp = this;
     for (uint32_t i = 0; i < mCopiedArguments.Length(); ++i) {
       NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mCopiedArguments[i])
     }
+
+    NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mGlobal);
   }
 
   void
   AssertIsOnOwningThread() const
   {
     MOZ_ASSERT(mOwningThread);
     MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
   }
 
+  JS::Heap<JSObject*> mGlobal;
+
   // This is a copy of the arguments we received from the DOM bindings. Console
   // object traces them because this ConsoleCallData calls
   // RegisterConsoleCallData() in the Initialize().
   nsTArray<JS::Heap<JS::Value>> mCopiedArguments;
 
   Console::MethodName mMethodName;
   bool mPrivate;
   int64_t mTimeStamp;
 
   // These values are set in the owning thread and they contain the timestamp of
   // when the new timer has started, the name of it and the status of the
   // creation of it. If status is false, something went wrong. User
   // DOMHighResTimeStamp instead mozilla::TimeStamp because we use
   // monotonicTimer from Performance.now();
   // They will be set on the owning thread and never touched again on that
-  // thread. They will be used on the main-thread in order to create a
-  // ConsoleTimerStart dictionary when console.time() is used.
+  // thread. They will be used in order to create a ConsoleTimerStart dictionary
+  // when console.time() is used.
   DOMHighResTimeStamp mStartTimerValue;
   nsString mStartTimerLabel;
   bool mStartTimerStatus;
 
   // These values are set in the owning thread and they contain the duration,
   // the name and the status of the StopTimer method. If status is false,
   // something went wrong. They will be set on the owning thread and never
-  // touched again on that thread. They will be used on the main-thread in order
-  // to create a ConsoleTimerEnd dictionary. This members are set when
+  // touched again on that thread. They will be used in order to create a
+  // ConsoleTimerEnd dictionary. This members are set when
   // console.timeEnd() is called.
   double mStopTimerDuration;
   nsString mStopTimerLabel;
   bool mStopTimerStatus;
 
   // These 2 values are set by IncreaseCounter on the owning thread and they are
-  // used on the main-thread by CreateCounterValue. These members are set when
-  // console.count() is called.
+  // used CreateCounterValue. These members are set when console.count() is
+  // called.
   nsString mCountLabel;
   uint32_t mCountValue;
 
   // The concept of outerID and innerID is misleading because when a
   // ConsoleCallData is created from a window, these are the window IDs, but
   // when the object is created from a SharedWorker, a ServiceWorker or a
   // subworker of a ChromeWorker these IDs are the type of worker and the
   // filename of the callee.
@@ -231,28 +248,46 @@ public:
   // 1)  mTopStackFrame is initialized whenever we have any JS on the stack
   // 2)  mReifiedStack is initialized if we're created in a worker.
   // 3)  mStack is set (possibly to null if there is no JS on the stack) if
   //     we're created on main thread.
   Maybe<ConsoleStackEntry> mTopStackFrame;
   Maybe<nsTArray<ConsoleStackEntry>> mReifiedStack;
   nsCOMPtr<nsIStackFrame> mStack;
 
+  // mStatus is about the lifetime of this object. Console must take care of
+  // keep it alive or not following this enumeration.
+  enum {
+    // If the object is created but it is owned by some runnable, this is its
+    // status. It can be deleted at any time.
+    eUnused,
+
+    // When a runnable takes ownership of a ConsoleCallData and send it to
+    // different thread, this is its status. Console cannot delete it at this
+    // time.
+    eInUse,
+
+    // When a runnable owns this ConsoleCallData, we can't delete it directly.
+    // instead, we mark it with this new status and we move it in
+    // mCallDataStoragePending list in order to keep it alive an trace it
+    // correctly. Once the runnable finishs its task, it will delete this object
+    // calling ReleaseCallData().
+    eToBeDeleted
+  } mStatus;
+
 #ifdef DEBUG
   PRThread* mOwningThread;
 #endif
 
 private:
   ~ConsoleCallData()
   {
     AssertIsOnOwningThread();
-    CleanupJSObjects();
+    MOZ_ASSERT(mStatus != eInUse);
   }
-
-  RefPtr<Console> mConsole;
 };
 
 // This class is used to clear any exception at the end of this method.
 class ClearException
 {
 public:
   explicit ClearException(JSContext* aCx)
     : mCx(aCx)
@@ -283,43 +318,36 @@ public:
   virtual
   ~ConsoleRunnable()
   {
     // Clear the StructuredCloneHolderBase class.
     Clear();
   }
 
   bool
-  Dispatch(JS::Handle<JSObject*> aGlobal)
+  Dispatch(JSContext* aCx)
   {
-    mWorkerPrivate->AssertIsOnWorkerThread();
-
-    JSContext* cx = mWorkerPrivate->GetJSContext();
-
-    if (!PreDispatch(cx, aGlobal)) {
+    if (!DispatchInternal(aCx)) {
+      ReleaseData();
       return false;
     }
 
-    if (NS_WARN_IF(!mWorkerPrivate->AddFeature(this))) {
-      return false;
-    }
-
-    MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(this)));
     return true;
   }
 
   virtual bool Notify(JSContext* aCx, workers::Status aStatus) override
   {
     // We don't care about the notification. We just want to keep the
     // mWorkerPrivate alive.
     return true;
   }
 
 private:
-  NS_IMETHOD Run() override
+  NS_IMETHOD
+  Run() override
   {
     AssertIsOnMainThread();
 
     // Walk up to our containing page
     WorkerPrivate* wp = mWorkerPrivate;
     while (wp->GetParent()) {
       wp = wp->GetParent();
     }
@@ -330,16 +358,36 @@ private:
     } else {
       RunWithWindow(window);
     }
 
     PostDispatch();
     return NS_OK;
   }
 
+  bool
+  DispatchInternal(JSContext* aCx)
+  {
+    mWorkerPrivate->AssertIsOnWorkerThread();
+
+    if (NS_WARN_IF(!PreDispatch(aCx))) {
+      return false;
+    }
+
+    if (NS_WARN_IF(!mWorkerPrivate->AddFeature(this))) {
+      return false;
+    }
+
+    if (NS_WARN_IF(NS_FAILED(NS_DispatchToMainThread(this)))) {
+      return false;
+    }
+
+    return true;
+  }
+
   void
   PostDispatch()
   {
     class ConsoleReleaseRunnable final : public MainThreadWorkerControlRunnable
     {
       RefPtr<ConsoleRunnable> mRunnable;
 
     public:
@@ -366,17 +414,17 @@ private:
 
     private:
       ~ConsoleReleaseRunnable()
       {}
     };
 
     RefPtr<WorkerControlRunnable> runnable =
       new ConsoleReleaseRunnable(mWorkerPrivate, this);
-    runnable->Dispatch();
+    NS_WARN_IF(!runnable->Dispatch());
   }
 
   void
   RunWithWindow(nsPIDOMWindowInner* aWindow)
   {
     AssertIsOnMainThread();
 
     AutoJSAPI jsapi;
@@ -422,17 +470,17 @@ private:
     JSAutoCompartment ac(cx, global);
 
     RunConsole(cx, nullptr, nullptr);
   }
 
 protected:
   // This method is called in the owning thread of the Console object.
   virtual bool
-  PreDispatch(JSContext* aCx, JS::Handle<JSObject*> aGlobal) = 0;
+  PreDispatch(JSContext* aCx) = 0;
 
   // This method is called in the main-thread.
   virtual void
   RunConsole(JSContext* aCx, nsPIDOMWindowOuter* aOuterWindow,
              nsPIDOMWindowInner* aInnerWindow) = 0;
 
   // This method is called in the owning thread of the Console object.
   virtual void
@@ -466,32 +514,32 @@ protected:
 
   virtual bool CustomWriteHandler(JSContext* aCx,
                                   JSStructuredCloneWriter* aWriter,
                                   JS::Handle<JSObject*> aObj) override
   {
     RefPtr<Blob> blob;
     if (NS_SUCCEEDED(UNWRAP_OBJECT(Blob, aObj, blob)) &&
         blob->Impl()->MayBeClonedToOtherThreads()) {
-      if (!JS_WriteUint32Pair(aWriter, CONSOLE_TAG_BLOB,
-                              mClonedData.mBlobs.Length())) {
+      if (NS_WARN_IF(!JS_WriteUint32Pair(aWriter, CONSOLE_TAG_BLOB,
+                                         mClonedData.mBlobs.Length()))) {
         return false;
       }
 
       mClonedData.mBlobs.AppendElement(blob->Impl());
       return true;
     }
 
     JS::Rooted<JS::Value> value(aCx, JS::ObjectOrNullValue(aObj));
     JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
-    if (!jsString) {
+    if (NS_WARN_IF(!jsString)) {
       return false;
     }
 
-    if (!JS_WriteString(aWriter, jsString)) {
+    if (NS_WARN_IF(!JS_WriteString(aWriter, jsString))) {
       return false;
     }
 
     return true;
   }
 
   WorkerPrivate* mWorkerPrivate;
 
@@ -505,57 +553,66 @@ protected:
 // the main-thread.
 class ConsoleCallDataRunnable final : public ConsoleRunnable
 {
 public:
   ConsoleCallDataRunnable(Console* aConsole,
                           ConsoleCallData* aCallData)
     : ConsoleRunnable(aConsole)
     , mCallData(aCallData)
-  { }
+  {
+    MOZ_ASSERT(aCallData);
+    mWorkerPrivate->AssertIsOnWorkerThread();
+    mCallData->AssertIsOnOwningThread();
+  }
 
 private:
+  ~ConsoleCallDataRunnable()
+  {
+    MOZ_ASSERT(!mCallData);
+  }
+
   bool
-  PreDispatch(JSContext* aCx, JS::Handle<JSObject*> aGlobal) override
+  PreDispatch(JSContext* aCx) override
   {
     mWorkerPrivate->AssertIsOnWorkerThread();
+    mCallData->AssertIsOnOwningThread();
 
     ClearException ce(aCx);
-    JSAutoCompartment ac(aCx, aGlobal);
 
     JS::Rooted<JSObject*> arguments(aCx,
       JS_NewArrayObject(aCx, mCallData->mCopiedArguments.Length()));
-    if (!arguments) {
+    if (NS_WARN_IF(!arguments)) {
       return false;
     }
 
     JS::Rooted<JS::Value> arg(aCx);
     for (uint32_t i = 0; i < mCallData->mCopiedArguments.Length(); ++i) {
       arg = mCallData->mCopiedArguments[i];
-      if (!JS_DefineElement(aCx, arguments, i, arg, JSPROP_ENUMERATE)) {
+      if (NS_WARN_IF(!JS_DefineElement(aCx, arguments, i, arg,
+                                       JSPROP_ENUMERATE))) {
         return false;
       }
     }
 
     JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
 
-    if (!Write(aCx, value)) {
+    if (NS_WARN_IF(!Write(aCx, value))) {
       return false;
     }
 
-    mCallData->CleanupJSObjects();
+    mCallData->mStatus = ConsoleCallData::eInUse;
     return true;
   }
 
   void
   RunConsole(JSContext* aCx, nsPIDOMWindowOuter* aOuterWindow,
              nsPIDOMWindowInner* aInnerWindow) override
   {
     AssertIsOnMainThread();
-    MOZ_ASSERT(mCallData->mCopiedArguments.IsEmpty());
 
     // The windows have to run in parallel.
     MOZ_ASSERT(!!aOuterWindow == !!aInnerWindow);
 
     if (aOuterWindow) {
       mCallData->SetIDs(aOuterWindow->WindowID(), aInnerWindow->WindowID());
     } else {
       ConsoleStackEntry frame;
@@ -585,16 +642,25 @@ private:
     ProcessCallData(aCx);
 
     mClonedData.mParent = nullptr;
   }
 
   virtual void
   ReleaseData() override
   {
+    mConsole->AssertIsOnOwningThread();
+
+    if (mCallData->mStatus == ConsoleCallData::eToBeDeleted) {
+      mConsole->ReleaseCallData(mCallData);
+    } else {
+      MOZ_ASSERT(mCallData->mStatus == ConsoleCallData::eInUse);
+      mCallData->mStatus = ConsoleCallData::eUnused;
+    }
+
     mCallData = nullptr;
   }
 
   void
   ProcessCallData(JSContext* aCx)
   {
     AssertIsOnMainThread();
 
@@ -626,19 +692,17 @@ private:
 
       if (!values.AppendElement(value, fallible)) {
         return;
       }
     }
 
     MOZ_ASSERT(values.Length() == length);
 
-    JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
-
-    mConsole->ProcessCallData(mCallData, global, values);
+    mConsole->ProcessCallData(aCx, mCallData, values);
   }
 
   RefPtr<ConsoleCallData> mCallData;
 };
 
 // This runnable calls ProfileMethod() on the console on the main-thread.
 class ConsoleProfileRunnable final : public ConsoleRunnable
 {
@@ -649,44 +713,38 @@ public:
     , mAction(aAction)
     , mArguments(aArguments)
   {
     MOZ_ASSERT(aConsole);
   }
 
 private:
   bool
-  PreDispatch(JSContext* aCx, JS::Handle<JSObject*> aGlobal) override
+  PreDispatch(JSContext* aCx) override
   {
     ClearException ce(aCx);
 
-    JS::Rooted<JSObject*> global(aCx, aGlobal);
-    if (!global) {
-      return false;
-    }
-
-    JSAutoCompartment ac(aCx, global);
-
     JS::Rooted<JSObject*> arguments(aCx,
       JS_NewArrayObject(aCx, mArguments.Length()));
-    if (!arguments) {
+    if (NS_WARN_IF(!arguments)) {
       return false;
     }
 
     JS::Rooted<JS::Value> arg(aCx);
     for (uint32_t i = 0; i < mArguments.Length(); ++i) {
       arg = mArguments[i];
-      if (!JS_DefineElement(aCx, arguments, i, arg, JSPROP_ENUMERATE)) {
+      if (NS_WARN_IF(!JS_DefineElement(aCx, arguments, i, arg,
+                                       JSPROP_ENUMERATE))) {
         return false;
       }
     }
 
     JS::Rooted<JS::Value> value(aCx, JS::ObjectValue(*arguments));
 
-    if (!Write(aCx, value)) {
+    if (NS_WARN_IF(!Write(aCx, value))) {
       return false;
     }
 
     return true;
   }
 
   void
   RunConsole(JSContext* aCx, nsPIDOMWindowOuter* aOuterWindow,
@@ -747,121 +805,184 @@ private:
 NS_IMPL_CYCLE_COLLECTION_CLASS(Console)
 
 // We don't need to traverse/unlink mStorage and mSandbox because they are not
 // CCed objects and they are only used on the main thread, even when this
 // Console object is used on workers.
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(Console)
   NS_IMPL_CYCLE_COLLECTION_UNLINK(mWindow)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK(mConsoleEventNotifier)
+  tmp->Shutdown();
   NS_IMPL_CYCLE_COLLECTION_UNLINK_PRESERVED_WRAPPER
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(Console)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mWindow)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mConsoleEventNotifier)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE_SCRIPT_OBJECTS
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(Console)
-  for (uint32_t i = 0; i < tmp->mConsoleCallDataArray.Length(); ++i) {
-    tmp->mConsoleCallDataArray[i]->Trace(aCallbacks, aClosure);
+  for (uint32_t i = 0; i < tmp->mCallDataStorage.Length(); ++i) {
+    tmp->mCallDataStorage[i]->Trace(aCallbacks, aClosure);
+  }
+
+  for (uint32_t i = 0; i < tmp->mCallDataStoragePending.Length(); ++i) {
+    tmp->mCallDataStoragePending[i]->Trace(aCallbacks, aClosure);
   }
 
   NS_IMPL_CYCLE_COLLECTION_TRACE_PRESERVED_WRAPPER
 NS_IMPL_CYCLE_COLLECTION_TRACE_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(Console)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(Console)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(Console)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsIObserver)
   NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIObserver)
   NS_INTERFACE_MAP_ENTRY(nsISupportsWeakReference)
 NS_INTERFACE_MAP_END
 
+/* static */ already_AddRefed<Console>
+Console::Create(nsPIDOMWindowInner* aWindow, ErrorResult& aRv)
+{
+  RefPtr<Console> console = new Console(aWindow);
+  console->Initialize(aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  return console.forget();
+}
+
 Console::Console(nsPIDOMWindowInner* aWindow)
   : mWindow(aWindow)
 #ifdef DEBUG
   , mOwningThread(PR_GetCurrentThread())
 #endif
   , mOuterID(0)
   , mInnerID(0)
+  , mStatus(eUnknown)
 {
   if (mWindow) {
     MOZ_ASSERT(mWindow->IsInnerWindow());
     mInnerID = mWindow->WindowID();
 
     // Without outerwindow any console message coming from this object will not
     // shown in the devtools webconsole. But this should be fine because
     // probably we are shutting down, or the window is CCed/GCed.
     nsPIDOMWindowOuter* outerWindow = mWindow->GetOuterWindow();
     if (outerWindow) {
       mOuterID = outerWindow->WindowID();
     }
   }
 
-  if (NS_IsMainThread()) {
-    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
-    if (obs) {
-      obs->AddObserver(this, "inner-window-destroyed", true);
-    }
-  }
-
   mozilla::HoldJSObjects(this);
 }
 
 Console::~Console()
 {
   AssertIsOnOwningThread();
-  MOZ_ASSERT(mConsoleCallDataArray.IsEmpty());
-
-  if (!NS_IsMainThread()) {
-    if (mStorage) {
-      NS_ReleaseOnMainThread(mStorage.forget());
+  Shutdown();
+  mozilla::DropJSObjects(this);
+}
+
+void
+Console::Initialize(ErrorResult& aRv)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(mStatus == eUnknown);
+
+  if (NS_IsMainThread()) {
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    if (NS_WARN_IF(!obs)) {
+      aRv.Throw(NS_ERROR_FAILURE);
+      return;
     }
 
-    if (mSandbox) {
-      NS_ReleaseOnMainThread(mSandbox.forget());
+    aRv = obs->AddObserver(this, "inner-window-destroyed", true);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return;
+    }
+
+    aRv = obs->AddObserver(this, "memory-pressure", true);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return;
     }
   }
 
-  mozilla::DropJSObjects(this);
+  mStatus = eInitialized;
+}
+
+void
+Console::Shutdown()
+{
+  AssertIsOnOwningThread();
+
+  if (mStatus == eUnknown || mStatus == eShuttingDown) {
+    return;
+  }
+
+  if (NS_IsMainThread()) {
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    if (obs) {
+      obs->RemoveObserver(this, "inner-window-destroyed");
+      obs->RemoveObserver(this, "memory-pressure");
+    }
+  }
+
+  NS_ReleaseOnMainThread(mStorage.forget());
+  NS_ReleaseOnMainThread(mSandbox.forget());
+
+  mTimerRegistry.Clear();
+  mCounterRegistry.Clear();
+
+  mCallDataStorage.Clear();
+  mCallDataStoragePending.Clear();
+
+  mStatus = eShuttingDown;
 }
 
 NS_IMETHODIMP
 Console::Observe(nsISupports* aSubject, const char* aTopic,
                  const char16_t* aData)
 {
   AssertIsOnMainThread();
 
-  if (strcmp(aTopic, "inner-window-destroyed")) {
+  if (!strcmp(aTopic, "inner-window-destroyed")) {
+    nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
+    NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
+
+    uint64_t innerID;
+    nsresult rv = wrapper->GetData(&innerID);
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    if (innerID == mInnerID) {
+      Shutdown();
+    }
+
     return NS_OK;
   }
 
-  nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
-  NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
-
-  uint64_t innerID;
-  nsresult rv = wrapper->GetData(&innerID);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  if (innerID == mInnerID) {
-    nsCOMPtr<nsIObserverService> obs =
-      do_GetService("@mozilla.org/observer-service;1");
-    if (obs) {
-      obs->RemoveObserver(this, "inner-window-destroyed");
-    }
-
-    mTimerRegistry.Clear();
+  if (!strcmp(aTopic, "memory-pressure")) {
+    ClearStorage();
+    return NS_OK;
   }
 
   return NS_OK;
 }
 
+void
+Console::ClearStorage()
+{
+  mCallDataStorage.Clear();
+}
+
 JSObject*
 Console::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return ConsoleBinding::Wrap(aCx, this, aGivenProto);
 }
 
 #define METHOD(name, string)                                          \
   void                                                                \
@@ -877,16 +998,17 @@ METHOD(Error, "error")
 METHOD(Exception, "exception")
 METHOD(Debug, "debug")
 METHOD(Table, "table")
 
 void
 Console::Trace(JSContext* aCx)
 {
   AssertIsOnOwningThread();
+  MOZ_ASSERT(mStatus == eInitialized);
 
   const Sequence<JS::Value> data;
   Method(aCx, MethodTrace, NS_LITERAL_STRING("trace"), data);
 }
 
 // Displays an interactive listing of all the properties of an object.
 METHOD(Dir, "dir");
 METHOD(Dirxml, "dirxml");
@@ -894,82 +1016,91 @@ METHOD(Dirxml, "dirxml");
 METHOD(Group, "group")
 METHOD(GroupCollapsed, "groupCollapsed")
 METHOD(GroupEnd, "groupEnd")
 
 void
 Console::Time(JSContext* aCx, const JS::Handle<JS::Value> aTime)
 {
   AssertIsOnOwningThread();
+  MOZ_ASSERT(mStatus == eInitialized);
 
   Sequence<JS::Value> data;
   SequenceRooter<JS::Value> rooter(aCx, &data);
 
   if (!aTime.isUndefined() && !data.AppendElement(aTime, fallible)) {
     return;
   }
 
   Method(aCx, MethodTime, NS_LITERAL_STRING("time"), data);
 }
 
 void
 Console::TimeEnd(JSContext* aCx, const JS::Handle<JS::Value> aTime)
 {
   AssertIsOnOwningThread();
+  MOZ_ASSERT(mStatus == eInitialized);
 
   Sequence<JS::Value> data;
   SequenceRooter<JS::Value> rooter(aCx, &data);
 
   if (!aTime.isUndefined() && !data.AppendElement(aTime, fallible)) {
     return;
   }
 
   Method(aCx, MethodTimeEnd, NS_LITERAL_STRING("timeEnd"), data);
 }
 
 void
 Console::TimeStamp(JSContext* aCx, const JS::Handle<JS::Value> aData)
 {
   AssertIsOnOwningThread();
+  MOZ_ASSERT(mStatus == eInitialized);
 
   Sequence<JS::Value> data;
   SequenceRooter<JS::Value> rooter(aCx, &data);
 
   if (aData.isString() && !data.AppendElement(aData, fallible)) {
     return;
   }
 
   Method(aCx, MethodTimeStamp, NS_LITERAL_STRING("timeStamp"), data);
 }
 
 void
 Console::Profile(JSContext* aCx, const Sequence<JS::Value>& aData)
 {
   AssertIsOnOwningThread();
+  MOZ_ASSERT(mStatus == eInitialized);
+
   ProfileMethod(aCx, NS_LITERAL_STRING("profile"), aData);
 }
 
 void
 Console::ProfileEnd(JSContext* aCx, const Sequence<JS::Value>& aData)
 {
   AssertIsOnOwningThread();
+  MOZ_ASSERT(mStatus == eInitialized);
+
   ProfileMethod(aCx, NS_LITERAL_STRING("profileEnd"), aData);
 }
 
 void
 Console::ProfileMethod(JSContext* aCx, const nsAString& aAction,
                        const Sequence<JS::Value>& aData)
 {
+  MOZ_ASSERT(mStatus == eInitialized);
+
   if (!NS_IsMainThread()) {
     // Here we are in a worker thread.
     RefPtr<ConsoleProfileRunnable> runnable =
       new ConsoleProfileRunnable(this, aAction, aData);
 
     JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
-    runnable->Dispatch(global);
+    runnable->Dispatch(aCx);
     return;
   }
 
   ClearException ce(aCx);
 
   RootedDictionary<ConsoleProfileEvent> event(aCx);
   event.mAction = aAction;
 
@@ -998,40 +1129,41 @@ Console::ProfileMethod(JSContext* aCx, c
   nsXPConnect*  xpc = nsXPConnect::XPConnect();
   nsCOMPtr<nsISupports> wrapper;
   const nsIID& iid = NS_GET_IID(nsISupports);
 
   if (NS_FAILED(xpc->WrapJS(aCx, eventObj, iid, getter_AddRefs(wrapper)))) {
     return;
   }
 
-  nsCOMPtr<nsIObserverService> obs =
-    do_GetService("@mozilla.org/observer-service;1");
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   if (obs) {
     obs->NotifyObservers(wrapper, "console-api-profiler", nullptr);
   }
 }
 
 void
 Console::Assert(JSContext* aCx, bool aCondition,
                 const Sequence<JS::Value>& aData)
 {
   AssertIsOnOwningThread();
+  MOZ_ASSERT(mStatus == eInitialized);
 
   if (!aCondition) {
     Method(aCx, MethodAssert, NS_LITERAL_STRING("assert"), aData);
   }
 }
 
 METHOD(Count, "count")
 
 void
 Console::NoopMethod()
 {
   AssertIsOnOwningThread();
+  MOZ_ASSERT(mStatus == eInitialized);
 
   // Nothing to do.
 }
 
 static
 nsresult
 StackFrameToStackEntry(JSContext* aCx, nsIStackFrame* aStackFrame,
                        ConsoleStackEntry& aStackEntry)
@@ -1095,22 +1227,26 @@ ReifyStack(JSContext* aCx, nsIStackFrame
 
 // Queue a call to a console method. See the CALL_DELAY constant.
 void
 Console::Method(JSContext* aCx, MethodName aMethodName,
                 const nsAString& aMethodString,
                 const Sequence<JS::Value>& aData)
 {
   AssertIsOnOwningThread();
+  MOZ_ASSERT(mStatus == eInitialized);
 
   RefPtr<ConsoleCallData> callData(new ConsoleCallData());
 
   ClearException ce(aCx);
 
-  callData->Initialize(aCx, aMethodName, aMethodString, aData, this);
+  if (NS_WARN_IF(!callData->Initialize(aCx, aMethodName, aMethodString,
+                                       aData, this))) {
+    return;
+  }
 
   if (mWindow) {
     nsCOMPtr<nsIWebNavigation> webNav = do_GetInterface(mWindow);
     if (!webNav) {
       return;
     }
 
     nsCOMPtr<nsILoadContext> loadContext = do_QueryInterface(webNav);
@@ -1226,246 +1362,107 @@ Console::Method(JSContext* aCx, MethodNa
     if (callData->mTopStackFrame) {
       frame = *callData->mTopStackFrame;
     }
 
     callData->mCountValue = IncreaseCounter(aCx, frame, aData,
                                             callData->mCountLabel);
   }
 
-  JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
-
   if (NS_IsMainThread()) {
     callData->SetIDs(mOuterID, mInnerID);
-    ProcessCallData(callData, global, aData);
+    ProcessCallData(aCx, callData, aData);
+
+    // Just because we don't want to expose
+    // retrieveConsoleEvents/setConsoleEventHandler to main-thread, we can
+    // cleanup the mCallDataStorage:
+    UnstoreCallData(callData);
     return;
   }
 
+  // We do this only in workers for now.
+  NotifyHandler(aCx, aData, callData);
+
   RefPtr<ConsoleCallDataRunnable> runnable =
     new ConsoleCallDataRunnable(this, callData);
-  runnable->Dispatch(global);
+  NS_WARN_IF(!runnable->Dispatch(aCx));
 }
 
 // We store information to lazily compute the stack in the reserved slots of
 // LazyStackGetter.  The first slot always stores a JS object: it's either the
 // JS wrapper of the nsIStackFrame or the actual reified stack representation.
 // The second slot is a PrivateValue() holding an nsIStackFrame* when we haven't
 // reified the stack yet, or an UndefinedValue() otherwise.
 enum {
   SLOT_STACKOBJ,
   SLOT_RAW_STACK
 };
 
 bool
 LazyStackGetter(JSContext* aCx, unsigned aArgc, JS::Value* aVp)
 {
-  AssertIsOnMainThread();
-
   JS::CallArgs args = CallArgsFromVp(aArgc, aVp);
   JS::Rooted<JSObject*> callee(aCx, &args.callee());
 
   JS::Value v = js::GetFunctionNativeReserved(&args.callee(), SLOT_RAW_STACK);
   if (v.isUndefined()) {
     // Already reified.
     args.rval().set(js::GetFunctionNativeReserved(callee, SLOT_STACKOBJ));
     return true;
   }
 
   nsIStackFrame* stack = reinterpret_cast<nsIStackFrame*>(v.toPrivate());
   nsTArray<ConsoleStackEntry> reifiedStack;
   nsresult rv = ReifyStack(aCx, stack, reifiedStack);
-  if (NS_FAILED(rv)) {
+  if (NS_WARN_IF(NS_FAILED(rv))) {
     Throw(aCx, rv);
     return false;
   }
 
   JS::Rooted<JS::Value> stackVal(aCx);
-  if (!ToJSValue(aCx, reifiedStack, &stackVal)) {
+  if (NS_WARN_IF(!ToJSValue(aCx, reifiedStack, &stackVal))) {
     return false;
   }
 
   MOZ_ASSERT(stackVal.isObject());
 
   js::SetFunctionNativeReserved(callee, SLOT_STACKOBJ, stackVal);
   js::SetFunctionNativeReserved(callee, SLOT_RAW_STACK, JS::UndefinedValue());
 
   args.rval().set(stackVal);
   return true;
 }
 
 void
-Console::ProcessCallData(ConsoleCallData* aData, JS::Handle<JSObject*> aGlobal,
+Console::ProcessCallData(JSContext* aCx, ConsoleCallData* aData,
                          const Sequence<JS::Value>& aArguments)
 {
   AssertIsOnMainThread();
   MOZ_ASSERT(aData);
 
-  ConsoleStackEntry frame;
-  if (aData->mTopStackFrame) {
-    frame = *aData->mTopStackFrame;
-  }
-
-  AutoJSAPI jsapi;
-  if (!jsapi.Init(aGlobal)) {
-    return;
-  }
-  JSContext* cx = jsapi.cx();
-  ClearException ce(cx);
-  RootedDictionary<ConsoleEvent> event(cx);
-
-  event.mID.Construct();
-  event.mInnerID.Construct();
-
-  MOZ_ASSERT(aData->mIDType != ConsoleCallData::eUnknown);
-  if (aData->mIDType == ConsoleCallData::eString) {
-    event.mID.Value().SetAsString() = aData->mOuterIDString;
-    event.mInnerID.Value().SetAsString() = aData->mInnerIDString;
-  } else {
-    MOZ_ASSERT(aData->mIDType == ConsoleCallData::eNumber);
-    event.mID.Value().SetAsUnsignedLongLong() = aData->mOuterIDNumber;
-    event.mInnerID.Value().SetAsUnsignedLongLong() = aData->mInnerIDNumber;
-  }
-
-  event.mLevel = aData->mMethodString;
-  event.mFilename = frame.mFilename;
-
-  nsCOMPtr<nsIURI> filenameURI;
-  nsAutoCString pass;
-  if (NS_SUCCEEDED(NS_NewURI(getter_AddRefs(filenameURI), frame.mFilename)) &&
-      NS_SUCCEEDED(filenameURI->GetPassword(pass)) && !pass.IsEmpty()) {
-    nsCOMPtr<nsISensitiveInfoHiddenURI> safeURI = do_QueryInterface(filenameURI);
-    nsAutoCString spec;
-    if (safeURI &&
-        NS_SUCCEEDED(safeURI->GetSensitiveInfoHiddenSpec(spec))) {
-      CopyUTF8toUTF16(spec, event.mFilename);
-    }
-  }
-
-  event.mLineNumber = frame.mLineNumber;
-  event.mColumnNumber = frame.mColumnNumber;
-  event.mFunctionName = frame.mFunctionName;
-  event.mTimeStamp = aData->mTimeStamp;
-  event.mPrivate = aData->mPrivate;
-
-  switch (aData->mMethodName) {
-    case MethodLog:
-    case MethodInfo:
-    case MethodWarn:
-    case MethodError:
-    case MethodException:
-    case MethodDebug:
-    case MethodAssert:
-      event.mArguments.Construct();
-      event.mStyles.Construct();
-      if (!ProcessArguments(cx, aArguments, event.mArguments.Value(),
-                            event.mStyles.Value())) {
-        return;
-      }
-
-      break;
-
-    default:
-      event.mArguments.Construct();
-      if (!ArgumentsToValueList(aArguments, event.mArguments.Value())) {
-        return;
-      }
-  }
-
-  if (aData->mMethodName == MethodGroup ||
-      aData->mMethodName == MethodGroupCollapsed ||
-      aData->mMethodName == MethodGroupEnd) {
-    ComposeGroupName(cx, aArguments, event.mGroupName);
-  }
-
-  else if (aData->mMethodName == MethodTime && !aArguments.IsEmpty()) {
-    event.mTimer = CreateStartTimerValue(cx, aData->mStartTimerLabel,
-                                         aData->mStartTimerValue,
-                                         aData->mStartTimerStatus);
-  }
-
-  else if (aData->mMethodName == MethodTimeEnd && !aArguments.IsEmpty()) {
-    event.mTimer = CreateStopTimerValue(cx, aData->mStopTimerLabel,
-                                        aData->mStopTimerDuration,
-                                        aData->mStopTimerStatus);
-  }
-
-  else if (aData->mMethodName == MethodCount) {
-    event.mCounter = CreateCounterValue(cx, aData->mCountLabel,
-                                        aData->mCountValue);
-  }
+  JS::Rooted<JS::Value> eventValue(aCx);
 
   // We want to create a console event object and pass it to our
   // nsIConsoleAPIStorage implementation.  We want to define some accessor
   // properties on this object, and those will need to keep an nsIStackFrame
   // alive.  But nsIStackFrame cannot be wrapped in an untrusted scope.  And
   // further, passing untrusted objects to system code is likely to run afoul of
   // Object Xrays.  So we want to wrap in a system-principal scope here.  But
   // which one?  We could cheat and try to get the underlying JSObject* of
   // mStorage, but that's a bit fragile.  Instead, we just use the junk scope,
   // with explicit permission from the XPConnect module owner.  If you're
   // tempted to do that anywhere else, talk to said module owner first.
-  JSAutoCompartment ac2(cx, xpc::PrivilegedJunkScope());
-
-  JS::Rooted<JS::Value> eventValue(cx);
-  if (!ToJSValue(cx, event, &eventValue)) {
-    return;
-  }
-
-  JS::Rooted<JSObject*> eventObj(cx, &eventValue.toObject());
-  MOZ_ASSERT(eventObj);
-
-  if (!JS_DefineProperty(cx, eventObj, "wrappedJSObject", eventValue, JSPROP_ENUMERATE)) {
+
+  // aCx and aArguments are in the same compartment.
+  if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope(aCx, aArguments,
+                                                              xpc::PrivilegedJunkScope(),
+                                                              &eventValue, aData))) {
     return;
   }
 
-  if (ShouldIncludeStackTrace(aData->mMethodName)) {
-    // Now define the "stacktrace" property on eventObj.  There are two cases
-    // here.  Either we came from a worker and have a reified stack, or we want
-    // to define a getter that will lazily reify the stack.
-    if (aData->mReifiedStack) {
-      JS::Rooted<JS::Value> stacktrace(cx);
-      if (!ToJSValue(cx, *aData->mReifiedStack, &stacktrace) ||
-          !JS_DefineProperty(cx, eventObj, "stacktrace", stacktrace,
-                             JSPROP_ENUMERATE)) {
-        return;
-      }
-    } else {
-      JSFunction* fun = js::NewFunctionWithReserved(cx, LazyStackGetter, 0, 0,
-                                                    "stacktrace");
-      if (!fun) {
-        return;
-      }
-
-      JS::Rooted<JSObject*> funObj(cx, JS_GetFunctionObject(fun));
-
-      // We want to store our stack in the function and have it stay alive.  But
-      // we also need sane access to the C++ nsIStackFrame.  So store both a JS
-      // wrapper and the raw pointer: the former will keep the latter alive.
-      JS::Rooted<JS::Value> stackVal(cx);
-      nsresult rv = nsContentUtils::WrapNative(cx, aData->mStack,
-                                               &stackVal);
-      if (NS_FAILED(rv)) {
-        return;
-      }
-
-      js::SetFunctionNativeReserved(funObj, SLOT_STACKOBJ, stackVal);
-      js::SetFunctionNativeReserved(funObj, SLOT_RAW_STACK,
-                                    JS::PrivateValue(aData->mStack.get()));
-
-      if (!JS_DefineProperty(cx, eventObj, "stacktrace",
-                             JS::UndefinedHandleValue,
-                             JSPROP_ENUMERATE | JSPROP_SHARED | JSPROP_GETTER |
-                             JSPROP_SETTER,
-                             JS_DATA_TO_FUNC_PTR(JSNative, funObj.get()),
-                             nullptr)) {
-        return;
-      }
-    }
-  }
-
   if (!mStorage) {
     mStorage = do_GetService("@mozilla.org/consoleAPI-storage;1");
   }
 
   if (!mStorage) {
     NS_WARNING("Failed to get the ConsoleAPIStorage service.");
     return;
   }
@@ -1482,33 +1479,199 @@ Console::ProcessCallData(ConsoleCallData
     innerID.AppendInt(aData->mInnerIDNumber);
   }
 
   if (NS_FAILED(mStorage->RecordEvent(innerID, outerID, eventValue))) {
     NS_WARNING("Failed to record a console event.");
   }
 }
 
+bool
+Console::PopulateConsoleNotificationInTheTargetScope(JSContext* aCx,
+                                                     const Sequence<JS::Value>& aArguments,
+                                                     JSObject* aTargetScope,
+                                                     JS::MutableHandle<JS::Value> aEventValue,
+                                                     ConsoleCallData* aData) const
+{
+  MOZ_ASSERT(aCx);
+  MOZ_ASSERT(aData);
+  MOZ_ASSERT(aTargetScope);
+
+  JS::Rooted<JSObject*> targetScope(aCx, aTargetScope);
+
+  ConsoleStackEntry frame;
+  if (aData->mTopStackFrame) {
+    frame = *aData->mTopStackFrame;
+  }
+
+  ClearException ce(aCx);
+  RootedDictionary<ConsoleEvent> event(aCx);
+
+  event.mID.Construct();
+  event.mInnerID.Construct();
+
+  if (aData->mIDType == ConsoleCallData::eString) {
+    event.mID.Value().SetAsString() = aData->mOuterIDString;
+    event.mInnerID.Value().SetAsString() = aData->mInnerIDString;
+  } else if (aData->mIDType == ConsoleCallData::eNumber) {
+    event.mID.Value().SetAsUnsignedLongLong() = aData->mOuterIDNumber;
+    event.mInnerID.Value().SetAsUnsignedLongLong() = aData->mInnerIDNumber;
+  } else {
+    // aData->mIDType can be eUnknown when we dispatch notifications via
+    // mConsoleEventNotifier.
+    event.mID.Value().SetAsUnsignedLongLong() = 0;
+    event.mInnerID.Value().SetAsUnsignedLongLong() = 0;
+  }
+
+  event.mLevel = aData->mMethodString;
+  event.mFilename = frame.mFilename;
+
+  nsCOMPtr<nsIURI> filenameURI;
+  nsAutoCString pass;
+  if (NS_IsMainThread() &&
+      NS_SUCCEEDED(NS_NewURI(getter_AddRefs(filenameURI), frame.mFilename)) &&
+      NS_SUCCEEDED(filenameURI->GetPassword(pass)) && !pass.IsEmpty()) {
+    nsCOMPtr<nsISensitiveInfoHiddenURI> safeURI = do_QueryInterface(filenameURI);
+    nsAutoCString spec;
+    if (safeURI &&
+        NS_SUCCEEDED(safeURI->GetSensitiveInfoHiddenSpec(spec))) {
+      CopyUTF8toUTF16(spec, event.mFilename);
+    }
+  }
+
+  event.mLineNumber = frame.mLineNumber;
+  event.mColumnNumber = frame.mColumnNumber;
+  event.mFunctionName = frame.mFunctionName;
+  event.mTimeStamp = aData->mTimeStamp;
+  event.mPrivate = aData->mPrivate;
+
+  switch (aData->mMethodName) {
+    case MethodLog:
+    case MethodInfo:
+    case MethodWarn:
+    case MethodError:
+    case MethodException:
+    case MethodDebug:
+    case MethodAssert:
+      event.mArguments.Construct();
+      event.mStyles.Construct();
+      if (NS_WARN_IF(!ProcessArguments(aCx, aArguments,
+                                       event.mArguments.Value(),
+                                       event.mStyles.Value()))) {
+        return false;
+      }
+
+      break;
+
+    default:
+      event.mArguments.Construct();
+      if (NS_WARN_IF(!ArgumentsToValueList(aArguments,
+                                           event.mArguments.Value()))) {
+        return false;
+      }
+  }
+
+  if (aData->mMethodName == MethodGroup ||
+      aData->mMethodName == MethodGroupCollapsed ||
+      aData->mMethodName == MethodGroupEnd) {
+    ComposeGroupName(aCx, aArguments, event.mGroupName);
+  }
+
+  else if (aData->mMethodName == MethodTime && !aArguments.IsEmpty()) {
+    event.mTimer = CreateStartTimerValue(aCx, aData->mStartTimerLabel,
+                                         aData->mStartTimerValue,
+                                         aData->mStartTimerStatus);
+  }
+
+  else if (aData->mMethodName == MethodTimeEnd && !aArguments.IsEmpty()) {
+    event.mTimer = CreateStopTimerValue(aCx, aData->mStopTimerLabel,
+                                        aData->mStopTimerDuration,
+                                        aData->mStopTimerStatus);
+  }
+
+  else if (aData->mMethodName == MethodCount) {
+    event.mCounter = CreateCounterValue(aCx, aData->mCountLabel,
+                                        aData->mCountValue);
+  }
+
+  JSAutoCompartment ac2(aCx, targetScope);
+
+  if (NS_WARN_IF(!ToJSValue(aCx, event, aEventValue))) {
+    return false;
+  }
+
+  JS::Rooted<JSObject*> eventObj(aCx, &aEventValue.toObject());
+  if (NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "wrappedJSObject", eventObj,
+                                    JSPROP_ENUMERATE))) {
+    return false;
+  }
+
+  if (ShouldIncludeStackTrace(aData->mMethodName)) {
+    // Now define the "stacktrace" property on eventObj.  There are two cases
+    // here.  Either we came from a worker and have a reified stack, or we want
+    // to define a getter that will lazily reify the stack.
+    if (aData->mReifiedStack) {
+      JS::Rooted<JS::Value> stacktrace(aCx);
+      if (NS_WARN_IF(!ToJSValue(aCx, *aData->mReifiedStack, &stacktrace)) ||
+          NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "stacktrace", stacktrace,
+                                        JSPROP_ENUMERATE))) {
+        return false;
+      }
+    } else {
+      JSFunction* fun = js::NewFunctionWithReserved(aCx, LazyStackGetter, 0, 0,
+                                                    "stacktrace");
+      if (NS_WARN_IF(!fun)) {
+        return false;
+      }
+
+      JS::Rooted<JSObject*> funObj(aCx, JS_GetFunctionObject(fun));
+
+      // We want to store our stack in the function and have it stay alive.  But
+      // we also need sane access to the C++ nsIStackFrame.  So store both a JS
+      // wrapper and the raw pointer: the former will keep the latter alive.
+      JS::Rooted<JS::Value> stackVal(aCx);
+      nsresult rv = nsContentUtils::WrapNative(aCx, aData->mStack,
+                                               &stackVal);
+      if (NS_WARN_IF(NS_FAILED(rv))) {
+        return false;
+      }
+
+      js::SetFunctionNativeReserved(funObj, SLOT_STACKOBJ, stackVal);
+      js::SetFunctionNativeReserved(funObj, SLOT_RAW_STACK,
+                                    JS::PrivateValue(aData->mStack.get()));
+
+      if (NS_WARN_IF(!JS_DefineProperty(aCx, eventObj, "stacktrace",
+                                        JS::UndefinedHandleValue,
+                                        JSPROP_ENUMERATE | JSPROP_SHARED |
+                                        JSPROP_GETTER | JSPROP_SETTER,
+                                        JS_DATA_TO_FUNC_PTR(JSNative, funObj.get()),
+                                        nullptr))) {
+        return false;
+      }
+    }
+  }
+
+  return true;
+}
+
 namespace {
 
 // Helper method for ProcessArguments. Flushes output, if non-empty, to aSequence.
 bool
 FlushOutput(JSContext* aCx, Sequence<JS::Value>& aSequence, nsString &aOutput)
 {
-  AssertIsOnMainThread();
-
   if (!aOutput.IsEmpty()) {
     JS::Rooted<JSString*> str(aCx, JS_NewUCStringCopyN(aCx,
                                                        aOutput.get(),
                                                        aOutput.Length()));
-    if (!str) {
+    if (NS_WARN_IF(!str)) {
       return false;
     }
 
-    if (!aSequence.AppendElement(JS::StringValue(str), fallible)) {
+    if (NS_WARN_IF(!aSequence.AppendElement(JS::StringValue(str), fallible))) {
       return false;
     }
 
     aOutput.Truncate();
   }
 
   return true;
 }
@@ -1516,34 +1679,32 @@ FlushOutput(JSContext* aCx, Sequence<JS:
 } // namespace
 
 bool
 Console::ProcessArguments(JSContext* aCx,
                           const Sequence<JS::Value>& aData,
                           Sequence<JS::Value>& aSequence,
                           Sequence<nsString>& aStyles) const
 {
-  AssertIsOnMainThread();
-
   if (aData.IsEmpty()) {
     return true;
   }
 
   if (aData.Length() == 1 || !aData[0].isString()) {
     return ArgumentsToValueList(aData, aSequence);
   }
 
   JS::Rooted<JS::Value> format(aCx, aData[0]);
   JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, format));
-  if (!jsString) {
+  if (NS_WARN_IF(!jsString)) {
     return false;
   }
 
   nsAutoJSString string;
-  if (!string.init(aCx, jsString)) {
+  if (NS_WARN_IF(!string.init(aCx, jsString))) {
     return false;
   }
 
   nsString::const_iterator start, end;
   string.BeginReading(start);
   string.EndReading(end);
 
   nsString output;
@@ -1622,111 +1783,111 @@ Console::ProcessArguments(JSContext* aCx
     char ch = *start;
     tmp.Append(ch);
     ++start;
 
     switch (ch) {
       case 'o':
       case 'O':
       {
-        if (!FlushOutput(aCx, aSequence, output)) {
+        if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) {
           return false;
         }
 
         JS::Rooted<JS::Value> v(aCx);
         if (index < aData.Length()) {
           v = aData[index++];
         }
 
-        if (!aSequence.AppendElement(v, fallible)) {
+        if (NS_WARN_IF(!aSequence.AppendElement(v, fallible))) {
           return false;
         }
 
         break;
       }
 
       case 'c':
       {
         // If there isn't any output but there's already a style, then
         // discard the previous style and use the next one instead.
         if (output.IsEmpty() && !aStyles.IsEmpty()) {
           aStyles.TruncateLength(aStyles.Length() - 1);
         }
 
-        if (!FlushOutput(aCx, aSequence, output)) {
+        if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) {
           return false;
         }
 
         if (index < aData.Length()) {
           JS::Rooted<JS::Value> v(aCx, aData[index++]);
           JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, v));
-          if (!jsString) {
+          if (NS_WARN_IF(!jsString)) {
             return false;
           }
 
           int32_t diff = aSequence.Length() - aStyles.Length();
           if (diff > 0) {
             for (int32_t i = 0; i < diff; i++) {
-              if (!aStyles.AppendElement(NullString(), fallible)) {
+              if (NS_WARN_IF(!aStyles.AppendElement(NullString(), fallible))) {
                 return false;
               }
             }
           }
 
           nsAutoJSString string;
-          if (!string.init(aCx, jsString)) {
+          if (NS_WARN_IF(!string.init(aCx, jsString))) {
             return false;
           }
 
-          if (!aStyles.AppendElement(string, fallible)) {
+          if (NS_WARN_IF(!aStyles.AppendElement(string, fallible))) {
             return false;
           }
         }
         break;
       }
 
       case 's':
         if (index < aData.Length()) {
           JS::Rooted<JS::Value> value(aCx, aData[index++]);
           JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
-          if (!jsString) {
+          if (NS_WARN_IF(!jsString)) {
             return false;
           }
 
           nsAutoJSString v;
-          if (!v.init(aCx, jsString)) {
+          if (NS_WARN_IF(!v.init(aCx, jsString))) {
             return false;
           }
 
           output.Append(v);
         }
         break;
 
       case 'd':
       case 'i':
         if (index < aData.Length()) {
           JS::Rooted<JS::Value> value(aCx, aData[index++]);
 
           int32_t v;
-          if (!JS::ToInt32(aCx, value, &v)) {
+          if (NS_WARN_IF(!JS::ToInt32(aCx, value, &v))) {
             return false;
           }
 
           nsCString format;
           MakeFormatString(format, integer, mantissa, 'd');
           output.AppendPrintf(format.get(), v);
         }
         break;
 
       case 'f':
         if (index < aData.Length()) {
           JS::Rooted<JS::Value> value(aCx, aData[index++]);
 
           double v;
-          if (!JS::ToNumber(aCx, value, &v)) {
+          if (NS_WARN_IF(!JS::ToNumber(aCx, value, &v))) {
             return false;
           }
 
           // nspr returns "nan", but we want to expose it as "NaN"
           if (std::isnan(v)) {
             output.AppendFloat(v);
           } else {
             nsCString format;
@@ -1737,41 +1898,39 @@ Console::ProcessArguments(JSContext* aCx
         break;
 
       default:
         output.Append(tmp);
         break;
     }
   }
 
-  if (!FlushOutput(aCx, aSequence, output)) {
+  if (NS_WARN_IF(!FlushOutput(aCx, aSequence, output))) {
     return false;
   }
 
   // Discard trailing style element if there is no output to apply it to.
   if (aStyles.Length() > aSequence.Length()) {
     aStyles.TruncateLength(aSequence.Length());
   }
 
   // The rest of the array, if unused by the format string.
   for (; index < aData.Length(); ++index) {
-    if (!aSequence.AppendElement(aData[index], fallible)) {
+    if (NS_WARN_IF(!aSequence.AppendElement(aData[index], fallible))) {
       return false;
     }
   }
 
   return true;
 }
 
 void
 Console::MakeFormatString(nsCString& aFormat, int32_t aInteger,
                           int32_t aMantissa, char aCh) const
 {
-  AssertIsOnMainThread();
-
   aFormat.Append('%');
   if (aInteger >= 0) {
     aFormat.AppendInt(aInteger);
   }
 
   if (aMantissa >= 0) {
     aFormat.Append('.');
     aFormat.AppendInt(aMantissa);
@@ -1780,18 +1939,16 @@ Console::MakeFormatString(nsCString& aFo
   aFormat.Append(aCh);
 }
 
 void
 Console::ComposeGroupName(JSContext* aCx,
                           const Sequence<JS::Value>& aData,
                           nsAString& aName) const
 {
-  AssertIsOnMainThread();
-
   for (uint32_t i = 0; i < aData.Length(); ++i) {
     if (i != 0) {
       aName.AppendASCII(" ");
     }
 
     JS::Rooted<JS::Value> value(aCx, aData[i]);
     JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, value));
     if (!jsString) {
@@ -1813,28 +1970,28 @@ Console::StartTimer(JSContext* aCx, cons
                     nsAString& aTimerLabel,
                     DOMHighResTimeStamp* aTimerValue)
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aTimerValue);
 
   *aTimerValue = 0;
 
-  if (mTimerRegistry.Count() >= MAX_PAGE_TIMERS) {
+  if (NS_WARN_IF(mTimerRegistry.Count() >= MAX_PAGE_TIMERS)) {
     return false;
   }
 
   JS::Rooted<JS::Value> name(aCx, aName);
   JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
-  if (!jsString) {
+  if (NS_WARN_IF(!jsString)) {
     return false;
   }
 
   nsAutoJSString label;
-  if (!label.init(aCx, jsString)) {
+  if (NS_WARN_IF(!label.init(aCx, jsString))) {
     return false;
   }
 
   DOMHighResTimeStamp entry;
   if (!mTimerRegistry.Get(label, &entry)) {
     mTimerRegistry.Put(label, aTimestamp);
   } else {
     aTimestamp = entry;
@@ -1845,18 +2002,16 @@ Console::StartTimer(JSContext* aCx, cons
   return true;
 }
 
 JS::Value
 Console::CreateStartTimerValue(JSContext* aCx, const nsAString& aTimerLabel,
                                DOMHighResTimeStamp aTimerValue,
                                bool aTimerStatus) const
 {
-  AssertIsOnMainThread();
-
   if (!aTimerStatus) {
     RootedDictionary<ConsoleTimerError> error(aCx);
 
     JS::Rooted<JS::Value> value(aCx);
     if (!ToJSValue(aCx, error, &value)) {
       return JS::UndefinedValue();
     }
 
@@ -1884,43 +2039,41 @@ Console::StopTimer(JSContext* aCx, const
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aTimerDuration);
 
   *aTimerDuration = 0;
 
   JS::Rooted<JS::Value> name(aCx, aName);
   JS::Rooted<JSString*> jsString(aCx, JS::ToString(aCx, name));
-  if (!jsString) {
+  if (NS_WARN_IF(!jsString)) {
     return false;
   }
 
   nsAutoJSString key;
-  if (!key.init(aCx, jsString)) {
+  if (NS_WARN_IF(!key.init(aCx, jsString))) {
     return false;
   }
 
   DOMHighResTimeStamp entry;
-  if (!mTimerRegistry.Get(key, &entry)) {
+  if (NS_WARN_IF(!mTimerRegistry.Get(key, &entry))) {
     return false;
   }
 
   mTimerRegistry.Remove(key);
 
   aTimerLabel = key;
   *aTimerDuration = aTimestamp - entry;
   return true;
 }
 
 JS::Value
 Console::CreateStopTimerValue(JSContext* aCx, const nsAString& aLabel,
                               double aDuration, bool aStatus) const
 {
-  AssertIsOnMainThread();
-
   if (!aStatus) {
     return JS::UndefinedValue();
   }
 
   RootedDictionary<ConsoleTimerEnd> timer(aCx);
   timer.mName = aLabel;
   timer.mDuration = aDuration;
 
@@ -1931,20 +2084,18 @@ Console::CreateStopTimerValue(JSContext*
 
   return value;
 }
 
 bool
 Console::ArgumentsToValueList(const Sequence<JS::Value>& aData,
                               Sequence<JS::Value>& aSequence) const
 {
-  AssertIsOnMainThread();
-
   for (uint32_t i = 0; i < aData.Length(); ++i) {
-    if (!aSequence.AppendElement(aData[i], fallible)) {
+    if (NS_WARN_IF(!aSequence.AppendElement(aData[i], fallible))) {
       return false;
     }
   }
 
   return true;
 }
 
 uint32_t
@@ -1988,18 +2139,16 @@ Console::IncreaseCounter(JSContext* aCx,
   aCountLabel = label;
   return count;
 }
 
 JS::Value
 Console::CreateCounterValue(JSContext* aCx, const nsAString& aCountLabel,
                             uint32_t aCountValue) const
 {
-  AssertIsOnMainThread();
-
   ClearException ce(aCx);
 
   if (aCountValue == MAX_PAGE_COUNTERS) {
     RootedDictionary<ConsoleCounterError> error(aCx);
 
     JS::Rooted<JS::Value> value(aCx);
     if (!ToJSValue(aCx, error, &value)) {
       return JS::UndefinedValue();
@@ -2051,31 +2200,141 @@ Console::GetOrCreateSandbox(JSContext* a
 
     mSandbox = new JSObjectHolder(aCx, sandbox);
   }
 
   return mSandbox->GetJSObject();
 }
 
 void
-Console::RegisterConsoleCallData(ConsoleCallData* aData)
+Console::StoreCallData(ConsoleCallData* aCallData)
 {
   AssertIsOnOwningThread();
 
-  MOZ_ASSERT(!mConsoleCallDataArray.Contains(aData));
-  mConsoleCallDataArray.AppendElement(aData);
+  MOZ_ASSERT(aCallData);
+  MOZ_ASSERT(!mCallDataStorage.Contains(aCallData));
+  MOZ_ASSERT(!mCallDataStoragePending.Contains(aCallData));
+
+  mCallDataStorage.AppendElement(aCallData);
+
+  if (mCallDataStorage.Length() > STORAGE_MAX_EVENTS) {
+    RefPtr<ConsoleCallData> callData = mCallDataStorage[0];
+    mCallDataStorage.RemoveElementAt(0);
+
+    MOZ_ASSERT(callData->mStatus != ConsoleCallData::eToBeDeleted);
+
+    // We cannot delete this object now because we have to trace its JSValues
+    // until the pending operation (ConsoleCallDataRunnable) is completed.
+    if (callData->mStatus == ConsoleCallData::eInUse) {
+      callData->mStatus = ConsoleCallData::eToBeDeleted;
+      mCallDataStoragePending.AppendElement(callData);
+    }
+  }
+}
+
+void
+Console::UnstoreCallData(ConsoleCallData* aCallData)
+{
+  AssertIsOnOwningThread();
+
+  MOZ_ASSERT(aCallData);
+  MOZ_ASSERT(mCallDataStorage.Contains(aCallData));
+  MOZ_ASSERT(!mCallDataStoragePending.Contains(aCallData));
+
+  mCallDataStorage.RemoveElement(aCallData);
+}
+
+void
+Console::ReleaseCallData(ConsoleCallData* aCallData)
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(aCallData);
+  MOZ_ASSERT(aCallData->mStatus == ConsoleCallData::eToBeDeleted);
+  MOZ_ASSERT(mCallDataStoragePending.Contains(aCallData));
+
+  mCallDataStoragePending.RemoveElement(aCallData);
 }
 
 void
-Console::UnregisterConsoleCallData(ConsoleCallData* aData)
+Console::NotifyHandler(JSContext* aCx, const Sequence<JS::Value>& aArguments,
+                       ConsoleCallData* aCallData) const
+{
+  AssertIsOnOwningThread();
+  MOZ_ASSERT(!NS_IsMainThread());
+  MOZ_ASSERT(aCallData);
+
+  if (!mConsoleEventNotifier) {
+    return;
+  }
+
+  JS::Rooted<JS::Value> value(aCx);
+
+  // aCx and aArguments are in the same compartment because this method is
+  // called directly when a Console.something() runs.
+  // mConsoleEventNotifier->Callable() is the scope where value will be sent to.
+  if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope(aCx, aArguments,
+                                                              mConsoleEventNotifier->Callable(),
+                                                              &value,
+                                                              aCallData))) {
+    return;
+  }
+
+  JS::Rooted<JS::Value> ignored(aCx);
+  mConsoleEventNotifier->Call(value, &ignored);
+}
+
+void
+Console::RetrieveConsoleEvents(JSContext* aCx, nsTArray<JS::Value>& aEvents,
+                               ErrorResult& aRv)
 {
   AssertIsOnOwningThread();
 
-  MOZ_ASSERT(mConsoleCallDataArray.Contains(aData));
-  mConsoleCallDataArray.RemoveElement(aData);
+  // We don't want to expose this functionality to main-thread yet.
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  JS::Rooted<JSObject*> targetScope(aCx, JS::CurrentGlobalOrNull(aCx));
+
+  for (uint32_t i = 0; i < mCallDataStorage.Length(); ++i) {
+    JS::Rooted<JS::Value> value(aCx);
+
+    JS::Rooted<JSObject*> sequenceScope(aCx, mCallDataStorage[i]->mGlobal);
+    JSAutoCompartment ac(aCx, sequenceScope);
+
+    Sequence<JS::Value> sequence;
+    SequenceRooter<JS::Value> arguments(aCx, &sequence);
+
+    if (!mCallDataStorage[i]->PopulateArgumentsSequence(sequence)) {
+      aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+      return;
+    }
+
+    // Here we have aCx and sequence in the same compartment.
+    // targetScope is the destination scope and value will be populated in its
+    // compartment.
+    if (NS_WARN_IF(!PopulateConsoleNotificationInTheTargetScope(aCx, sequence,
+                                                                targetScope,
+                                                                &value,
+                                                                mCallDataStorage[i]))) {
+      aRv.Throw(NS_ERROR_FAILURE);
+      return;
+    }
+
+    aEvents.AppendElement(value);
+  }
+}
+
+void
+Console::SetConsoleEventHandler(AnyCallback& aHandler)
+{
+  AssertIsOnOwningThread();
+
+  // We don't want to expose this functionality to main-thread yet.
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  mConsoleEventNotifier = &aHandler;
 }
 
 void
 Console::AssertIsOnOwningThread() const
 {
   MOZ_ASSERT(mOwningThread);
   MOZ_ASSERT(PR_GetCurrentThread() == mOwningThread);
 }
--- a/dom/base/Console.h
+++ b/dom/base/Console.h
@@ -20,16 +20,17 @@
 #include "nsPIDOMWindow.h"
 
 class nsIConsoleAPIStorage;
 class nsIPrincipal;
 
 namespace mozilla {
 namespace dom {
 
+class AnyCallback;
 class ConsoleCallData;
 class ConsoleRunnable;
 class ConsoleCallDataRunnable;
 class ConsoleProfileRunnable;
 struct ConsoleStackEntry;
 
 class Console final : public nsIObserver
                     , public nsWrapperCache
@@ -37,17 +38,18 @@ class Console final : public nsIObserver
 {
   ~Console();
 
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_AMBIGUOUS(Console, nsIObserver)
   NS_DECL_NSIOBSERVER
 
-  explicit Console(nsPIDOMWindowInner* aWindow);
+  static already_AddRefed<Console>
+  Create(nsPIDOMWindowInner* aWindow, ErrorResult& aRv);
 
   // WebIDL methods
   nsPIDOMWindowInner* GetParentObject() const
   {
     return mWindow;
   }
 
   virtual JSObject*
@@ -111,17 +113,35 @@ public:
   Assert(JSContext* aCx, bool aCondition, const Sequence<JS::Value>& aData);
 
   void
   Count(JSContext* aCx, const Sequence<JS::Value>& aData);
 
   void
   NoopMethod();
 
+  void
+  ClearStorage();
+
+  void
+  RetrieveConsoleEvents(JSContext* aCx, nsTArray<JS::Value>& aEvents,
+                        ErrorResult& aRv);
+
+  void
+  SetConsoleEventHandler(AnyCallback& aHandler);
+
 private:
+  explicit Console(nsPIDOMWindowInner* aWindow);
+
+  void
+  Initialize(ErrorResult& aRv);
+
+  void
+  Shutdown();
+
   enum MethodName
   {
     MethodLog,
     MethodInfo,
     MethodWarn,
     MethodError,
     MethodException,
     MethodDebug,
@@ -138,21 +158,56 @@ private:
     MethodAssert,
     MethodCount
   };
 
   void
   Method(JSContext* aCx, MethodName aName, const nsAString& aString,
          const Sequence<JS::Value>& aData);
 
+  // This method must receive aCx and aArguments in the same JSCompartment.
   void
-  ProcessCallData(ConsoleCallData* aData,
-                  JS::Handle<JSObject*> aGlobal,
+  ProcessCallData(JSContext* aCx,
+                  ConsoleCallData* aData,
                   const Sequence<JS::Value>& aArguments);
 
+  void
+  StoreCallData(ConsoleCallData* aData);
+
+  void
+  UnstoreCallData(ConsoleCallData* aData);
+
+  // Read in Console.cpp how this method is used.
+  void
+  ReleaseCallData(ConsoleCallData* aCallData);
+
+  // aCx and aArguments must be in the same JS compartment.
+  void
+  NotifyHandler(JSContext* aCx,
+                const Sequence<JS::Value>& aArguments,
+                ConsoleCallData* aData) const;
+
+  // PopulateConsoleNotificationInTheTargetScope receives aCx and aArguments in
+  // the same JS compartment and populates the ConsoleEvent object (aValue) in
+  // the aTargetScope.
+  // aTargetScope can be:
+  // - the system-principal scope when we want to dispatch the ConsoleEvent to
+  //   nsIConsoleAPIStorage (See the comment in Console.cpp about the use of
+  //   xpc::PrivilegedJunkScope()
+  // - the mConsoleEventNotifier->Callable() scope when we want to notify this
+  //   handler about a new ConsoleEvent.
+  // - It can be the global from the JSContext when RetrieveConsoleEvents is
+  //   called.
+  bool
+  PopulateConsoleNotificationInTheTargetScope(JSContext* aCx,
+                                              const Sequence<JS::Value>& aArguments,
+                                              JSObject* aTargetScope,
+                                              JS::MutableHandle<JS::Value> aValue,
+                                              ConsoleCallData* aData) const;
+
   // If the first JS::Value of the array is a string, this method uses it to
   // format a string. The supported sequences are:
   //   %s    - string
   //   %d,%i - integer
   //   %f    - double
   //   %o,%O - a JS object.
   //   %c    - style string.
   // The output is an array where any object is a separated item, the rest is
@@ -194,51 +249,50 @@ private:
   // * aTimerValue - the StartTimer value stored into (or taken from)
   //                 mTimerRegistry.
   bool
   StartTimer(JSContext* aCx, const JS::Value& aName,
              DOMHighResTimeStamp aTimestamp,
              nsAString& aTimerLabel,
              DOMHighResTimeStamp* aTimerValue);
 
-  // CreateStartTimerValue is called on the main thread only and generates a
-  // ConsoleTimerStart dictionary exposed as JS::Value. If aTimerStatus is
-  // false, it generates a ConsoleTimerError instead. It's called only after
-  // the execution StartTimer on the owning thread.
+  // CreateStartTimerValue generates a ConsoleTimerStart dictionary exposed as
+  // JS::Value. If aTimerStatus is false, it generates a ConsoleTimerError
+  // instead. It's called only after the execution StartTimer on the owning
+  // thread.
   // * aCx - this is the context that will root the returned value.
   // * aTimerLabel - this label must be what StartTimer received as aTimerLabel.
   // * aTimerValue - this is what StartTimer received as aTimerValue
   // * aTimerStatus - the return value of StartTimer.
   JS::Value
   CreateStartTimerValue(JSContext* aCx, const nsAString& aTimerLabel,
                         DOMHighResTimeStamp aTimerValue,
                         bool aTimerStatus) const;
 
   // StopTimer follows the same pattern as StartTimer: it runs on the
   // owning thread and populates aTimerLabel and aTimerDuration, used by
-  // CreateStopTimerValue on the main thread. It returns false if a JS
-  // exception is thrown or if the aName timer doesn't exist in mTimerRegistry.
+  // CreateStopTimerValue. It returns false if a JS exception is thrown or if
+  // the aName timer doesn't exist in the mTimerRegistry.
   // * aCx - the JSContext rooting aName.
   // * aName - this is (should be) the name of the timer as JS::Value.
   // * aTimestamp - the monotonicTimer for this context (taken from
   //                window->performance.now() or from Now() -
   //                workerPrivate->NowBaseTimeStamp() in workers.
   // * aTimerLabel - This label will be populated with the aName converted to a
   //                 string.
   // * aTimerDuration - the difference between aTimestamp and when the timer
   //                    started (see StartTimer).
   bool
   StopTimer(JSContext* aCx, const JS::Value& aName,
             DOMHighResTimeStamp aTimestamp,
             nsAString& aTimerLabel,
             double* aTimerDuration);
 
-  // Executed on the main thread and generates a ConsoleTimerEnd dictionary
-  // exposed as JS::Value, or a ConsoleTimerError dictionary if aTimerStatus is
-  // false. See StopTimer.
+  // This method generates a ConsoleTimerEnd dictionary exposed as JS::Value, or
+  // a ConsoleTimerError dictionary if aTimerStatus is false. See StopTimer.
   // * aCx - this is the context that will root the returned value.
   // * aTimerLabel - this label must be what StopTimer received as aTimerLabel.
   // * aTimerDuration - this is what StopTimer received as aTimerDuration
   // * aTimerStatus - the return value of StopTimer.
   JS::Value
   CreateStopTimerValue(JSContext* aCx, const nsAString& aTimerLabel,
                        double aTimerDuration,
                        bool aTimerStatus) const;
@@ -248,74 +302,85 @@ private:
   ArgumentsToValueList(const Sequence<JS::Value>& aData,
                        Sequence<JS::Value>& aSequence) const;
 
   void
   ProfileMethod(JSContext* aCx, const nsAString& aAction,
                 const Sequence<JS::Value>& aData);
 
   // This method follows the same pattern as StartTimer: its runs on the owning
-  // thread and populates aCountLabel, used by CreateCounterValue on the
-  // main thread. Returns MAX_PAGE_COUNTERS in case of error otherwise the
-  // incremented counter value.
+  // thread and populate aCountLabel, used by CreateCounterValue. Returns
+  // MAX_PAGE_COUNTERS in case of error, otherwise the incremented counter
+  // value.
   // * aCx - the JSContext rooting aData.
   // * aFrame - the first frame of ConsoleCallData.
   // * aData - the arguments received by the console.count() method.
   // * aCountLabel - the label that will be populated by this method.
   uint32_t
   IncreaseCounter(JSContext* aCx, const ConsoleStackEntry& aFrame,
                   const Sequence<JS::Value>& aData,
                   nsAString& aCountLabel);
 
-  // Executed on the main thread and generates a ConsoleCounter dictionary as
-  // JS::Value. If aCountValue is == MAX_PAGE_COUNTERS it generates a
-  // ConsoleCounterError instead. See IncreaseCounter.
+  // This method generates a ConsoleCounter dictionary as JS::Value. If
+  // aCountValue is == MAX_PAGE_COUNTERS it generates a ConsoleCounterError
+  // instead. See IncreaseCounter.
   // * aCx - this is the context that will root the returned value.
   // * aCountLabel - this label must be what IncreaseCounter received as
   //                 aTimerLabel.
   // * aCountValue - the return value of IncreaseCounter.
   JS::Value
   CreateCounterValue(JSContext* aCx, const nsAString& aCountLabel,
                      uint32_t aCountValue) const;
 
   bool
   ShouldIncludeStackTrace(MethodName aMethodName) const;
 
   JSObject*
   GetOrCreateSandbox(JSContext* aCx, nsIPrincipal* aPrincipal);
 
   void
-  RegisterConsoleCallData(ConsoleCallData* aData);
-
-  void
-  UnregisterConsoleCallData(ConsoleCallData* aData);
-
-  void
   AssertIsOnOwningThread() const;
 
   // All these nsCOMPtr are touched on main thread only.
   nsCOMPtr<nsPIDOMWindowInner> mWindow;
   nsCOMPtr<nsIConsoleAPIStorage> mStorage;
   RefPtr<JSObjectHolder> mSandbox;
 
   // Touched on the owner thread.
   nsDataHashtable<nsStringHashKey, DOMHighResTimeStamp> mTimerRegistry;
   nsDataHashtable<nsStringHashKey, uint32_t> mCounterRegistry;
 
-  // Raw pointers because ConsoleCallData manages its own
-  // registration/unregistration.
-  nsTArray<ConsoleCallData*> mConsoleCallDataArray;
+  nsTArray<RefPtr<ConsoleCallData>> mCallDataStorage;
+
+  // This array is used in a particular corner-case where:
+  // 1. we are in a worker thread
+  // 2. we have more than STORAGE_MAX_EVENTS
+  // 3. but the main-thread ConsoleCallDataRunnable of the first one is still
+  // running (this means that something very bad is happening on the
+  // main-thread).
+  // When this happens we want to keep the ConsoleCallData alive for traceing
+  // its JSValues also if 'officially' this ConsoleCallData must be removed from
+  // the storage.
+  nsTArray<RefPtr<ConsoleCallData>> mCallDataStoragePending;
+
+  RefPtr<AnyCallback> mConsoleEventNotifier;
 
 #ifdef DEBUG
   PRThread* mOwningThread;
 #endif
 
   uint64_t mOuterID;
   uint64_t mInnerID;
 
+  enum {
+    eUnknown,
+    eInitialized,
+    eShuttingDown
+  } mStatus;
+
   friend class ConsoleCallData;
   friend class ConsoleRunnable;
   friend class ConsoleCallDataRunnable;
   friend class ConsoleProfileRunnable;
 };
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/base/DirectionalityUtils.cpp
+++ b/dom/base/DirectionalityUtils.cpp
@@ -381,17 +381,17 @@ GetDirectionFromText(const nsTextFragmen
  * Set the directionality of a node with dir=auto as defined in
  * http://www.whatwg.org/specs/web-apps/current-work/multipage/elements.html#the-directionality
  *
  * @param[in] changedNode If we call this method because the content of a text
  *            node is about to change, pass in the changed node, so that we
  *            know not to return it
  * @return the text node containing the character that determined the direction
  */
-static nsINode*
+static nsTextNode*
 WalkDescendantsSetDirectionFromText(Element* aElement, bool aNotify = true,
                                     nsINode* aChangedNode = nullptr)
 {
   MOZ_ASSERT(aElement, "Must have an element");
   MOZ_ASSERT(aElement->HasDirAuto(), "Element must have dir=auto");
 
   if (DoesNotParticipateInAutoDirection(aElement)) {
     return nullptr;
@@ -407,17 +407,17 @@ WalkDescendantsSetDirectionFromText(Elem
 
     if (child->NodeType() == nsIDOMNode::TEXT_NODE &&
         child != aChangedNode) {
       Directionality textNodeDir = GetDirectionFromText(child->GetText());
       if (textNodeDir != eDir_NotSet) {
         // We found a descendant text node with strong directional characters.
         // Set the directionality of aElement to the corresponding value.
         aElement->SetDirectionality(textNodeDir, aNotify);
-        return child;
+        return static_cast<nsTextNode*>(child);
       }
     }
     child = child->GetNextNode(aElement);
   }
 
   // We walked all the descendants without finding a text node with strong
   // directional characters. Set the directionality to LTR
   aElement->SetDirectionality(eDir_LTR, aNotify);
@@ -449,26 +449,26 @@ public:
     aTextNode->SetHasTextNodeDirectionalityMap();
   }
 
   ~nsTextNodeDirectionalityMap()
   {
     MOZ_COUNT_DTOR(nsTextNodeDirectionalityMap);
   }
 
-  void AddEntry(nsINode* aTextNode, Element* aElement)
+  void AddEntry(nsTextNode* aTextNode, Element* aElement)
   {
     if (!mElements.Contains(aElement)) {
       mElements.Put(aElement);
       aElement->SetProperty(nsGkAtoms::dirAutoSetBy, aTextNode);
       aElement->SetHasDirAutoSet();
     }
   }
 
-  void RemoveEntry(nsINode* aTextNode, Element* aElement)
+  void RemoveEntry(nsTextNode* aTextNode, Element* aElement)
   {
     NS_ASSERTION(mElements.Contains(aElement),
                  "element already removed from map");
 
     mElements.Remove(aElement);
     aElement->ClearHasDirAutoSet();
     aElement->UnsetProperty(nsGkAtoms::dirAutoSetBy);
   }
@@ -500,17 +500,17 @@ private:
 
   static nsCheapSetOperator ResetNodeDirection(nsPtrHashKey<Element>* aEntry, void* aData)
   {
     MOZ_ASSERT(aEntry->GetKey()->IsElement(), "Must be an Element");
     // run the downward propagation algorithm
     // and remove the text node from the map
     nsINode* oldTextNode = static_cast<Element*>(aData);
     Element* rootNode = aEntry->GetKey();
-    nsINode* newTextNode = nullptr;
+    nsTextNode* newTextNode = nullptr;
     if (rootNode->GetParentNode() && rootNode->HasDirAuto()) {
       newTextNode = WalkDescendantsSetDirectionFromText(rootNode, true,
                                                         oldTextNode);
     }
     if (newTextNode) {
       nsTextNodeDirectionalityMap::AddEntryToMap(newTextNode, rootNode);
     } else {
       rootNode->ClearHasDirAutoSet();
@@ -540,24 +540,24 @@ public:
 
   void EnsureMapIsClear(nsINode* aTextNode)
   {
     DebugOnly<uint32_t> clearedEntries =
       mElements.EnumerateEntries(ClearEntry, aTextNode);
     MOZ_ASSERT(clearedEntries == 0, "Map should be empty already");
   }
 
-  static void RemoveElementFromMap(nsINode* aTextNode, Element* aElement)
+  static void RemoveElementFromMap(nsTextNode* aTextNode, Element* aElement)
   {
     if (aTextNode->HasTextNodeDirectionalityMap()) {
       GetDirectionalityMap(aTextNode)->RemoveEntry(aTextNode, aElement);
     }
   }
 
-  static void AddEntryToMap(nsINode* aTextNode, Element* aElement)
+  static void AddEntryToMap(nsTextNode* aTextNode, Element* aElement)
   {
     nsTextNodeDirectionalityMap* map = GetDirectionalityMap(aTextNode);
     if (!map) {
       map = new nsTextNodeDirectionalityMap(aTextNode);
     }
 
     map->AddEntry(aTextNode, aElement);
   }
@@ -565,18 +565,18 @@ public:
   static uint32_t UpdateTextNodeDirection(nsINode* aTextNode,
                                           Directionality aDir)
   {
     MOZ_ASSERT(aTextNode->HasTextNodeDirectionalityMap(),
                "Map missing in UpdateTextNodeDirection");
     return GetDirectionalityMap(aTextNode)->UpdateAutoDirection(aDir);
   }
 
-  static void ResetTextNodeDirection(nsINode* aTextNode,
-                                     nsINode* aChangedTextNode)
+  static void ResetTextNodeDirection(nsTextNode* aTextNode,
+                                     nsTextNode* aChangedTextNode)
   {
     MOZ_ASSERT(aTextNode->HasTextNodeDirectionalityMap(),
                "Map missing in ResetTextNodeDirection");
     GetDirectionalityMap(aTextNode)->ResetAutoDirection(aChangedTextNode);
   }
 
   static void EnsureMapIsClearFor(nsINode* aTextNode)
   {
@@ -640,27 +640,27 @@ SetDirectionalityOnDescendants(Element* 
 /**
  * Walk the parent chain of a text node whose dir attribute has been removed and
  * reset the direction of any of its ancestors which have dir=auto and whose
  * directionality is determined by a text node descendant.
  */
 void
 WalkAncestorsResetAutoDirection(Element* aElement, bool aNotify)
 {
-  nsINode* setByNode;
+  nsTextNode* setByNode;
   Element* parent = aElement->GetParentElement();
 
   while (parent && parent->NodeOrAncestorHasDirAuto()) {
     if (parent->HasDirAutoSet()) {
       // If the parent has the DirAutoSet flag, its direction is determined by
       // some text node descendant.
       // Remove it from the map and reset its direction by the downward
       // propagation algorithm
       setByNode =
-        static_cast<nsINode*>(parent->GetProperty(nsGkAtoms::dirAutoSetBy));
+        static_cast<nsTextNode*>(parent->GetProperty(nsGkAtoms::dirAutoSetBy));
       if (setByNode) {
         nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, parent);
       }
     }
     if (parent->HasDirAuto()) {
       setByNode = WalkDescendantsSetDirectionFromText(parent, aNotify);
       if (setByNode) {
         nsTextNodeDirectionalityMap::AddEntryToMap(setByNode, parent);
@@ -676,18 +676,19 @@ WalkDescendantsResetAutoDirection(Elemen
 {
   nsIContent* child = aElement->GetFirstChild();
   while (child) {
     if (child->HasDirAuto()) {
       child = child->GetNextNonChildNode(aElement);
       continue;
     }
 
-    if (child->HasTextNodeDirectionalityMap()) {
-      nsTextNodeDirectionalityMap::ResetTextNodeDirection(child, nullptr);
+    if (child->NodeType() == nsIDOMNode::TEXT_NODE &&
+        child->HasTextNodeDirectionalityMap()) {
+      nsTextNodeDirectionalityMap::ResetTextNodeDirection(static_cast<nsTextNode*>(child), nullptr);
       nsTextNodeDirectionalityMap::EnsureMapIsClearFor(child);
     }
     child = child->GetNextNode(aElement);
   }
 }
 
 void
 WalkDescendantsSetDirAuto(Element* aElement, bool aNotify)
@@ -720,17 +721,17 @@ WalkDescendantsSetDirAuto(Element* aElem
                    child->AncestorHasDirAuto(),
                    "AncestorHasDirAuto set on node but not its children");
         child->SetAncestorHasDirAuto();
         child = child->GetNextNode(aElement);
       }
     }
   }
 
-  nsINode* textNode = WalkDescendantsSetDirectionFromText(aElement, aNotify);
+  nsTextNode* textNode = WalkDescendantsSetDirectionFromText(aElement, aNotify);
   if (textNode) {
     nsTextNodeDirectionalityMap::AddEntryToMap(textNode, aElement);
   }
 }
 
 void
 WalkDescendantsClearAncestorDirAuto(Element* aElement)
 {
@@ -741,32 +742,32 @@ WalkDescendantsClearAncestorDirAuto(Elem
       continue;
     }
 
     child->ClearAncestorHasDirAuto();
     child = child->GetNextNode(aElement);
   }
 }
 
-void SetAncestorDirectionIfAuto(nsINode* aTextNode, Directionality aDir,
+void SetAncestorDirectionIfAuto(nsTextNode* aTextNode, Directionality aDir,
                                 bool aNotify = true)
 {
   MOZ_ASSERT(aTextNode->NodeType() == nsIDOMNode::TEXT_NODE,
              "Must be a text node");
 
   Element* parent = aTextNode->GetParentElement();
   while (parent && parent->NodeOrAncestorHasDirAuto()) {
     if (DoesNotParticipateInAutoDirection(parent) || parent->HasFixedDir()) {
       break;
     }
 
     if (parent->HasDirAuto()) {
       bool resetDirection = false;
-      nsINode* directionWasSetByTextNode =
-        static_cast<nsINode*>(parent->GetProperty(nsGkAtoms::dirAutoSetBy));
+      nsTextNode* directionWasSetByTextNode =
+        static_cast<nsTextNode*>(parent->GetProperty(nsGkAtoms::dirAutoSetBy));
 
       if (!parent->HasDirAutoSet()) {
         // Fast path if parent's direction is not yet set by any descendant
         MOZ_ASSERT(!directionWasSetByTextNode,
                    "dirAutoSetBy property should be null");
         resetDirection = true;
       } else {
         // If parent's direction is already set, we need to know if
@@ -827,17 +828,17 @@ TextNodeWillChangeDirection(nsIContent* 
   }
 
   uint32_t firstStrong;
   *aOldDir = GetDirectionFromText(aTextNode->GetText(), &firstStrong);
   return (aOffset <= firstStrong);
 }
 
 void
-TextNodeChangedDirection(nsIContent* aTextNode, Directionality aOldDir,
+TextNodeChangedDirection(nsTextNode* aTextNode, Directionality aOldDir,
                          bool aNotify)
 {
   Directionality newDir = GetDirectionFromText(aTextNode->GetText());
   if (newDir == eDir_NotSet) {
     if (aOldDir != eDir_NotSet && aTextNode->HasTextNodeDirectionalityMap()) {
       // This node used to have a strong directional character but no
       // longer does. ResetTextNodeDirection() will re-resolve the
       // directionality of any elements whose directionality was
@@ -857,17 +858,17 @@ TextNodeChangedDirection(nsIContent* aTe
                                                              newDir)) {
       return;
     }
     SetAncestorDirectionIfAuto(aTextNode, newDir, aNotify);
   }
 }
 
 void
-SetDirectionFromNewTextNode(nsIContent* aTextNode)
+SetDirectionFromNewTextNode(nsTextNode* aTextNode)
 {
   if (!NodeAffectsDirAutoAncestor(aTextNode)) {
     return;
   }
 
   Element* parent = aTextNode->GetParentElement();
   if (parent && parent->NodeOrAncestorHasDirAuto()) {
     aTextNode->SetAncestorHasDirAuto();
@@ -944,18 +945,18 @@ OnSetDirAttr(Element* aElement, const ns
     //      here is simpler.
     WalkDescendantsClearAncestorDirAuto(aElement);
   }
 
   if (aElement->HasDirAuto()) {
     WalkDescendantsSetDirAuto(aElement, aNotify);
   } else {
     if (aElement->HasDirAutoSet()) {
-      nsINode* setByNode =
-        static_cast<nsINode*>(aElement->GetProperty(nsGkAtoms::dirAutoSetBy));
+      nsTextNode* setByNode =
+        static_cast<nsTextNode*>(aElement->GetProperty(nsGkAtoms::dirAutoSetBy));
       nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement);
     }
     SetDirectionalityOnDescendants(aElement,
                                    RecomputeDirectionality(aElement, aNotify),
                                    aNotify);
   }
 }
 
@@ -996,18 +997,18 @@ SetDirOnBind(mozilla::dom::Element* aEle
     // the dir attribute or by inheriting from its ancestors.
     RecomputeDirectionality(aElement, false);
   }
 }
 
 void ResetDir(mozilla::dom::Element* aElement)
 {
   if (aElement->HasDirAutoSet()) {
-    nsINode* setByNode =
-      static_cast<nsINode*>(aElement->GetProperty(nsGkAtoms::dirAutoSetBy));
+    nsTextNode* setByNode =
+      static_cast<nsTextNode*>(aElement->GetProperty(nsGkAtoms::dirAutoSetBy));
     nsTextNodeDirectionalityMap::RemoveElementFromMap(setByNode, aElement);
   }
 
   if (!aElement->HasDirAuto()) {
     RecomputeDirectionality(aElement, false);
   }
 }
 
--- a/dom/base/DirectionalityUtils.h
+++ b/dom/base/DirectionalityUtils.h
@@ -84,24 +84,24 @@ void WalkDescendantsClearAncestorDirAuto
  */
 bool TextNodeWillChangeDirection(nsIContent* aTextNode, Directionality* aOldDir,
                                  uint32_t aOffset);
 
 /**
  * After the contents of a text node have changed, change the directionality
  * of any elements whose directionality is determined by that node
  */
-void TextNodeChangedDirection(nsIContent* aTextNode, Directionality aOldDir,
+void TextNodeChangedDirection(nsTextNode* aTextNode, Directionality aOldDir,
                               bool aNotify);
 
 /**
  * When a text node is appended to an element, find any ancestors with dir=auto
  * whose directionality will be determined by the text node
  */
-void SetDirectionFromNewTextNode(nsIContent* aTextNode);
+void SetDirectionFromNewTextNode(nsTextNode* aTextNode);
 
 /**
  * When a text node is removed from a document, find any ancestors whose
  * directionality it determined and redetermine their directionality
  *
  * @param aTextNode the text node
  */
 void ResetDirectionSetByTextNode(nsTextNode* aTextNode);
--- a/dom/base/ScriptSettings.cpp
+++ b/dom/base/ScriptSettings.cpp
@@ -300,41 +300,49 @@ FindJSContext(nsIGlobalObject* aGlobalOb
   if (!cx) {
     cx = nsContentUtils::GetSafeJSContext();
   }
   return cx;
 }
 
 AutoJSAPI::AutoJSAPI()
   : mCx(nullptr)
-  , mOwnErrorReporting(false)
   , mOldAutoJSAPIOwnsErrorReporting(false)
   , mIsMainThread(false) // For lack of anything better
 {
 }
 
 AutoJSAPI::~AutoJSAPI()
 {
-  if (mOwnErrorReporting) {
-    ReportException();
+  if (!mCx) {
+    // No need to do anything here: we never managed to Init, so can't have an
+    // exception on our (nonexistent) JSContext.  We also don't need to restore
+    // any state on it.
+    return;
+  }
 
-    // We need to do this _after_ processing the existing exception, because the
-    // JS engine can throw while doing that, and uses this bit to determine what
-    // to do in that case: squelch the exception if the bit is set, otherwise
-    // call the error reporter. Calling WarningOnlyErrorReporter with a
-    // non-warning will assert, so we need to make sure we do the former.
-    JS::ContextOptionsRef(cx()).setAutoJSAPIOwnsErrorReporting(mOldAutoJSAPIOwnsErrorReporting);
-  }
+  ReportException();
+
+  // We need to do this _after_ processing the existing exception, because the
+  // JS engine can throw while doing that, and uses this bit to determine what
+  // to do in that case: squelch the exception if the bit is set, otherwise
+  // call the error reporter. Calling WarningOnlyErrorReporter with a
+  // non-warning will assert, so we need to make sure we do the former.
+  JS::ContextOptionsRef(cx()).setAutoJSAPIOwnsErrorReporting(mOldAutoJSAPIOwnsErrorReporting);
 
   if (mOldErrorReporter.isSome()) {
     JS_SetErrorReporter(JS_GetRuntime(cx()), mOldErrorReporter.value());
   }
 }
 
 void
+WarningOnlyErrorReporter(JSContext* aCx, const char* aMessage,
+                         JSErrorReport* aRep);
+
+void
 AutoJSAPI::InitInternal(JSObject* aGlobal, JSContext* aCx, bool aIsMainThread)
 {
   MOZ_ASSERT(aCx);
   MOZ_ASSERT(aIsMainThread == NS_IsMainThread());
 #ifdef DEBUG
   bool haveException = JS_IsExceptionPending(aCx);
 #endif // DEBUG
 
@@ -350,19 +358,19 @@ AutoJSAPI::InitInternal(JSObject* aGloba
     mAutoNullableCompartment.emplace(mCx, global);
   } else {
     mAutoNullableCompartment.emplace(mCx, aGlobal);
   }
 
   JSRuntime* rt = JS_GetRuntime(aCx);
   mOldErrorReporter.emplace(JS_GetErrorReporter(rt));
 
-  if (aIsMainThread) {
-    JS_SetErrorReporter(rt, xpc::SystemErrorReporter);
-  }
+  mOldAutoJSAPIOwnsErrorReporting = JS::ContextOptionsRef(aCx).autoJSAPIOwnsErrorReporting();
+  JS::ContextOptionsRef(aCx).setAutoJSAPIOwnsErrorReporting(true);
+  JS_SetErrorReporter(rt, WarningOnlyErrorReporter);
 
 #ifdef DEBUG
   if (haveException) {
     JS::Rooted<JS::Value> exn(aCx);
     JS_GetPendingException(aCx, &exn);
 
     JS_ClearPendingException(aCx);
     if (exn.isObject()) {
@@ -424,18 +432,17 @@ AutoJSAPI::InitInternal(JSObject* aGloba
     MOZ_ASSERT(false, "We had an exception; we should not have");
   }
 #endif // DEBUG
 }
 
 AutoJSAPI::AutoJSAPI(nsIGlobalObject* aGlobalObject,
                      bool aIsMainThread,
                      JSContext* aCx)
-  : mOwnErrorReporting(false)
-  , mOldAutoJSAPIOwnsErrorReporting(false)
+  : mOldAutoJSAPIOwnsErrorReporting(false)
   , mIsMainThread(aIsMainThread)
 {
   MOZ_ASSERT(aGlobalObject);
   MOZ_ASSERT(aGlobalObject->GetGlobalJSObject(), "Must have a JS global");
   MOZ_ASSERT(aCx);
   MOZ_ASSERT(aIsMainThread == NS_IsMainThread());
 
   InitInternal(aGlobalObject->GetGlobalJSObject(), aCx, aIsMainThread);
@@ -540,31 +547,18 @@ WarningOnlyErrorReporter(JSContext* aCx,
     win = xpc::AddonWindowOrNull(JS::CurrentGlobalOrNull(aCx));
   }
   xpcReport->Init(aRep, aMessage, nsContentUtils::IsCallerChrome(),
                   win ? win->AsInner()->WindowID() : 0);
   xpcReport->LogToConsole();
 }
 
 void
-AutoJSAPI::TakeOwnershipOfErrorReporting()
-{
-  MOZ_ASSERT(!mOwnErrorReporting);
-  mOwnErrorReporting = true;
-
-  JSRuntime *rt = JS_GetRuntime(cx());
-  mOldAutoJSAPIOwnsErrorReporting = JS::ContextOptionsRef(cx()).autoJSAPIOwnsErrorReporting();
-  JS::ContextOptionsRef(cx()).setAutoJSAPIOwnsErrorReporting(true);
-  JS_SetErrorReporter(rt, WarningOnlyErrorReporter);
-}
-
-void
 AutoJSAPI::ReportException()
 {
-  MOZ_ASSERT(OwnsErrorReporting(), "This is not our exception to report!");
   if (!HasException()) {
     return;
   }
 
   // AutoJSAPI uses a JSAutoNullableCompartment, and may be in a null
   // compartment when the destructor is called. However, the JS engine
   // requires us to be in a compartment when we fetch the pending exception.
   // In this case, we enter the privileged junk scope and don't dispatch any
@@ -656,18 +650,16 @@ AutoEntryScript::AutoEntryScript(nsIGlob
 {
   MOZ_ASSERT(aGlobalObject);
   MOZ_ASSERT_IF(!aCx, aIsMainThread); // cx is mandatory off-main-thread.
   MOZ_ASSERT_IF(aCx && aIsMainThread, aCx == FindJSContext(aGlobalObject));
 
   if (aIsMainThread && gRunToCompletionListeners > 0) {
     mDocShellEntryMonitor.emplace(cx(), aReason);
   }
-
-  TakeOwnershipOfErrorReporting();
 }
 
 AutoEntryScript::AutoEntryScript(JSObject* aObject,
                                  const char *aReason,
                                  bool aIsMainThread,
                                  JSContext* aCx)
   : AutoEntryScript(xpc::NativeGlobal(aObject), aReason, aIsMainThread, aCx)
 {
@@ -846,38 +838,16 @@ AutoJSContext::AutoJSContext(MOZ_GUARD_O
   }
 }
 
 AutoJSContext::operator JSContext*() const
 {
   return mCx;
 }
 
-ThreadsafeAutoJSContext::ThreadsafeAutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
-{
-  MOZ_GUARD_OBJECT_NOTIFIER_INIT;
-
-  if (NS_IsMainThread()) {
-    mCx = nullptr;
-    mAutoJSContext.emplace();
-  } else {
-    mCx = mozilla::dom::workers::GetCurrentThreadJSContext();
-    mRequest.emplace(mCx);
-  }
-}
-
-ThreadsafeAutoJSContext::operator JSContext*() const
-{
-  if (mCx) {
-    return mCx;
-  } else {
-    return *mAutoJSContext;
-  }
-}
-
 AutoSafeJSContext::AutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM_IN_IMPL)
   : AutoJSAPI()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   MOZ_GUARD_OBJECT_NOTIFIER_INIT;
 
   DebugOnly<bool> ok = Init(xpc::UnprivilegedJunkScope());
--- a/dom/base/ScriptSettings.h
+++ b/dom/base/ScriptSettings.h
@@ -262,24 +262,17 @@ public:
   JSContext* cx() const {
     MOZ_ASSERT(mCx, "Must call Init before using an AutoJSAPI");
     MOZ_ASSERT_IF(mIsMainThread, CxPusherIsStackTop());
     return mCx;
   }
 
   bool CxPusherIsStackTop() const { return mCxPusher->IsStackTop(); }
 
-  // We're moving towards a world where the AutoJSAPI always handles
-  // exceptions that bubble up from the JS engine. In order to make this
-  // process incremental, we allow consumers to opt-in to the new behavior
-  // while keeping the old behavior as the default.
-  void TakeOwnershipOfErrorReporting();
-  bool OwnsErrorReporting() { return mOwnErrorReporting; }
-  // If HasException, report it.  Otherwise, a no-op.  This must be
-  // called only if OwnsErrorReporting().
+  // If HasException, report it.  Otherwise, a no-op.
   void ReportException();
 
   bool HasException() const {
     MOZ_ASSERT_IF(NS_IsMainThread(), CxPusherIsStackTop());
     return JS_IsExceptionPending(cx());
   };
 
   // Transfers ownership of the current exception from the JS engine to the
@@ -312,17 +305,16 @@ protected:
   AutoJSAPI(nsIGlobalObject* aGlobalObject, bool aIsMainThread, JSContext* aCx);
 
 private:
   mozilla::Maybe<danger::AutoCxPusher> mCxPusher;
   mozilla::Maybe<JSAutoNullableCompartment> mAutoNullableCompartment;
   JSContext *mCx;
 
   // Track state between the old and new error reporting modes.
-  bool mOwnErrorReporting;
   bool mOldAutoJSAPIOwnsErrorReporting;
   // Whether we're mainthread or not; set when we're initialized.
   bool mIsMainThread;
   Maybe<JSErrorReporter> mOldErrorReporter;
 
   void InitInternal(JSObject* aGlobal, JSContext* aCx, bool aIsMainThread);
 
   AutoJSAPI(const AutoJSAPI&) = delete;
@@ -440,32 +432,16 @@ public:
 
 protected:
   JSContext* mCx;
   dom::AutoJSAPI mJSAPI;
   MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
 };
 
 /**
- * Use ThreadsafeAutoJSContext when you want an AutoJSContext but might be
- * running on a worker thread.
- */
-class MOZ_RAII ThreadsafeAutoJSContext {
-public:
-  explicit ThreadsafeAutoJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
-  operator JSContext*() const;
-
-private:
-  JSContext* mCx; // Used on workers.  Null means mainthread.
-  Maybe<JSAutoRequest> mRequest; // Used on workers.
-  Maybe<AutoJSContext> mAutoJSContext; // Used on main thread.
-  MOZ_DECL_USE_GUARD_OBJECT_NOTIFIER
-};
-
-/**
  * AutoSafeJSContext is similar to AutoJSContext but will only return the safe
  * JS context. That means it will never call nsContentUtils::GetCurrentJSContext().
  *
  * Note - This is deprecated. Please use AutoJSAPI instead.
  */
 class MOZ_RAII AutoSafeJSContext : public dom::AutoJSAPI {
 public:
   explicit AutoSafeJSContext(MOZ_GUARD_OBJECT_NOTIFIER_ONLY_PARAM);
--- a/dom/base/WebSocket.cpp
+++ b/dom/base/WebSocket.cpp
@@ -1881,17 +1881,16 @@ WebSocket::CreateAndDispatchMessageEvent
   } else {
     MOZ_ASSERT(!mIsMainThread);
     MOZ_ASSERT(mImpl->mWorkerPrivate);
     if (NS_WARN_IF(!jsapi.Init(mImpl->mWorkerPrivate->GlobalScope()))) {
       return NS_ERROR_FAILURE;
     }
   }
 
-  jsapi.TakeOwnershipOfErrorReporting();
   JSContext* cx = jsapi.cx();
 
   nsresult rv = CheckInnerWindowCorrectness();
   if (NS_FAILED(rv)) {
     return NS_OK;
   }
 
   uint16_t messageType = nsIWebSocketEventListener::TYPE_STRING;
--- a/dom/base/WindowNamedPropertiesHandler.cpp
+++ b/dom/base/WindowNamedPropertiesHandler.cpp
@@ -270,20 +270,20 @@ const NativePropertyHooks sWindowNamedPr
   constructors::id::_ID_Count,
   nullptr
 } };
 
 static const DOMIfaceAndProtoJSClass WindowNamedPropertiesClass = {
   PROXY_CLASS_DEF("WindowProperties",
                   JSCLASS_IS_DOMIFACEANDPROTOJSCLASS),
   eNamedPropertiesObject,
+  prototypes::id::_ID_Count,
+  0,
   sWindowNamedPropertiesNativePropertyHooks,
   "[object WindowProperties]",
-  prototypes::id::_ID_Count,
-  0,
   EventTargetBinding::GetProtoObject
 };
 
 // static
 JSObject*
 WindowNamedPropertiesHandler::Create(JSContext* aCx,
                                      JS::Handle<JSObject*> aProto)
 {
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -6485,20 +6485,16 @@ nsContentUtils::IsPatternMatching(nsAStr
                                   nsIDocument* aDocument)
 {
   NS_ASSERTION(aDocument, "aDocument should be a valid pointer (not null)");
 
   AutoJSAPI jsapi;
   jsapi.Init();
   JSContext* cx = jsapi.cx();
 
-  // Failure to create or run the regexp results in the invalid pattern
-  // matching, but we can still report the error to the console.
-  jsapi.TakeOwnershipOfErrorReporting();
-
   // We can use the junk scope here, because we're just using it for
   // regexp evaluation, not actual script execution.
   JSAutoCompartment ac(cx, xpc::UnprivilegedJunkScope());
 
   // The pattern has to match the entire value.
   aPattern.Insert(NS_LITERAL_STRING("^(?:"), 0);
   aPattern.AppendLiteral(")$");
 
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -2243,23 +2243,27 @@ nsDOMWindowUtils::BeginTabSwitch()
 }
 
 static bool
 ComputeAnimationValue(nsCSSProperty aProperty,
                       Element* aElement,
                       const nsAString& aInput,
                       StyleAnimationValue& aOutput)
 {
-
-  if (!StyleAnimationValue::ComputeValue(aProperty,
-                                         aElement,
-                                         CSSPseudoElementType::NotPseudo,
-                                         aInput,
-                                         false,
-                                         aOutput)) {
+  nsIDocument* doc = aElement->GetCurrentDoc();
+  nsIPresShell* shell = doc->GetShell();
+  if (!shell) {
+    return false;
+  }
+
+  RefPtr<nsStyleContext> styleContext =
+    nsComputedDOMStyle::GetStyleContextForElement(aElement, nullptr, shell);
+
+  if (!StyleAnimationValue::ComputeValue(aProperty, aElement, styleContext,
+                                         aInput, false, aOutput)) {
     return false;
   }
 
   // This matches TransExtractComputedValue in nsTransitionManager.cpp.
   if (aProperty == eCSSProperty_visibility) {
     MOZ_ASSERT(aOutput.GetUnit() == StyleAnimationValue::eUnit_Enumerated,
                "unexpected unit");
     aOutput.SetIntValue(aOutput.GetIntValue(),
--- a/dom/base/nsGenericDOMDataNode.cpp
+++ b/dom/base/nsGenericDOMDataNode.cpp
@@ -366,17 +366,20 @@ nsGenericDOMDataNode::SetTextInternal(ui
 
   if (document && mText.IsBidi()) {
     // If we found bidi characters in mText.SetTo() above, indicate that the
     // document contains bidi characters.
     document->SetBidiEnabled();
   }
 
   if (dirAffectsAncestor) {
-    TextNodeChangedDirection(this, oldDir, aNotify);
+    // dirAffectsAncestor being true implies that we have a text node, see
+    // above.
+    MOZ_ASSERT(NodeType() == nsIDOMNode::TEXT_NODE);
+    TextNodeChangedDirection(static_cast<nsTextNode*>(this), oldDir, aNotify);
   }
 
   // Notify observers
   if (aNotify) {
     CharacterDataChangeInfo info = {
       aOffset == textLength,
       aOffset,
       endOffset,
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -13785,17 +13785,20 @@ nsGlobalWindow::Orientation() const
 #endif
 
 Console*
 nsGlobalWindow::GetConsole(ErrorResult& aRv)
 {
   MOZ_RELEASE_ASSERT(IsInnerWindow());
 
   if (!mConsole) {
-    mConsole = new Console(AsInner());
+    mConsole = Console::Create(AsInner(), aRv);
+    if (NS_WARN_IF(aRv.Failed())) {
+      return nullptr;
+    }
   }
 
   return mConsole;
 }
 
 already_AddRefed<External>
 nsGlobalWindow::GetExternal(ErrorResult& aRv)
 {
--- a/dom/base/nsINode.h
+++ b/dom/base/nsINode.h
@@ -1619,18 +1619,21 @@ public:
                "SetHasTextNodeDirectionalityMap on non-text node");
     SetBoolFlag(NodeHasTextNodeDirectionalityMap);
   }
   void ClearHasTextNodeDirectionalityMap() {
     MOZ_ASSERT(NodeType() == nsIDOMNode::TEXT_NODE,
                "ClearHasTextNodeDirectionalityMap on non-text node");
     ClearBoolFlag(NodeHasTextNodeDirectionalityMap);
   }
-  bool HasTextNodeDirectionalityMap() const
-    { return GetBoolFlag(NodeHasTextNodeDirectionalityMap); }
+  bool HasTextNodeDirectionalityMap() const {
+    MOZ_ASSERT(NodeType() == nsIDOMNode::TEXT_NODE,
+               "HasTextNodeDirectionalityMap on non-text node");
+    return GetBoolFlag(NodeHasTextNodeDirectionalityMap);
+  }
 
   void SetHasDirAuto() { SetBoolFlag(NodeHasDirAuto); }
   void ClearHasDirAuto() { ClearBoolFlag(NodeHasDirAuto); }
   bool HasDirAuto() const { return GetBoolFlag(NodeHasDirAuto); }
 
   void SetAncestorHasDirAuto() { SetBoolFlag(NodeAncestorHasDirAuto); }
   void ClearAncestorHasDirAuto() { ClearBoolFlag(NodeAncestorHasDirAuto); }
   bool AncestorHasDirAuto() const { return GetBoolFlag(NodeAncestorHasDirAuto); }
--- a/dom/base/nsJSEnvironment.cpp
+++ b/dom/base/nsJSEnvironment.cpp
@@ -492,86 +492,16 @@ private:
 
 bool ScriptErrorEvent::sHandlingScriptError = false;
 
 // This temporarily lives here to avoid code churn. It will go away entirely
 // soon.
 namespace xpc {
 
 void
-SystemErrorReporter(JSContext *cx, const char *message, JSErrorReport *report)
-{
-  JS::Rooted<JS::Value> exception(cx);
-  ::JS_GetPendingException(cx, &exception);
-
-  // Note: we must do this before running any more code on cx.
-  ::JS_ClearPendingException(cx);
-
-  MOZ_ASSERT(cx == nsContentUtils::GetCurrentJSContext());
-  nsCOMPtr<nsIGlobalObject> globalObject;
-
-  // The eventual plan is for error reporting to happen in the AutoJSAPI
-  // destructor using the global with which the AutoJSAPI was initialized. We
-  // can't _quite_ do that yet, so we take a sloppy stab at those semantics. If
-  // we have an nsIScriptContext, we'll get the right answer modulo
-  // non-current-inners.
-  //
-  // Otherwise, we just use the privileged junk scope. This has the effect of
-  // causing us to report the error as "chrome javascript" rather than "content
-  // javascript", and not invoking any error reporters. This is exactly what we
-  // want here.
-  if (nsIScriptContext* scx = GetScriptContextFromJSContext(cx)) {
-    nsCOMPtr<nsPIDOMWindowOuter> outer = do_QueryInterface(scx->GetGlobalObject());
-    if (outer) {
-      globalObject = nsGlobalWindow::Cast(outer->GetCurrentInnerWindow());
-    }
-  }
-
-  // We run addons in a separate privileged compartment, but they still expect
-  // to trigger the onerror handler of their associated DOMWindow.
-  //
-  // Note that the way we do this right now is sloppy. Error reporters can
-  // theoretically be triggered at arbitrary times (not just immediately before
-  // an AutoJSAPI comes off the stack), so we don't really have a way of knowing
-  // that the global of the current compartment is the correct global with which
-  // to report the error. But in practice this is probably fine for the time
-  // being, and will get cleaned up soon when we fix bug 981187.
-  if (!globalObject && JS::CurrentGlobalOrNull(cx)) {
-    globalObject = xpc::AddonWindowOrNull(JS::CurrentGlobalOrNull(cx));
-  }
-
-  if (!globalObject) {
-    globalObject = xpc::NativeGlobal(xpc::PrivilegedJunkScope());
-  }
-
-  if (globalObject) {
-    RefPtr<xpc::ErrorReport> xpcReport = new xpc::ErrorReport();
-    bool isChrome = nsContentUtils::IsSystemPrincipal(globalObject->PrincipalOrNull());
-    nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(globalObject);
-    xpcReport->Init(report, message, isChrome, win ? win->WindowID() : 0);
-
-    // If we can't dispatch an event to a window, report it to the console
-    // directly. This includes the case where the error was an OOM, because
-    // triggering a scripted event handler is likely to generate further OOMs.
-    if (!win || JSREPORT_IS_WARNING(xpcReport->mFlags) ||
-        report->errorNumber == JSMSG_OUT_OF_MEMORY)
-    {
-      JS::Rooted<JSObject*> stack(cx,
-        xpc::FindExceptionStackForConsoleReport(win, exception));
-      xpcReport->LogToConsoleWithStack(stack);
-      return;
-    }
-
-    // Otherwise, we need to asynchronously invoke onerror before we can decide
-    // whether or not to report the error to the console.
-    DispatchScriptErrorEvent(win, JS_GetRuntime(cx), xpcReport, exception);
-  }
-}
-
-void
 DispatchScriptErrorEvent(nsPIDOMWindowInner *win, JSRuntime *rt, xpc::ErrorReport *xpcReport,
                          JS::Handle<JS::Value> exception)
 {
   nsContentUtils::AddScriptRunner(new ScriptErrorEvent(win, rt, xpcReport, exception));
 }
 
 } /* namespace xpc */
 
@@ -837,17 +767,16 @@ nsJSContext::InitContext()
 
 nsresult
 nsJSContext::SetProperty(JS::Handle<JSObject*> aTarget, const char* aPropName, nsISupports* aArgs)
 {
   AutoJSAPI jsapi;
   if (NS_WARN_IF(!jsapi.Init(GetGlobalObject()))) {
     return NS_ERROR_FAILURE;
   }
-  jsapi.TakeOwnershipOfErrorReporting();
   JSContext* cx = jsapi.cx();
 
   JS::AutoValueVector args(cx);
 
   JS::Rooted<JSObject*> global(cx, GetWindowProxy());
   nsresult rv =
     ConvertSupportsTojsvals(aArgs, global, args);
   NS_ENSURE_SUCCESS(rv, rv);
--- a/dom/base/nsJSUtils.cpp
+++ b/dom/base/nsJSUtils.cpp
@@ -92,17 +92,16 @@ nsJSUtils::CompileFunction(AutoJSAPI& js
                            JS::AutoObjectVector& aScopeChain,
                            JS::CompileOptions& aOptions,
                            const nsACString& aName,
                            uint32_t aArgCount,
                            const char** aArgArray,
                            const nsAString& aBody,
                            JSObject** aFunctionObject)
 {
-  MOZ_ASSERT(jsapi.OwnsErrorReporting());
   JSContext* cx = jsapi.cx();
   MOZ_ASSERT(js::GetEnterCompartmentDepth(cx) > 0);
   MOZ_ASSERT_IF(aScopeChain.length() != 0,
                 js::IsObjectInContextCompartment(aScopeChain[0], cx));
   MOZ_ASSERT_IF(aOptions.versionSet, aOptions.version != JSVERSION_UNKNOWN);
   mozilla::DebugOnly<nsIScriptContext*> ctx = GetScriptContextFromJSContext(cx);
   MOZ_ASSERT_IF(ctx, ctx->IsContextInitialized());
 
--- a/dom/base/nsScriptLoader.cpp
+++ b/dom/base/nsScriptLoader.cpp
@@ -824,17 +824,16 @@ nsScriptLoader::AttemptAsyncScriptCompil
   if (!globalObject) {
     return NS_ERROR_FAILURE;
   }
 
   AutoJSAPI jsapi;
   if (!jsapi.Init(globalObject)) {
     return NS_ERROR_FAILURE;
   }
-  jsapi.TakeOwnershipOfErrorReporting();
 
   JSContext* cx = jsapi.cx();
   JS::Rooted<JSObject*> global(cx, globalObject->GetGlobalJSObject());
   JS::CompileOptions options(cx);
   FillCompileOptionsForRequest(jsapi, aRequest, global, &options);
 
   if (!JS::CanCompileOffThread(cx, options, aRequest->mScriptTextLength)) {
     return NS_ERROR_FAILURE;
--- a/dom/base/nsTextNode.h
+++ b/dom/base/nsTextNode.h
@@ -44,16 +44,17 @@ public:
     Init();
   }
 
   // nsISupports
   NS_DECL_ISUPPORTS_INHERITED
 
   // nsIDOMNode
   NS_FORWARD_NSIDOMNODE_TO_NSINODE
+  using mozilla::dom::Text::GetParentElement;
 
   // nsIDOMCharacterData
   NS_FORWARD_NSIDOMCHARACTERDATA(nsGenericDOMDataNode::)
   using nsGenericDOMDataNode::SetData; // Prevent hiding overloaded virtual function.
 
   // nsIDOMText
   NS_FORWARD_NSIDOMTEXT(nsGenericDOMDataNode::)
 
--- a/dom/base/test/chrome/chrome.ini
+++ b/dom/base/test/chrome/chrome.ini
@@ -59,17 +59,16 @@ skip-if = buildapp == 'mulet'
 [test_bug914381.html]
 [test_bug990812.xul]
 [test_bug1063837.xul]
 [test_bug1139964.xul]
 [test_bug1209621.xul]
 [test_cpows.xul]
 skip-if = buildapp == 'mulet'
 [test_document_register.xul]
-[test_mutationobserver_anonymous.html]
 [test_registerElement_content.xul]
 [test_registerElement_ep.xul]
 [test_domparsing.xul]
 [test_fileconstructor.xul]
 [test_fileconstructor_tempfile.xul]
 [test_nsITextInputProcessor.xul]
 [test_title.xul]
 [test_windowroot.xul]
deleted file mode 100644
--- a/dom/base/test/chrome/test_mutationobserver_anonymous.html
+++ /dev/null
@@ -1,251 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=1034110
--->
-<head>
-  <title>Test for Bug 1034110</title>
-  <script type="application/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
-  <script type="application/javascript"  src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
-  <script type="application/javascript"  src="chrome://mochikit/content/tests/SimpleTest/ChromeUtils.js"></script>
-  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
-</head>
-<body>
-<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1034110">Mozilla Bug 1034110</a>
-<style type="text/css">
-  #pseudo.before::before { content: "before"; }
-  #pseudo.after::after { content: "after"; }
-</style>
-<div id="pseudo"></div>
-<video id="video"></video>
-<p id="display"></p>
-<div id="content" style="display: none">
-
-</div>
-
-<pre id="test">
-<script type="application/javascript;version=1.7">
-
-/** Test for Bug 1034110 **/
-
-SimpleTest.waitForExplicitFinish();
-const {Cc, Ci, Cu} = SpecialPowers;
-
-function getWalker(node) {
-  let walker = Cc["@mozilla.org/inspector/deep-tree-walker;1"].createInstance(Ci.inIDeepTreeWalker);
-  walker.showAnonymousContent = true;
-  walker.init(node.ownerDocument, Ci.nsIDOMNodeFilter.SHOW_ALL);
-  walker.currentNode = node;
-  return walker;
-}
-
-function getFirstChild(parent) {
-  return SpecialPowers.unwrap(getWalker(parent).firstChild());
-}
-
-function getLastChild(parent) {
-  return SpecialPowers.unwrap(getWalker(parent).lastChild());
-}
-
-function assertSamePseudoElement(which, node1, node2) {
-  is(node1.nodeName, "_moz_generated_content_" + which,
-     "Correct pseudo element type");
-  is(node1, node2,
-     "Referencing the same ::after element");
-}
-
-window.onload = function () {
-  testOneAdded();
-};
-
-function testOneAdded() {
-  let parent = document.getElementById("pseudo");
-  var m = new MutationObserver(function(records, observer) {
-    is(records.length, 1, "Correct number of records");
-    is(records[0].type, "nativeAnonymousChildList", "Correct record type");
-    is(records[0].target, parent, "Correct target");
-
-    is(records[0].addedNodes.length, 1, "Should have got addedNodes");
-    assertSamePseudoElement("before", records[0].addedNodes[0], getFirstChild(parent));
-    is(records[0].removedNodes.length, 0, "Shouldn't have got removedNodes");
-
-    observer.disconnect();
-    testAddedAndRemoved();
-  });
-
-  m.observe(parent, { nativeAnonymousChildList: true});
-  parent.className = "before";
-}
-
-function testAddedAndRemoved() {
-  let parent = document.getElementById("pseudo");
-  let originalBeforeElement = getFirstChild(parent);
-  var m = new MutationObserver(function(records, observer) {
-    is(records.length, 2, "Correct number of records");
-    is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
-    is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
-    is(records[0].target, parent, "Correct target (1)");
-    is(records[1].target, parent, "Correct target (2)");
-
-    // Two records are sent - one for removed and one for added.
-    is(records[0].addedNodes.length, 0, "Shouldn't have got addedNodes");
-    is(records[0].removedNodes.length, 1, "Should have got removedNodes");
-    assertSamePseudoElement("before", records[0].removedNodes[0], originalBeforeElement);
-
-    is(records[1].addedNodes.length, 1, "Should have got addedNodes");
-    assertSamePseudoElement("after", records[1].addedNodes[0], getLastChild(parent));
-    is(records[1].removedNodes.length, 0, "Shouldn't have got removedNodes");
-
-    observer.disconnect();
-    testRemoved();
-  });
-
-  m.observe(parent, { nativeAnonymousChildList: true});
-  parent.className = "after";
-}
-
-function testRemoved() {
-  let parent = document.getElementById("pseudo");
-  let originalAfterElement = getLastChild(parent);
-  var m = new MutationObserver(function(records, observer) {
-    is(records.length, 1, "Correct number of records");
-    is(records[0].type, "nativeAnonymousChildList", "Correct record type");
-    is(records[0].target, parent, "Correct target");
-
-    is(records[0].addedNodes.length, 0, "Shouldn't have got addedNodes");
-    is(records[0].removedNodes.length, 1, "Should have got removedNodes");
-    assertSamePseudoElement("after", records[0].removedNodes[0], originalAfterElement);
-
-    observer.disconnect();
-    testMultipleAdded();
-  });
-
-  m.observe(parent, { nativeAnonymousChildList: true });
-  parent.className = "";
-}
-
-function testMultipleAdded() {
-  let parent = document.getElementById("pseudo");
-  var m = new MutationObserver(function(records, observer) {
-    is(records.length, 2, "Correct number of records");
-    is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
-    is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
-    is(records[0].target, parent, "Correct target (1)");
-    is(records[1].target, parent, "Correct target (2)");
-
-    is(records[0].addedNodes.length, 1, "Should have got addedNodes");
-    assertSamePseudoElement("before", records[0].addedNodes[0], getFirstChild(parent));
-    is(records[0].removedNodes.length, 0, "Shouldn't have got removedNodes");
-
-    is(records[1].addedNodes.length, 1, "Should have got addedNodes");
-    assertSamePseudoElement("after", records[1].addedNodes[0], getLastChild(parent));
-    is(records[1].removedNodes.length, 0, "Shouldn't have got removedNodes");
-
-    observer.disconnect();
-    testRemovedDueToDisplay();
-  });
-
-  m.observe(parent, { nativeAnonymousChildList: true });
-  parent.className = "before after";
-}
-
-function testRemovedDueToDisplay() {
-  let parent = document.getElementById("pseudo");
-  let originalBeforeElement = getFirstChild(parent);
-  let originalAfterElement = getLastChild(parent);
-  var m = new MutationObserver(function(records, observer) {
-    is(records.length, 2, "Correct number of records");
-    is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
-    is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
-    is(records[0].target, parent, "Correct target (1)");
-    is(records[1].target, parent, "Correct target (2)");
-
-    is(records[0].addedNodes.length, 0, "Shouldn't have got addedNodes");
-    is(records[0].removedNodes.length, 1, "Should have got removedNodes");
-    assertSamePseudoElement("before", records[0].removedNodes[0], originalBeforeElement);
-
-    is(records[1].addedNodes.length, 0, "Shouldn't have got addedNodes");
-    is(records[1].removedNodes.length, 1, "Should have got removedNodes");
-    assertSamePseudoElement("after", records[1].removedNodes[0], originalAfterElement);
-
-    observer.disconnect();
-    testAddedDueToDisplay();
-  });
-
-  m.observe(parent, { nativeAnonymousChildList: true });
-  parent.style.display = "none";
-}
-
-function testAddedDueToDisplay() {
-  let parent = document.getElementById("pseudo");
-  var m = new MutationObserver(function(records, observer) {
-    is(records.length, 2, "Correct number of records");
-    is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
-    is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
-    is(records[0].target, parent, "Correct target (1)");
-    is(records[1].target, parent, "Correct target (2)");
-
-    is(records[0].addedNodes.length, 1, "Should have got addedNodes");
-    assertSamePseudoElement("before", records[0].addedNodes[0], getFirstChild(parent));
-    is(records[0].removedNodes.length, 0, "Shouldn't have got removedNodes");
-
-    is(records[1].addedNodes.length, 1, "Should have got addedNodes");
-    assertSamePseudoElement("after", records[1].addedNodes[0], getLastChild(parent));
-    is(records[1].removedNodes.length, 0, "Shouldn't have got removedNodes");
-
-    observer.disconnect();
-    testDifferentTargetNoSubtree();
-  });
-
-  m.observe(parent, { nativeAnonymousChildList: true });
-  parent.style.display = "block";
-}
-
-function testDifferentTargetNoSubtree() {
-  let parent = document.getElementById("pseudo");
-  var m = new MutationObserver(function(records, observer) {
-    ok(false,
-       "No mutation should fire when observing on a parent without subtree option.");
-  });
-  m.observe(document, { nativeAnonymousChildList: true });
-  parent.style.display = "none";
-
-  // Wait for the actual mutation to come through, making sure that
-  // the original observer never fires.
-  var m2 = new MutationObserver(function(records, observer) {
-    ok(!getFirstChild(parent), "Pseudo element has been removed, but no mutation");
-    ok(!getLastChild(parent), "Pseudo element has been removed, but no mutation");
-    observer.disconnect();
-    testSubtree();
-  });
-  m2.observe(parent, { nativeAnonymousChildList: true });
-}
-
-function testSubtree() {
-  let parent = document.getElementById("pseudo");
-  var m = new MutationObserver(function(records, observer) {
-    is(records.length, 2, "Correct number of records");
-    is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
-    is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
-    is(records[0].target, parent, "Correct target (1)");
-    is(records[1].target, parent, "Correct target (2)");
-
-    is(records[0].addedNodes.length, 1, "Should have got addedNodes");
-    assertSamePseudoElement("before", records[0].addedNodes[0], getFirstChild(parent));
-    is(records[0].removedNodes.length, 0, "Shouldn't have got removedNodes");
-
-    is(records[1].addedNodes.length, 1, "Should have got addedNodes");
-    assertSamePseudoElement("after", records[1].addedNodes[0], getLastChild(parent));
-    is(records[1].removedNodes.length, 0, "Shouldn't have got removedNodes");
-
-    observer.disconnect();
-    SimpleTest.finish();
-  });
-  m.observe(document, { nativeAnonymousChildList: true, subtree: true });
-  parent.style.display = "block";
-}
-
-</script>
-</pre>
-</body>
-</html>
--- a/dom/base/test/mochitest.ini
+++ b/dom/base/test/mochitest.ini
@@ -788,16 +788,17 @@ skip-if = (os != 'b2g' && os != 'android
 skip-if = (os != 'b2g' && os != 'android')    # meta-viewport tag support is mobile-only
 [test_meta_viewport6.html]
 skip-if = (os != 'b2g' && os != 'android')    # meta-viewport tag support is mobile-only
 [test_meta_viewport7.html]
 skip-if = (os != 'b2g' && os != 'android')    # meta-viewport tag support is mobile-only
 [test_mozfiledataurl.html]
 skip-if = buildapp == 'mulet' || buildapp == 'b2g' || toolkit == 'android' #TIMED_OUT
 [test_mozMatchesSelector.html]
+[test_mutationobserver_anonymous.html]
 [test_mutationobservers.html]
 skip-if = buildapp == 'b2g' # b2g(bug 901385, showmodaldialog) b2g-debug(bug 901385, showmodaldialog) b2g-desktop(bug 901385, showmodaldialog)
 [test_nodelist_holes.html]
 [test_plugin_freezing.html]
 skip-if = buildapp == 'b2g' || toolkit == 'android' #CLICK_TO_PLAY
 [test_processing_instruction_update_stylesheet.xhtml]
 [test_progress_events_for_gzip_data.html]
 [test_range_bounds.html]
new file mode 100644
--- /dev/null
+++ b/dom/base/test/test_mutationobserver_anonymous.html
@@ -0,0 +1,245 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=1034110
+-->
+<head>
+  <title>Test for Bug 1034110</title>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript"  src="/tests/SimpleTest/EventUtils.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1034110">Mozilla Bug 1034110</a>
+<style type="text/css">
+  #pseudo.before::before { content: "before"; }
+  #pseudo.after::after { content: "after"; }
+</style>
+<div id="pseudo"></div>
+<video id="video"></video>
+<p id="display"></p>
+<div id="content" style="display: none">
+
+</div>
+
+<pre id="test">
+<script type="application/javascript;version=1.7">
+
+/** Test for Bug 1034110 **/
+
+SimpleTest.waitForExplicitFinish();
+
+function getWalker(node) {
+  return SpecialPowers.createDOMWalker(node, true);
+}
+
+function getFirstChild(parent) {
+  return SpecialPowers.unwrap(getWalker(parent).firstChild);
+}
+
+function getLastChild(parent) {
+  return SpecialPowers.unwrap(getWalker(parent).lastChild);
+}
+
+function assertSamePseudoElement(which, node1, node2) {
+  is(SpecialPowers.wrap(node1).nodeName, "_moz_generated_content_" + which,
+     "Correct pseudo element type");
+  is(node1, node2,
+     "Referencing the same ::after element");
+}
+
+window.onload = function () {
+  testOneAdded();
+};
+
+function testOneAdded() {
+  let parent = document.getElementById("pseudo");
+  var m = new MutationObserver(function(records, observer) {
+    is(records.length, 1, "Correct number of records");
+    is(records[0].type, "nativeAnonymousChildList", "Correct record type");
+    is(records[0].target, parent, "Correct target");
+
+    is(records[0].addedNodes.length, 1, "Should have got addedNodes");
+    assertSamePseudoElement("before", records[0].addedNodes[0], getFirstChild(parent));
+    is(records[0].removedNodes.length, 0, "Shouldn't have got removedNodes");
+
+    observer.disconnect();
+    testAddedAndRemoved();
+  });
+
+  SpecialPowers.observeMutationEvents(m, parent, true);
+  parent.className = "before";
+}
+
+function testAddedAndRemoved() {
+  let parent = document.getElementById("pseudo");
+  let originalBeforeElement = getFirstChild(parent);
+  var m = new MutationObserver(function(records, observer) {
+    is(records.length, 2, "Correct number of records");
+    is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
+    is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
+    is(records[0].target, parent, "Correct target (1)");
+    is(records[1].target, parent, "Correct target (2)");
+
+    // Two records are sent - one for removed and one for added.
+    is(records[0].addedNodes.length, 0, "Shouldn't have got addedNodes");
+    is(records[0].removedNodes.length, 1, "Should have got removedNodes");
+    assertSamePseudoElement("before", records[0].removedNodes[0], originalBeforeElement);
+
+    is(records[1].addedNodes.length, 1, "Should have got addedNodes");
+    assertSamePseudoElement("after", records[1].addedNodes[0], getLastChild(parent));
+    is(records[1].removedNodes.length, 0, "Shouldn't have got removedNodes");
+
+    observer.disconnect();
+    testRemoved();
+  });
+
+  SpecialPowers.observeMutationEvents(m, parent, true);
+  parent.className = "after";
+}
+
+function testRemoved() {
+  let parent = document.getElementById("pseudo");
+  let originalAfterElement = getLastChild(parent);
+  var m = new MutationObserver(function(records, observer) {
+    is(records.length, 1, "Correct number of records");
+    is(records[0].type, "nativeAnonymousChildList", "Correct record type");
+    is(records[0].target, parent, "Correct target");
+
+    is(records[0].addedNodes.length, 0, "Shouldn't have got addedNodes");
+    is(records[0].removedNodes.length, 1, "Should have got removedNodes");
+    assertSamePseudoElement("after", records[0].removedNodes[0], originalAfterElement);
+
+    observer.disconnect();
+    testMultipleAdded();
+  });
+
+  SpecialPowers.observeMutationEvents(m, parent, true);
+  parent.className = "";
+}
+
+function testMultipleAdded() {
+  let parent = document.getElementById("pseudo");
+  var m = new MutationObserver(function(records, observer) {
+    is(records.length, 2, "Correct number of records");
+    is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
+    is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
+    is(records[0].target, parent, "Correct target (1)");
+    is(records[1].target, parent, "Correct target (2)");
+
+    is(records[0].addedNodes.length, 1, "Should have got addedNodes");
+    assertSamePseudoElement("before", records[0].addedNodes[0], getFirstChild(parent));
+    is(records[0].removedNodes.length, 0, "Shouldn't have got removedNodes");
+
+    is(records[1].addedNodes.length, 1, "Should have got addedNodes");
+    assertSamePseudoElement("after", records[1].addedNodes[0], getLastChild(parent));
+    is(records[1].removedNodes.length, 0, "Shouldn't have got removedNodes");
+
+    observer.disconnect();
+    testRemovedDueToDisplay();
+  });
+
+  SpecialPowers.observeMutationEvents(m, parent, true);
+  parent.className = "before after";
+}
+
+function testRemovedDueToDisplay() {
+  let parent = document.getElementById("pseudo");
+  let originalBeforeElement = getFirstChild(parent);
+  let originalAfterElement = getLastChild(parent);
+  var m = new MutationObserver(function(records, observer) {
+    is(records.length, 2, "Correct number of records");
+    is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
+    is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
+    is(records[0].target, parent, "Correct target (1)");
+    is(records[1].target, parent, "Correct target (2)");
+
+    is(records[0].addedNodes.length, 0, "Shouldn't have got addedNodes");
+    is(records[0].removedNodes.length, 1, "Should have got removedNodes");
+    assertSamePseudoElement("before", records[0].removedNodes[0], originalBeforeElement);
+
+    is(records[1].addedNodes.length, 0, "Shouldn't have got addedNodes");
+    is(records[1].removedNodes.length, 1, "Should have got removedNodes");
+    assertSamePseudoElement("after", records[1].removedNodes[0], originalAfterElement);
+
+    observer.disconnect();
+    testAddedDueToDisplay();
+  });
+
+  SpecialPowers.observeMutationEvents(m, parent, true);
+  parent.style.display = "none";
+}
+
+function testAddedDueToDisplay() {
+  let parent = document.getElementById("pseudo");
+  var m = new MutationObserver(function(records, observer) {
+    is(records.length, 2, "Correct number of records");
+    is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
+    is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
+    is(records[0].target, parent, "Correct target (1)");
+    is(records[1].target, parent, "Correct target (2)");
+
+    is(records[0].addedNodes.length, 1, "Should have got addedNodes");
+    assertSamePseudoElement("before", records[0].addedNodes[0], getFirstChild(parent));
+    is(records[0].removedNodes.length, 0, "Shouldn't have got removedNodes");
+
+    is(records[1].addedNodes.length, 1, "Should have got addedNodes");
+    assertSamePseudoElement("after", records[1].addedNodes[0], getLastChild(parent));
+    is(records[1].removedNodes.length, 0, "Shouldn't have got removedNodes");
+
+    observer.disconnect();
+    testDifferentTargetNoSubtree();
+  });
+
+  SpecialPowers.observeMutationEvents(m, parent, true);
+  parent.style.display = "block";
+}
+
+function testDifferentTargetNoSubtree() {
+  let parent = document.getElementById("pseudo");
+  var m = new MutationObserver(function(records, observer) {
+    ok(false,
+       "No mutation should fire when observing on a parent without subtree option.");
+  });
+  SpecialPowers.observeMutationEvents(m, document, true);
+  parent.style.display = "none";
+
+  // Wait for the actual mutation to come through, making sure that
+  // the original observer never fires.
+  var m2 = new MutationObserver(function(records, observer) {
+    ok(!getFirstChild(parent), "Pseudo element has been removed, but no mutation");
+    ok(!getLastChild(parent), "Pseudo element has been removed, but no mutation");
+    observer.disconnect();
+    testSubtree();
+  });
+  SpecialPowers.observeMutationEvents(m2, parent, true);
+}
+
+function testSubtree() {
+  let parent = document.getElementById("pseudo");
+  var m = new MutationObserver(function(records, observer) {
+    is(records.length, 2, "Correct number of records");
+    is(records[0].type, "nativeAnonymousChildList", "Correct record type (1)");
+    is(records[1].type, "nativeAnonymousChildList", "Correct record type (2)");
+    is(records[0].target, parent, "Correct target (1)");
+    is(records[1].target, parent, "Correct target (2)");
+
+    is(records[0].addedNodes.length, 1, "Should have got addedNodes");
+    assertSamePseudoElement("before", records[0].addedNodes[0], getFirstChild(parent));
+    is(records[0].removedNodes.length, 0, "Shouldn't have got removedNodes");
+
+    is(records[1].addedNodes.length, 1, "Should have got addedNodes");
+    assertSamePseudoElement("after", records[1].addedNodes[0], getLastChild(parent));
+    is(records[1].removedNodes.length, 0, "Shouldn't have got removedNodes");
+
+    observer.disconnect();
+    SimpleTest.finish();
+  });
+  SpecialPowers.observeMutationEvents(m, document, true, true);
+  parent.style.display = "block";
+}
+
+</script>
+</pre>
+</body>
+</html>
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -756,47 +756,16 @@ CreateInterfaceObject(JSContext* cx, JS:
                           JS::ObjectValue(*namedConstructor));
       ++namedConstructors;
     }
   }
 
   return constructor;
 }
 
-bool
-DefineWebIDLBindingUnforgeablePropertiesOnXPCObject(JSContext* cx,
-                                                    JS::Handle<JSObject*> obj,
-                                                    const NativeProperties* properties)
-{
-  if (properties->unforgeableAttributes &&
-      !DefinePrefable(cx, obj, properties->unforgeableAttributes)) {
-    return false;
-  }
-
-  return true;
-}
-
-bool
-DefineWebIDLBindingPropertiesOnXPCObject(JSContext* cx,
-                                         JS::Handle<JSObject*> obj,
-                                         const NativeProperties* properties)
-{
-  if (properties->methods &&
-      !DefinePrefable(cx, obj, properties->methods)) {
-    return false;
-  }
-
-  if (properties->attributes &&
-      !DefinePrefable(cx, obj, properties->attributes)) {
-    return false;
-  }
-
-  return true;
-}
-
 static JSObject*
 CreateInterfacePrototypeObject(JSContext* cx, JS::Handle<JSObject*> global,
                                JS::Handle<JSObject*> parentProto,
                                const js::Class* protoClass,
                                const NativeProperties* properties,
                                const NativeProperties* chromeOnlyProperties)
 {
   JS::Rooted<JSObject*> ourProto(cx,
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -639,26 +639,16 @@ DefineUnforgeableMethods(JSContext* cx, 
 
 /*
  * Define the unforgeable attributes on an object.
  */
 bool
 DefineUnforgeableAttributes(JSContext* cx, JS::Handle<JSObject*> obj,
                             const Prefable<const JSPropertySpec>* props);
 
-bool
-DefineWebIDLBindingUnforgeablePropertiesOnXPCObject(JSContext* cx,
-                                                    JS::Handle<JSObject*> obj,
-                                                    const NativeProperties* properties);
-
-bool
-DefineWebIDLBindingPropertiesOnXPCObject(JSContext* cx,
-                                         JS::Handle<JSObject*> obj,
-                                         const NativeProperties* properties);
-
 #define HAS_MEMBER_TYPEDEFS                                               \
 private:                                                                  \
   typedef char yes[1];                                                    \
   typedef char no[2]
 
 #ifdef _MSC_VER
 #define HAS_MEMBER_CHECK(_name)                                           \
   template<typename V> static yes& Check##_name(char (*)[(&V::_name == 0) + 1])
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -610,16 +610,26 @@ DOMInterfaces = {
         'direction': 'getDirection'
     }
 },
 
 'IDBCursorWithValue': {
     'nativeType': 'mozilla::dom::IDBCursor',
 },
 
+'IDBDatabase': {
+    'implicitJSContext': [ 'transaction', 'createMutableFile',
+                           'mozCreateFileHandle' ],
+},
+
+'IDBFactory': {
+    'implicitJSContext': [ 'open', 'deleteDatabase', 'openForPrincipal',
+                           'deleteForPrincipal' ],
+},
+
 'IDBIndex': {
     'binaryNames': {
         'mozGetAll': 'getAll',
         'mozGetAllKeys': 'getAllKeys',
     }
 },
 
 'IDBKeyRange': {
@@ -629,17 +639,18 @@ DOMInterfaces = {
 'IDBLocaleAwareKeyRange': {
     'headerFile': 'IDBKeyRange.h',
     'wrapperCache': False,
 },
 
 'IDBObjectStore': {
     'binaryNames': {
         'mozGetAll': 'getAll'
-    }
+    },
+    'implicitJSContext': [ 'clear' ],
 },
 
 'IDBOpenDBRequest': {
     'headerFile': 'IDBRequest.h'
 },
 
 'IDBVersionChangeEvent': {
     'headerFile': 'IDBEvents.h',
@@ -1589,31 +1600,30 @@ DOMInterfaces = {
     'headerFile': 'mozilla/dom/WorkerPrivate.h',
     'nativeType': 'mozilla::dom::workers::WorkerPrivate',
 },
 
 'WorkerDebuggerGlobalScope': {
     'headerFile': 'mozilla/dom/WorkerScope.h',
     'nativeType': 'mozilla::dom::workers::WorkerDebuggerGlobalScope',
     'implicitJSContext': [
-        'dump', 'global', 'reportError',
+        'dump', 'global', 'reportError', 'setConsoleEventHandler',
     ],
 },
 
 'WorkerGlobalScope': {
     'headerFile': 'mozilla/dom/WorkerScope.h',
     'workers': True,
     'concrete': False,
     'implicitJSContext': [
         'close',
     ],
     # Rename a few things so we don't have both classes and methods
     # with the same name
     'binaryNames': {
-        'console': 'getConsole',
         'performance': 'getPerformance',
     },
 },
 
 'WorkerNavigator': {
     'implicitJSContext': ['getDataStores'],
 },
 
--- a/dom/bindings/CallbackObject.cpp
+++ b/dom/bindings/CallbackObject.cpp
@@ -245,17 +245,16 @@ CallbackObject::CallSetup::~CallSetup()
 
   // Now, if we have a JSContext, report any pending errors on it, unless we
   // were told to re-throw them.
   if (mCx) {
     bool needToDealWithException = mAutoEntryScript->HasException();
     if ((mCompartment && mExceptionHandling == eRethrowContentExceptions) ||
         mExceptionHandling == eRethrowExceptions) {
       mErrorResult.MightThrowJSException();
-      MOZ_ASSERT(mAutoEntryScript->OwnsErrorReporting());
       if (needToDealWithException) {
         JS::Rooted<JS::Value> exn(mCx);
         if (mAutoEntryScript->PeekException(&exn) &&
             ShouldRethrowException(exn)) {
           mAutoEntryScript->ClearException();
           MOZ_ASSERT(!mAutoEntryScript->HasException());
           mErrorResult.ThrowJSException(mCx, exn);
           needToDealWithException = false;
@@ -263,40 +262,30 @@ CallbackObject::CallSetup::~CallSetup()
       }
     }
 
     if (needToDealWithException) {
       // Either we're supposed to report our exceptions, or we're supposed to
       // re-throw them but we failed to get the exception value.  Either way,
       // just report the pending exception, if any.
       //
-      // We don't use nsJSUtils::ReportPendingException here because all it
-      // does at this point is JS_SaveFrameChain and enter a compartment around
-      // a JS_ReportPendingException call.  But our mAutoEntryScript should
-      // already do a JS_SaveFrameChain and we are already in the compartment
-      // we want to be in, so all nsJSUtils::ReportPendingException would do is
-      // screw up our compartment, which is exactly what we do not want.
-      //
       // XXXbz FIXME: bug 979525 means we don't always JS_SaveFrameChain here,
       // so we need to go ahead and do that.  This is also the reason we don't
       // just rely on ~AutoJSAPI reporting the exception for us.  I think if we
       // didn't need to JS_SaveFrameChain here, we could just rely on that.
       JS::Rooted<JSObject*> oldGlobal(mCx, JS::CurrentGlobalOrNull(mCx));
       MOZ_ASSERT(oldGlobal, "How can we not have a global here??");
       bool saved = JS_SaveFrameChain(mCx);
       // Make sure the JSAutoCompartment goes out of scope before the
       // JS_RestoreFrameChain call!
       {
         JSAutoCompartment ac(mCx, oldGlobal);
         MOZ_ASSERT(!JS::DescribeScriptedCaller(mCx),
                    "Our comment above about JS_SaveFrameChain having been "
                    "called is a lie?");
-        // Note that we don't JS_ReportPendingException here because we want to
-        // go through our AutoEntryScript's reporting mechanism instead, since
-        // it currently owns error reporting.
         mAutoEntryScript->ReportException();
       }
       if (saved) {
         JS_RestoreFrameChain(mCx);
       }
 
       if (mErrorResult.IsJSContextException()) {
         // XXXkhuey bug 1117269.
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -602,20 +602,20 @@ class CGPrototypeJSClass(CGThing):
                 nullptr,               /* hasInstance */
                 nullptr,               /* construct */
                 nullptr,               /* trace */
                 JS_NULL_CLASS_SPEC,
                 JS_NULL_CLASS_EXT,
                 JS_NULL_OBJECT_OPS
               },
               ${type},
+              ${prototypeID},
+              ${depth},
               ${hooks},
               "[object ${name}Prototype]",
-              ${prototypeID},
-              ${depth},
               ${protoGetter}
             };
             """,
             name=self.descriptor.interface.identifier.name,
             slotCount=slotCount,
             type=type,
             hooks=NativePropertyHooks(self.descriptor),
             prototypeID=prototypeID,
@@ -711,20 +711,20 @@ class CGInterfaceObjectJSClass(CGThing):
                   nullptr, /* watch */
                   nullptr, /* unwatch */
                   nullptr, /* getElements */
                   nullptr, /* enumerate */
                   InterfaceObjectToString, /* funToString */
                 }
               },
               eInterface,
+              ${prototypeID},
+              ${depth},
               ${hooks},
               "function ${name}() {\\n    [native code]\\n}",
-              ${prototypeID},
-              ${depth},
               ${protoGetter}
             };
             """,
             slotCount=slotCount,
             ctorname=ctorname,
             hasInstance=hasinstance,
             hooks=NativePropertyHooks(self.descriptor),
             name=self.descriptor.interface.identifier.name,
@@ -1943,16 +1943,24 @@ class MemberCondition:
                 self.available == other.available and
                 self.checkAnyPermissions == other.checkAnyPermissions and
                 self.checkAllPermissions == other.checkAllPermissions and
                 self.nonExposedGlobals == other.nonExposedGlobals)
 
     def __ne__(self, other):
         return not self.__eq__(other)
 
+    def hasDisablers(self):
+        return (self.pref is not None or
+                self.func != "nullptr" or
+                self.available != "nullptr" or
+                self.checkAnyPermissions != "nullptr" or
+                self.checkAllPermissions != "nullptr" or
+                self.nonExposedGlobals != "0")
+
 
 class PropertyDefiner:
     """
     A common superclass for defining things on prototype objects.
 
     Subclasses should implement generateArray to generate the actual arrays of
     things we're defining.  They should also set self.chrome to the list of
     things only exposed to chrome and self.regular to the list of things exposed
@@ -2063,68 +2071,85 @@ class PropertyDefiner:
         # inserted at every point where the pref name controlling the member
         # changes.  That will make sure the order of the properties as exposed
         # on the interface and interface prototype objects does not change when
         # pref control is added to members while still allowing us to define all
         # the members in the smallest number of JSAPI calls.
         assert len(array) != 0
         # So we won't put a specTerminator at the very front of the list:
         lastCondition = getCondition(array[0], self.descriptor)
+
         specs = []
+        disablers = []
         prefableSpecs = []
 
-        prefableTemplate = '  { true, %s, %s, %s, %s, %s, &%s[%d] }'
-        prefCacheTemplate = '&%s[%d].enabled'
+        disablersTemplate = dedent(
+            """
+            static PrefableDisablers %s_disablers%d = {
+              true, %s, %s, %s, %s, %s
+            };
+            """)
+        prefableWithDisablersTemplate = '  { &%s_disablers%d, &%s_specs[%d] }'
+        prefableWithoutDisablersTemplate = '  { nullptr, &%s_specs[%d] }'
+        prefCacheTemplate = '&%s[%d].disablers->enabled'
 
         def switchToCondition(props, condition):
             # Remember the info about where our pref-controlled
             # booleans live.
             if condition.pref is not None:
                 props.prefCacheData.append(
                     (condition.pref,
                      prefCacheTemplate % (name, len(prefableSpecs))))
             # Set up pointers to the new sets of specs inside prefableSpecs
-            prefableSpecs.append(prefableTemplate %
-                                 (condition.nonExposedGlobals,
+            if condition.hasDisablers():
+                prefableSpecs.append(prefableWithDisablersTemplate %
+                                     (name, len(specs), name, len(specs)))
+                disablers.append(disablersTemplate %
+                                 (name, len(specs),
+                                  condition.nonExposedGlobals,
                                   condition.func,
                                   condition.available,
                                   condition.checkAnyPermissions,
-                                  condition.checkAllPermissions,
-                                  name + "_specs", len(specs)))
+                                  condition.checkAllPermissions))
+            else:
+                prefableSpecs.append(prefableWithoutDisablersTemplate %
+                                     (name, len(specs)))
 
         switchToCondition(self, lastCondition)
 
         for member in array:
             curCondition = getCondition(member, self.descriptor)
             if lastCondition != curCondition:
                 # Terminate previous list
                 specs.append(specTerminator)
-                # And switch to our new pref
+                # And switch to our new condition
                 switchToCondition(self, curCondition)
                 lastCondition = curCondition
             # And the actual spec
             specs.append(specFormatter(getDataTuple(member)))
         specs.append(specTerminator)
-        prefableSpecs.append("  { false, 0, nullptr, nullptr, nullptr, nullptr, nullptr }")
+        prefableSpecs.append("  { nullptr, nullptr }")
 
         specType = "const " + specType
         arrays = fill(
             """
             static ${specType} ${name}_specs[] = {
             ${specs}
             };
 
+            ${disablers}
             // Can't be const because the pref-enabled boolean needs to be writable
             static Prefable<${specType}> ${name}[] = {
             ${prefableSpecs}
             };
 
             """,
             specType=specType,
             name=name,
+            disablers='\n'.join(disablers),
             specs=',\n'.join(specs),
             prefableSpecs=',\n'.join(prefableSpecs))
         if doIdArrays:
             arrays += "static jsid %s_ids[%i];\n\n" % (name, len(specs))
         return arrays
 
 
 # The length of a method is the minimum of the lengths of the
@@ -3367,17 +3392,17 @@ def InitUnforgeablePropertiesOnHolder(de
     the interface represented by descriptor.
 
     properties is a PropertyArrays instance.
 
     """
     assert (properties.unforgeableAttrs.hasNonChromeOnly() or
             properties.unforgeableAttrs.hasChromeOnly() or
             properties.unforgeableMethods.hasNonChromeOnly() or
-            properties.unforgeableMethods.hasChromeOnly)
+            properties.unforgeableMethods.hasChromeOnly())
 
     unforgeables = []
 
     defineUnforgeableAttrs = fill(
         """
         if (!DefineUnforgeableAttributes(aCx, unforgeableHolder, %s)) {
           $*{failureCode}
         }
@@ -12088,17 +12113,17 @@ class CGNamespacedEnum(CGThing):
 
         # Append a Count.
         entries.append('_' + enumName + '_Count')
 
         # Indent.
         entries = ['  ' + e for e in entries]
 
         # Build the enum body.
-        enumstr = comment + 'enum %s\n{\n%s\n};\n' % (enumName, ',\n'.join(entries))
+        enumstr = comment + 'enum %s : uint16_t\n{\n%s\n};\n' % (enumName, ',\n'.join(entries))
         curr = CGGeneric(declare=enumstr)
 
         # Add some whitespace padding.
         curr = CGWrapper(curr, pre='\n', post='\n')
 
         # Add the namespace.
         curr = CGNamespace(namespace, curr)
 
@@ -15788,17 +15813,16 @@ class CGMaplikeOrSetlikeHelperFunctionGe
             descriptor, name, self.args, self.body, needsBoolReturn)
 
     def getCallSetup(self):
         return dedent(
             """
             MOZ_ASSERT(self);
             AutoJSAPI jsapi;
             jsapi.Init();
-            jsapi.TakeOwnershipOfErrorReporting();
             JSContext* cx = jsapi.cx();
             // It's safe to use UnprivilegedJunkScopeOrWorkerGlobal here because
             // all we want is to wrap into _some_ scope and then unwrap to find
             // the reflector, and wrapping has no side-effects.
             JSAutoCompartment tempCompartment(cx, binding_detail::UnprivilegedJunkScopeOrWorkerGlobal());
             JS::Rooted<JS::Value> v(cx);
             if(!ToJSValue(cx, self, &v)) {
               aRv.Throw(NS_ERROR_UNEXPECTED);
@@ -16354,21 +16378,19 @@ class CGEventGetter(CGNativeMember):
                 }
                 aRetVal.set(${memberName});
                 return;
                 """,
                 memberName=memberName)
         if type.isAny():
             return fill(
                 """
-                JS::ExposeValueToActiveJS(${memberName});
-                aRetVal.set(${memberName});
-                return;
+                ${selfName}(aRetVal);
                 """,
-                memberName=memberName)
+                selfName=self.name)
         if type.isUnion():
             return "aRetVal = " + memberName + ";\n"
         if type.isSequence():
             return "aRetVal = " + memberName + ";\n"
         raise TypeError("Event code generator does not support this type!")
 
     def declare(self, cgClass):
         if getattr(self.member, "originatingInterface",
@@ -16575,18 +16597,38 @@ class CGEventMethod(CGNativeMember):
 
 class CGEventClass(CGBindingImplClass):
     """
     Codegen for the actual Event class implementation for this descriptor
     """
     def __init__(self, descriptor):
         CGBindingImplClass.__init__(self, descriptor, CGEventMethod, CGEventGetter, CGEventSetter, False, "WrapObjectInternal")
         members = []
+        extraMethods = []
         for m in descriptor.interface.members:
             if m.isAttr():
+                if m.type.isAny():
+                    # Add a getter that doesn't need a JSContext.  Note that we
+                    # don't need to do this if our originating interface is not
+                    # the descriptor's interface, because in that case we
+                    # wouldn't generate the getter that _does_ need a JSContext
+                    # either.
+                    extraMethods.append(
+                        ClassMethod(
+                            CGSpecializedGetter.makeNativeName(descriptor, m),
+                            "void",
+                            [Argument("JS::MutableHandle<JS::Value>",
+                                      "aRetVal")],
+                            const=True,
+                            body=fill(
+                                """
+                                JS::ExposeValueToActiveJS(${memberName});
+                                aRetVal.set(${memberName});
+                                """,
+                                memberName=CGDictionary.makeMemberName(m.identifier.name))))
                 if getattr(m, "originatingInterface",
                            descriptor.interface) != descriptor.interface:
                     continue
                 nativeType = self.getNativeTypeForIDLType(m.type).define()
                 members.append(ClassMember(CGDictionary.makeMemberName(m.identifier.name),
                                nativeType,
                                visibility="private",
                                body="body"))
@@ -16609,20 +16651,21 @@ class CGEventClass(CGBindingImplClass):
         className = descriptor.nativeType.split('::')[-1]
         asConcreteTypeMethod = ClassMethod("As%s" % className,
                                            "%s*" % className,
                                            [],
                                            virtual=True,
                                            body="return this;\n",
                                            breakAfterReturnDecl=" ",
                                            override=True)
+        extraMethods.append(asConcreteTypeMethod)
 
         CGClass.__init__(self, className,
                          bases=[ClassBase(self.parentType)],
-                         methods=[asConcreteTypeMethod]+self.methodDecls,
+                         methods=extraMethods+self.methodDecls,
                          members=members,
                          extradeclarations=baseDeclarations)
 
     def getWrapObjectBody(self):
         return "return %sBinding::Wrap(aCx, this, aGivenProto);\n" % self.descriptor.name
 
     def implTraverse(self):
         retVal = ""
--- a/dom/bindings/DOMJSClass.h
+++ b/dom/bindings/DOMJSClass.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/. */
 
 #ifndef mozilla_dom_DOMJSClass_h
 #define mozilla_dom_DOMJSClass_h
 
 #include "jsfriendapi.h"
 #include "mozilla/Assertions.h"
+#include "mozilla/Likely.h"
 
 #include "mozilla/dom/PrototypeList.h" // auto-generated
 
 #include "mozilla/dom/JSSlots.h"
 
 class nsCycleCollectionParticipant;
 
 // All DOM globals must have a slot at DOM_PROTOTYPE_SLOT.
@@ -72,18 +73,17 @@ namespace GlobalNames {
 static const uint32_t Window = 1u << 0;
 static const uint32_t BackstagePass = 1u << 1;
 static const uint32_t DedicatedWorkerGlobalScope = 1u << 2;
 static const uint32_t SharedWorkerGlobalScope = 1u << 3;
 static const uint32_t ServiceWorkerGlobalScope = 1u << 4;
 static const uint32_t WorkerDebuggerGlobalScope = 1u << 5;
 } // namespace GlobalNames
 
-template<typename T>
-struct Prefable {
+struct PrefableDisablers {
   inline bool isEnabled(JSContext* cx, JS::Handle<JSObject*> obj) const {
     // Reading "enabled" on a worker thread is technically undefined behavior,
     // because it's written only on main threads, with no barriers of any sort.
     // So we want to avoid doing that.  But we don't particularly want to make
     // expensive NS_IsMainThread calls here.
     //
     // The good news is that "enabled" is only written for things that have a
     // Pref annotation, and such things can never be exposed on non-Window
@@ -92,19 +92,16 @@ struct Prefable {
     if (nonExposedGlobals &&
         IsNonExposedGlobal(cx, js::GetGlobalForObjectCrossCompartment(obj),
                            nonExposedGlobals)) {
       return false;
     }
     if (!enabled) {
       return false;
     }
-    if (!enabledFunc && !availableFunc && !checkAnyPermissions && !checkAllPermissions) {
-      return true;
-    }
     if (enabledFunc &&
         !enabledFunc(cx, js::GetGlobalForObjectCrossCompartment(obj))) {
       return false;
     }
     if (availableFunc &&
         !availableFunc(cx, js::GetGlobalForObjectCrossCompartment(obj))) {
       return false;
     }
@@ -116,35 +113,54 @@ struct Prefable {
     if (checkAllPermissions &&
         !CheckAllPermissions(cx, js::GetGlobalForObjectCrossCompartment(obj),
                              checkAllPermissions)) {
       return false;
     }
     return true;
   }
 
-  // A boolean indicating whether this set of specs is enabled
+  // A boolean indicating whether this set of specs is enabled. Not const
+  // because it will change at runtime if the corresponding pref is changed.
   bool enabled;
+
   // Bitmask of global names that we should not be exposed in.
-  uint32_t nonExposedGlobals;
+  const uint16_t nonExposedGlobals;
+
   // A function pointer to a function that can say the property is disabled
   // even if "enabled" is set to true.  If the pointer is null the value of
   // "enabled" is used as-is unless availableFunc overrides.
-  PropertyEnabled enabledFunc;
+  const PropertyEnabled enabledFunc;
+
   // A function pointer to a function that can be used to disable a
   // property even if "enabled" is true and enabledFunc allowed.  This
   // is basically a hack to avoid having to codegen PropertyEnabled
   // implementations in case when we need to do two separate checks.
-  PropertyEnabled availableFunc;
-  const char* const* checkAnyPermissions;
-  const char* const* checkAllPermissions;
+  const PropertyEnabled availableFunc;
+  const char* const* const checkAnyPermissions;
+  const char* const* const checkAllPermissions;
+};
+
+template<typename T>
+struct Prefable {
+  inline bool isEnabled(JSContext* cx, JS::Handle<JSObject*> obj) const {
+    if (MOZ_LIKELY(!disablers)) {
+      return true;
+    }
+    return disablers->isEnabled(cx, obj);
+  }
+
+  // Things that can disable this set of specs. |nullptr| means "cannot be
+  // disabled".
+  PrefableDisablers* const disablers;
+
   // Array of specs, terminated in whatever way is customary for T.
   // Null to indicate a end-of-array for Prefable, when such an
   // indicator is needed.
-  const T* specs;
+  const T* const specs;
 };
 
 struct NativeProperties
 {
   const Prefable<const JSFunctionSpec>* staticMethods;
   jsid* staticMethodIds;
   const JSFunctionSpec* staticMethodSpecs;
 
@@ -207,17 +223,17 @@ struct NativePropertyHooks
   // constructors::id::_ID_Count.
   constructors::ID mConstructorID;
 
   // The NativePropertyHooks instance for the parent interface (for
   // ShimInterfaceInfo).
   const NativePropertyHooks* mProtoHooks;
 };
 
-enum DOMObjectType {
+enum DOMObjectType : uint8_t {
   eInstance,
   eGlobalInstance,
   eInterface,
   eInterfacePrototype,
   eGlobalInterfacePrototype,
   eNamedPropertiesObject
 };
 
@@ -296,25 +312,25 @@ struct DOMIfaceAndProtoJSClass
   // |DOMJSInterfaceAndPrototypeClass = {...};|, since C++ only allows brace
   // initialization for aggregate/POD types.
   const js::Class mBase;
 
   // Either eInterface, eInterfacePrototype, eGlobalInterfacePrototype or
   // eNamedPropertiesObject.
   DOMObjectType mType;
 
+  const prototypes::ID mPrototypeID;
+  const uint32_t mDepth;
+
   const NativePropertyHooks* mNativeHooks;
 
   // The value to return for toString() on this interface or interface prototype
   // object.
   const char* mToString;
 
-  const prototypes::ID mPrototypeID;
-  const uint32_t mDepth;
-
   ProtoGetter mGetParentProto;
 
   static const DOMIfaceAndProtoJSClass* FromJSClass(const JSClass* base) {
     MOZ_ASSERT(base->flags & JSCLASS_IS_DOMIFACEANDPROTOJSCLASS);
     return reinterpret_cast<const DOMIfaceAndProtoJSClass*>(base);
   }
   static const DOMIfaceAndProtoJSClass* FromJSClass(const js::Class* base) {
     return FromJSClass(Jsvalify(base));
--- a/dom/bindings/Exceptions.cpp
+++ b/dom/bindings/Exceptions.cpp
@@ -137,17 +137,16 @@ void
 ThrowAndReport(nsPIDOMWindowInner* aWindow, nsresult aRv)
 {
   MOZ_ASSERT(aRv != NS_ERROR_UNCATCHABLE_EXCEPTION,
              "Doesn't make sense to report uncatchable exceptions!");
   AutoJSAPI jsapi;
   if (NS_WARN_IF(!jsapi.Init(aWindow))) {
     return;
   }
-  jsapi.TakeOwnershipOfErrorReporting();
 
   Throw(jsapi.cx(), aRv);
 }
 
 already_AddRefed<Exception>
 CreateException(JSContext* aCx, nsresult aRv, const nsACString& aMessage)
 {
   // Do we use DOM exceptions for this error code?
--- a/dom/canvas/WebGLContextUtils.cpp
+++ b/dom/canvas/WebGLContextUtils.cpp
@@ -84,18 +84,16 @@ WebGLContext::GenerateWarning(const char
         return;
     }
 
     dom::AutoJSAPI api;
     if (!api.Init(mCanvasElement->OwnerDoc()->GetScopeObject())) {
         return;
     }
 
-    api.TakeOwnershipOfErrorReporting();
-
     JSContext* cx = api.cx();
     JS_ReportWarning(cx, "WebGL: %s", buf);
     if (!ShouldGenerateWarnings()) {
         JS_ReportWarning(cx,
                          "WebGL: No further warnings will be reported for this"
                          " WebGL context. (already reported %d warnings)",
                          mAlreadyGeneratedWarnings);
     }
--- a/dom/canvas/moz.build
+++ b/dom/canvas/moz.build
@@ -178,8 +178,13 @@ LOCAL_INCLUDES += [
     '/layout/xul',
     '/media/libyuv/include',
 ]
 
 CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
 CXXFLAGS += CONFIG['TK_CFLAGS']
 
 LOCAL_INCLUDES += CONFIG['SKIA_INCLUDES']
+
+if CONFIG['_MSC_VER']:
+    # This is intended as a temporary workaround to unblock compilation
+    # on VS2015 in warnings as errors mode.
+    CXXFLAGS += ['-wd4312']
--- a/dom/canvas/test/reftest/reftest.list
+++ b/dom/canvas/test/reftest/reftest.list
@@ -148,14 +148,14 @@ skip-if(!winWidget) pref(webgl.disable-a
 # Do we correctly handle multiple clip paths?
 != clip-multiple-paths.html clip-multiple-paths-badref.html
 
 # Bug 815648
 == stroketext-shadow.html stroketext-shadow-ref.html
 
 # focus rings
 pref(canvas.focusring.enabled,true) skip-if(B2G) skip-if(cocoaWidget) skip-if(winWidget) needs-focus == drawFocusIfNeeded.html drawFocusIfNeeded-ref.html
-pref(canvas.customfocusring.enabled,true) skip-if(B2G) skip-if(cocoaWidget) skip-if(Android) skip-if(winWidget) needs-focus == drawCustomFocusRing.html drawCustomFocusRing-ref.html
+pref(canvas.customfocusring.enabled,true) skip-if(B2G) skip-if(cocoaWidget) skip-if(Android) skip-if(winWidget) fuzzy-if(gtkWidget,64,410) needs-focus == drawCustomFocusRing.html drawCustomFocusRing-ref.html
 
 # Check that captureStream() displays in a local video element
 == capturestream.html wrapper.html?green.png
 
 fuzzy-if(azureSkiaGL,1,2) fuzzy-if(Android,3,40) fuzzy-if(/^Windows\x20NT\x2010\.0/.test(http.oscpu),1,1) == 1177726-text-stroke-bounds.html 1177726-text-stroke-bounds-ref.html
--- a/dom/datastore/DataStoreDB.cpp
+++ b/dom/datastore/DataStoreDB.cpp
@@ -142,18 +142,23 @@ DataStoreDB::Open(IDBTransactionMode aMo
 {
   MOZ_ASSERT(mState == Inactive);
 
   nsresult rv = CreateFactoryIfNeeded();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
 
+  // We only need a JSContext here to get a stack from, so just init our
+  // AutoJSAPI without a global.
+  AutoJSAPI jsapi;
+  jsapi.Init();
   ErrorResult error;
-  mRequest = mFactory->Open(mDatabaseName, DATASTOREDB_VERSION, error);
+  mRequest = mFactory->Open(jsapi.cx(), mDatabaseName, DATASTOREDB_VERSION,
+                            error);
   if (NS_WARN_IF(error.Failed())) {
     return error.StealNSResult();
   }
 
   rv = AddEventListeners();
   if (NS_WARN_IF(NS_FAILED(rv))) {
     return rv;
   }
@@ -308,18 +313,24 @@ DataStoreDB::DatabaseOpened()
   }
 
   StringOrStringSequence objectStores;
   if (!objectStores.RawSetAsStringSequence().AppendElements(mObjectStores,
                                                             fallible)) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
 
+  // We init with the global of our result, just for consistency.
+  AutoJSAPI jsapi;
+  if (!jsapi.Init(&result.toObject())) {
+    return NS_ERROR_UNEXPECTED;
+  }
   RefPtr<IDBTransaction> txn;
-  error = mDatabase->Transaction(objectStores,
+  error = mDatabase->Transaction(jsapi.cx(),
+                                 objectStores,
                                  mTransactionMode,
                                  getter_AddRefs(txn));
   if (NS_WARN_IF(error.Failed())) {
     return error.StealNSResult();
   }
 
   mTransaction = txn.forget();
   return NS_OK;
@@ -337,19 +348,24 @@ DataStoreDB::Delete()
 
   mTransaction = nullptr;
 
   if (mDatabase) {
     mDatabase->Close();
     mDatabase = nullptr;
   }
 
+  // We only need a JSContext here to get a stack from, so just init our
+  // AutoJSAPI without a global.
+  AutoJSAPI jsapi;
+  jsapi.Init();
   ErrorResult error;
   RefPtr<IDBOpenDBRequest> request =
-    mFactory->DeleteDatabase(mDatabaseName, IDBOpenDBOptions(), error);
+    mFactory->DeleteDatabase(jsapi.cx(), mDatabaseName, IDBOpenDBOptions(),
+                             error);
   if (NS_WARN_IF(error.Failed())) {
     return error.StealNSResult();
   }
 
   return NS_OK;
 }
 
 IDBTransaction*
--- a/dom/events/EventListenerManager.cpp
+++ b/dom/events/EventListenerManager.cpp
@@ -912,17 +912,16 @@ EventListenerManager::CompileEventHandle
   NS_ENSURE_STATE(global);
 
   // Activate JSAPI, and make sure that exceptions are reported on the right
   // Window.
   AutoJSAPI jsapi;
   if (NS_WARN_IF(!jsapi.Init(global))) {
     return NS_ERROR_UNEXPECTED;
   }
-  jsapi.TakeOwnershipOfErrorReporting();
   JSContext* cx = jsapi.cx();
 
   nsCOMPtr<nsIAtom> typeAtom = aListener->mTypeAtom;
   nsIAtom* attrName = typeAtom;
 
   // Flag us as not a string so we don't keep trying to compile strings which
   // can't be compiled.
   aListener->mHandlerIsString = false;
--- a/dom/events/JSEventHandler.cpp
+++ b/dom/events/JSEventHandler.cpp
@@ -149,19 +149,18 @@ JSEventHandler::HandleEvent(nsIDOMEvent*
       fileName = &file;
 
       lineNumber.Construct();
       lineNumber.Value() = scriptEvent->Lineno();
 
       columnNumber.Construct();
       columnNumber.Value() = scriptEvent->Colno();
 
-      ThreadsafeAutoJSContext cx;
-      error.Construct(cx);
-      scriptEvent->GetError(cx, &error.Value());
+      error.Construct(nsContentUtils::RootingCxForThread());
+      scriptEvent->GetError(&error.Value());
     } else {
       msgOrEvent.SetAsEvent() = aEvent->InternalDOMEvent();
     }
 
     RefPtr<OnErrorEventHandlerNonNull> handler =
       mTypedHandler.OnErrorEventHandler();
     ErrorResult rv;
     bool handled = handler->Call(mTarget, msgOrEvent, fileName, lineNumber,
--- a/dom/geolocation/nsGeolocation.cpp
+++ b/dom/geolocation/nsGeolocation.cpp
@@ -770,18 +770,19 @@ nsGeolocationRequest::Shutdown()
 // nsGeolocationRequest::TimerCallbackHolder
 ////////////////////////////////////////////////////
 
 NS_IMPL_ISUPPORTS(nsGeolocationRequest::TimerCallbackHolder, nsISupports, nsITimerCallback)
 
 NS_IMETHODIMP
 nsGeolocationRequest::TimerCallbackHolder::Notify(nsITimer*)
 {
-  if (mRequest.get()) {
-    mRequest->Notify();
+  if (mRequest) {
+    RefPtr<nsGeolocationRequest> request(mRequest);
+    request->Notify();
   }
   return NS_OK;
 }
 
 
 ////////////////////////////////////////////////////
 // nsGeolocationService
 ////////////////////////////////////////////////////
--- a/dom/indexedDB/IDBDatabase.cpp
+++ b/dom/indexedDB/IDBDatabase.cpp
@@ -596,62 +596,48 @@ IDBDatabase::DeleteObjectStore(const nsA
                transaction->LoggingSerialNumber(),
                requestSerialNumber,
                IDB_LOG_STRINGIFY(this),
                IDB_LOG_STRINGIFY(transaction),
                NS_ConvertUTF16toUTF8(aName).get());
 }
 
 already_AddRefed<IDBTransaction>
-IDBDatabase::Transaction(const StringOrStringSequence& aStoreNames,
+IDBDatabase::Transaction(JSContext* aCx,
+                         const StringOrStringSequence& aStoreNames,
                          IDBTransactionMode aMode,
                          ErrorResult& aRv)
 {
   AssertIsOnOwningThread();
 
-  aRv.MightThrowJSException();
-
   if ((aMode == IDBTransactionMode::Readwriteflush ||
        aMode == IDBTransactionMode::Cleanup) &&
       !IndexedDatabaseManager::ExperimentalFeaturesEnabled()) {
     // Pretend that this mode doesn't exist. We don't have a way to annotate
     // certain enum values as depending on preferences so we just duplicate the
     // normal exception generation here.
-    ThreadsafeAutoJSContext cx;
-
-    // Disable any automatic error reporting that might be set up so that we
-    // can grab the exception object.
-    AutoForceSetExceptionOnContext forceExn(cx);
-
-    MOZ_ALWAYS_FALSE(
-      ThrowErrorMessage(cx,
-                        MSG_INVALID_ENUM_VALUE,
-                        "Argument 2 of IDBDatabase.transaction",
-                        "readwriteflush",
-                        "IDBTransactionMode"));
-    MOZ_ASSERT(JS_IsExceptionPending(cx));
-
-    JS::Rooted<JS::Value> exception(cx);
-    MOZ_ALWAYS_TRUE(JS_GetPendingException(cx, &exception));
-
-    aRv.ThrowJSException(cx, exception);
+    aRv.ThrowTypeError<MSG_INVALID_ENUM_VALUE>(
+      NS_LITERAL_STRING("Argument 2 of IDBDatabase.transaction"),
+      NS_LITERAL_STRING("readwriteflush"),
+      NS_LITERAL_STRING("IDBTransactionMode"));
     return nullptr;
   }
 
   RefPtr<IDBTransaction> transaction;
-  aRv = Transaction(aStoreNames, aMode, getter_AddRefs(transaction));
+  aRv = Transaction(aCx, aStoreNames, aMode, getter_AddRefs(transaction));
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
   return transaction.forget();
 }
 
 nsresult
-IDBDatabase::Transaction(const StringOrStringSequence& aStoreNames,
+IDBDatabase::Transaction(JSContext* aCx,
+                         const StringOrStringSequence& aStoreNames,
                          IDBTransactionMode aMode,
                          IDBTransaction** aTransaction)
 {
   AssertIsOnOwningThread();
 
   if (NS_WARN_IF((aMode == IDBTransactionMode::Readwriteflush ||
                   aMode == IDBTransactionMode::Cleanup) &&
                  !IndexedDatabaseManager::ExperimentalFeaturesEnabled())) {
@@ -737,17 +723,17 @@ IDBDatabase::Transaction(const StringOrS
     case IDBTransactionMode::Versionchange:
       return NS_ERROR_DOM_INVALID_ACCESS_ERR;
 
     default:
       MOZ_CRASH("Unknown mode!");
   }
 
   RefPtr<IDBTransaction> transaction =
-    IDBTransaction::Create(this, sortedStoreNames, mode);
+    IDBTransaction::Create(aCx, this, sortedStoreNames, mode);
   if (NS_WARN_IF(!transaction)) {
     IDB_REPORT_INTERNAL_ERR();
     return NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR;
   }
 
   BackgroundTransactionChild* actor =
     new BackgroundTransactionChild(transaction);
 
@@ -779,17 +765,18 @@ IDBDatabase::Storage() const
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mSpec);
 
   return PersistenceTypeToStorage(mSpec->metadata().persistenceType());
 }
 
 already_AddRefed<IDBRequest>
-IDBDatabase::CreateMutableFile(const nsAString& aName,
+IDBDatabase::CreateMutableFile(JSContext* aCx,
+                               const nsAString& aName,
                                const Optional<nsAString>& aType,
                                ErrorResult& aRv)
 {
   AssertIsOnOwningThread();
 
   if (QuotaManager::IsShuttingDown()) {
     IDB_REPORT_INTERNAL_ERR();
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
@@ -803,17 +790,17 @@ IDBDatabase::CreateMutableFile(const nsA
 
   nsString type;
   if (aType.WasPassed()) {
     type = aType.Value();
   }
 
   CreateFileParams params(nsString(aName), type);
 
-  RefPtr<IDBRequest> request = IDBRequest::Create(this, nullptr);
+  RefPtr<IDBRequest> request = IDBRequest::Create(aCx, this, nullptr);
   MOZ_ASSERT(request);
 
   BackgroundDatabaseRequestChild* actor =
     new BackgroundDatabaseRequestChild(this, request);
 
   IDB_LOG_MARK("IndexedDB %s: Child  Request[%llu]: "
                  "database(%s).createMutableFile(%s)",
                "IndexedDB %s: C R[%llu]: IDBDatabase.createMutableFile()",
--- a/dom/indexedDB/IDBDatabase.h
+++ b/dom/indexedDB/IDBDatabase.h
@@ -220,44 +220,48 @@ public:
                     const IDBObjectStoreParameters& aOptionalParameters,
                     ErrorResult& aRv);
 
   void
   DeleteObjectStore(const nsAString& name, ErrorResult& aRv);
 
   // This will be called from the DOM.
   already_AddRefed<IDBTransaction>
-  Transaction(const StringOrStringSequence& aStoreNames,
+  Transaction(JSContext* aCx,
+              const StringOrStringSequence& aStoreNames,
               IDBTransactionMode aMode,
               ErrorResult& aRv);
 
   // This can be called from C++ to avoid JS exception.
   nsresult
-  Transaction(const StringOrStringSequence& aStoreNames,
+  Transaction(JSContext* aCx,
+              const StringOrStringSequence& aStoreNames,
               IDBTransactionMode aMode,
               IDBTransaction** aTransaction);
 
   StorageType
   Storage() const;
 
   IMPL_EVENT_HANDLER(abort)
   IMPL_EVENT_HANDLER(error)
   IMPL_EVENT_HANDLER(versionchange)
 
   already_AddRefed<IDBRequest>
-  CreateMutableFile(const nsAString& aName,
+  CreateMutableFile(JSContext* aCx,
+                    const nsAString& aName,
                     const Optional<nsAString>& aType,
                     ErrorResult& aRv);
 
   already_AddRefed<IDBRequest>
-  MozCreateFileHandle(const nsAString& aName,
+  MozCreateFileHandle(JSContext* aCx,
+                      const nsAString& aName,
                       const Optional<nsAString>& aType,
                       ErrorResult& aRv)
   {
-    return CreateMutableFile(aName, aType, aRv);
+    return CreateMutableFile(aCx, aName, aType, aRv);
   }
 
   void
   ClearBackgroundActor()
   {
     AssertIsOnOwningThread();
 
     mBackgroundActor = nullptr;
--- a/dom/indexedDB/IDBFactory.cpp
+++ b/dom/indexedDB/IDBFactory.cpp
@@ -473,47 +473,53 @@ IDBFactory::IncrementParentLoggingReques
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(mBackgroundActor);
 
   mBackgroundActor->SendIncrementLoggingRequestSerialNumber();
 }
 
 already_AddRefed<IDBOpenDBRequest>
-IDBFactory::Open(const nsAString& aName,
+IDBFactory::Open(JSContext* aCx,
+                 const nsAString& aName,
                  uint64_t aVersion,
                  ErrorResult& aRv)
 {
-  return OpenInternal(/* aPrincipal */ nullptr,
+  return OpenInternal(aCx,
+                      /* aPrincipal */ nullptr,
                       aName,
                       Optional<uint64_t>(aVersion),
                       Optional<StorageType>(),
                       /* aDeleting */ false,
                       aRv);
 }
 
 already_AddRefed<IDBOpenDBRequest>
-IDBFactory::Open(const nsAString& aName,
+IDBFactory::Open(JSContext* aCx,
+                 const nsAString& aName,
                  const IDBOpenDBOptions& aOptions,
                  ErrorResult& aRv)
 {
-  return OpenInternal(/* aPrincipal */ nullptr,
+  return OpenInternal(aCx,
+                      /* aPrincipal */ nullptr,
                       aName,
                       aOptions.mVersion,
                       aOptions.mStorage,
                       /* aDeleting */ false,
                       aRv);
 }
 
 already_AddRefed<IDBOpenDBRequest>
-IDBFactory::DeleteDatabase(const nsAString& aName,
+IDBFactory::DeleteDatabase(JSContext* aCx,
+                           const nsAString& aName,
                            const IDBOpenDBOptions& aOptions,
                            ErrorResult& aRv)
 {
-  return OpenInternal(/* aPrincipal */ nullptr,
+  return OpenInternal(aCx,
+                      /* aPrincipal */ nullptr,
                       aName,
                       Optional<uint64_t>(),
                       aOptions.mStorage,
                       /* aDeleting */ true,
                       aRv);
 }
 
 int16_t
@@ -537,77 +543,84 @@ IDBFactory::Cmp(JSContext* aCx, JS::Hand
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
     return 0;
   }
 
   return Key::CompareKeys(first, second);
 }
 
 already_AddRefed<IDBOpenDBRequest>
-IDBFactory::OpenForPrincipal(nsIPrincipal* aPrincipal,
+IDBFactory::OpenForPrincipal(JSContext* aCx,
+                             nsIPrincipal* aPrincipal,
                              const nsAString& aName,
                              uint64_t aVersion,
                              ErrorResult& aRv)
 {
   MOZ_ASSERT(aPrincipal);
   if (!NS_IsMainThread()) {
     MOZ_CRASH("Figure out security checks for workers!");
   }
   MOZ_ASSERT(nsContentUtils::IsCallerChrome());
 
-  return OpenInternal(aPrincipal,
+  return OpenInternal(aCx,
+                      aPrincipal,
                       aName,
                       Optional<uint64_t>(aVersion),
                       Optional<StorageType>(),
                       /* aDeleting */ false,
                       aRv);
 }
 
 already_AddRefed<IDBOpenDBRequest>
-IDBFactory::OpenForPrincipal(nsIPrincipal* aPrincipal,
+IDBFactory::OpenForPrincipal(JSContext* aCx,
+                             nsIPrincipal* aPrincipal,
                              const nsAString& aName,
                              const IDBOpenDBOptions& aOptions,
                              ErrorResult& aRv)
 {
   MOZ_ASSERT(aPrincipal);
   if (!NS_IsMainThread()) {
     MOZ_CRASH("Figure out security checks for workers!");
   }
   MOZ_ASSERT(nsContentUtils::IsCallerChrome());
 
-  return OpenInternal(aPrincipal,
+  return OpenInternal(aCx,
+                      aPrincipal,
                       aName,
                       aOptions.mVersion,
                       aOptions.mStorage,
                       /* aDeleting */ false,
                       aRv);
 }
 
 already_AddRefed<IDBOpenDBRequest>
-IDBFactory::DeleteForPrincipal(nsIPrincipal* aPrincipal,
+IDBFactory::DeleteForPrincipal(JSContext* aCx,
+                               nsIPrincipal* aPrincipal,
                                const nsAString& aName,
                                const IDBOpenDBOptions& aOptions,
                                ErrorResult& aRv)
 {
   MOZ_ASSERT(aPrincipal);
   if (!NS_IsMainThread()) {
     MOZ_CRASH("Figure out security checks for workers!");
   }
   MOZ_ASSERT(nsContentUtils::IsCallerChrome());
 
-  return OpenInternal(aPrincipal,
+  return OpenInternal(aCx,
+                      aPrincipal,
                       aName,
                       Optional<uint64_t>(),
                       aOptions.mStorage,
                       /* aDeleting */ true,
                       aRv);
 }
 
 already_AddRefed<IDBOpenDBRequest>
-IDBFactory::OpenInternal(nsIPrincipal* aPrincipal,
+IDBFactory::OpenInternal(JSContext* aCx,
+                         nsIPrincipal* aPrincipal,
                          const nsAString& aName,
                          const Optional<uint64_t>& aVersion,
                          const Optional<StorageType>& aStorageType,
                          bool aDeleting,
                          ErrorResult& aRv)
 {
   MOZ_ASSERT(mWindow || mOwningObject);
   MOZ_ASSERT_IF(!mWindow, !mPrivateBrowsingMode);
@@ -722,26 +735,25 @@ IDBFactory::OpenInternal(nsIPrincipal* a
 
       threadLocal->mIndexedDBThreadLocal = newIDBThreadLocal.forget();
     }
   }
 
   RefPtr<IDBOpenDBRequest> request;
 
   if (mWindow) {
-    JS::Rooted<JSObject*> scriptOwner(nsContentUtils::RootingCxForThread(),
+    JS::Rooted<JSObject*> scriptOwner(aCx,
                                       nsGlobalWindow::Cast(mWindow.get())->FastGetGlobalJSObject());
     MOZ_ASSERT(scriptOwner);
 
-    request = IDBOpenDBRequest::CreateForWindow(this, mWindow, scriptOwner);
+    request = IDBOpenDBRequest::CreateForWindow(aCx, this, mWindow, scriptOwner);
   } else {
-    JS::Rooted<JSObject*> scriptOwner(nsContentUtils::RootingCxForThread(),
-                                      mOwningObject);
+    JS::Rooted<JSObject*> scriptOwner(aCx, mOwningObject);
 
-    request = IDBOpenDBRequest::CreateForJS(this, scriptOwner);
+    request = IDBOpenDBRequest::CreateForJS(aCx, this, scriptOwner);
     if (!request) {
       MOZ_ASSERT(!NS_IsMainThread());
       aRv.ThrowUncatchableException();
       return nullptr;
     }
   }
 
   MOZ_ASSERT(request);
--- a/dom/indexedDB/IDBFactory.h
+++ b/dom/indexedDB/IDBFactory.h
@@ -159,50 +159,56 @@ public:
 
     return mInnerWindowID;
   }
 
   bool
   IsChrome() const;
 
   already_AddRefed<IDBOpenDBRequest>
-  Open(const nsAString& aName,
+  Open(JSContext* aCx,
+       const nsAString& aName,
        uint64_t aVersion,
        ErrorResult& aRv);
 
   already_AddRefed<IDBOpenDBRequest>
-  Open(const nsAString& aName,
+  Open(JSContext* aCx,
+       const nsAString& aName,
        const IDBOpenDBOptions& aOptions,
        ErrorResult& aRv);
 
   already_AddRefed<IDBOpenDBRequest>
-  DeleteDatabase(const nsAString& aName,
+  DeleteDatabase(JSContext* aCx,
+                 const nsAString& aName,
                  const IDBOpenDBOptions& aOptions,
                  ErrorResult& aRv);
 
   int16_t
   Cmp(JSContext* aCx,
       JS::Handle<JS::Value> aFirst,
       JS::Handle<JS::Value> aSecond,
       ErrorResult& aRv);
 
   already_AddRefed<IDBOpenDBRequest>
-  OpenForPrincipal(nsIPrincipal* aPrincipal,
+  OpenForPrincipal(JSContext* aCx,
+                   nsIPrincipal* aPrincipal,
                    const nsAString& aName,
                    uint64_t aVersion,
                    ErrorResult& aRv);
 
   already_AddRefed<IDBOpenDBRequest>
-  OpenForPrincipal(nsIPrincipal* aPrincipal,
+  OpenForPrincipal(JSContext* aCx,
+                   nsIPrincipal* aPrincipal,
                    const nsAString& aName,
                    const IDBOpenDBOptions& aOptions,
                    ErrorResult& aRv);
 
   already_AddRefed<IDBOpenDBRequest>
-  DeleteForPrincipal(nsIPrincipal* aPrincipal,
+  DeleteForPrincipal(JSContext* aCx,
+                     nsIPrincipal* aPrincipal,
                      const nsAString& aName,
                      const IDBOpenDBOptions& aOptions,
                      ErrorResult& aRv);
 
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(IDBFactory)
 
   // nsWrapperCache
@@ -226,17 +232,18 @@ private:
                       uint64_t aInnerWindowID,
                       IDBFactory** aFactory);
 
   static nsresult
   AllowedForWindowInternal(nsPIDOMWindowInner* aWindow,
                            nsIPrincipal** aPrincipal);
 
   already_AddRefed<IDBOpenDBRequest>
-  OpenInternal(nsIPrincipal* aPrincipal,
+  OpenInternal(JSContext* aCx,
+               nsIPrincipal* aPrincipal,
                const nsAString& aName,
                const Optional<uint64_t>& aVersion,
                const Optional<StorageType>& aStorageType,
                bool aDeleting,
                ErrorResult& aRv);
 
   nsresult
   BackgroundActorCreated(PBackgroundChild* aBackgroundActor,
--- a/dom/indexedDB/IDBIndex.cpp
+++ b/dom/indexedDB/IDBIndex.cpp
@@ -24,25 +24,25 @@
 #include "ActorsChild.h"
 
 namespace mozilla {
 namespace dom {
 
 namespace {
 
 already_AddRefed<IDBRequest>
-GenerateRequest(IDBIndex* aIndex)
+GenerateRequest(JSContext* aCx, IDBIndex* aIndex)
 {
   MOZ_ASSERT(aIndex);
   aIndex->AssertIsOnOwningThread();
 
   IDBTransaction* transaction = aIndex->ObjectStore()->Transaction();
 
   RefPtr<IDBRequest> request =
-    IDBRequest::Create(aIndex, transaction->Database(), transaction);
+    IDBRequest::Create(aCx, aIndex, transaction->Database(), transaction);
   MOZ_ASSERT(request);
 
   return request.forget();
 }
 
 } // namespace
 
 IDBIndex::IDBIndex(IDBObjectStore* aObjectStore, const IndexMetadata* aMetadata)
@@ -294,17 +294,17 @@ IDBIndex::GetInternal(bool aKeyOnly,
   RequestParams params;
 
   if (aKeyOnly) {
     params = IndexGetKeyParams(objectStoreId, indexId, serializedKeyRange);
   } else {
     params = IndexGetParams(objectStoreId, indexId, serializedKeyRange);
   }
 
-  RefPtr<IDBRequest> request = GenerateRequest(this);
+  RefPtr<IDBRequest> request = GenerateRequest(aCx, this);
   MOZ_ASSERT(request);
 
   if (aKeyOnly) {
     IDB_LOG_MARK("IndexedDB %s: Child  Transaction[%lld] Request[%llu]: "
                    "database(%s).transaction(%s).objectStore(%s).index(%s)."
                    "getKey(%s)",
                  "IndexedDB %s: C T[%lld] R[%llu]: IDBIndex.getKey()",
                  IDB_LOG_ID_STRING(),
@@ -378,17 +378,17 @@ IDBIndex::GetAllInternal(bool aKeysOnly,
   RequestParams params;
   if (aKeysOnly) {
     params = IndexGetAllKeysParams(objectStoreId, indexId, optionalKeyRange,
                                    limit);
   } else {
     params = IndexGetAllParams(objectStoreId, indexId, optionalKeyRange, limit);
   }
 
-  RefPtr<IDBRequest> request = GenerateRequest(this);
+  RefPtr<IDBRequest> request = GenerateRequest(aCx, this);
   MOZ_ASSERT(request);
 
   if (aKeysOnly) {
     IDB_LOG_MARK("IndexedDB %s: Child  Transaction[%lld] Request[%llu]: "
                    "database(%s).transaction(%s).objectStore(%s).index(%s)."
                    "getAllKeys(%s, %s)",
                  "IndexedDB %s: C T[%lld] R[%llu]: IDBIndex.getAllKeys()",
                  IDB_LOG_ID_STRING(),
@@ -477,17 +477,17 @@ IDBIndex::OpenCursorInternal(bool aKeysO
     openParams.objectStoreId() = objectStoreId;
     openParams.indexId() = indexId;
     openParams.optionalKeyRange() = Move(optionalKeyRange);
     openParams.direction() = direction;
 
     params = Move(openParams);
   }
 
-  RefPtr<IDBRequest> request = GenerateRequest(this);
+  RefPtr<IDBRequest> request = GenerateRequest(aCx, this);
   MOZ_ASSERT(request);
 
   if (aKeysOnly) {
     IDB_LOG_MARK("IndexedDB %s: Child  Transaction[%lld] Request[%llu]: "
                    "database(%s).transaction(%s).objectStore(%s).index(%s)."
                    "openKeyCursor(%s, %s)",
                  "IndexedDB %s: C T[%lld] R[%llu]: IDBIndex.openKeyCursor()",
                  IDB_LOG_ID_STRING(),
@@ -555,17 +555,17 @@ IDBIndex::Count(JSContext* aCx,
   if (keyRange) {
     SerializedKeyRange serializedKeyRange;
     keyRange->ToSerialized(serializedKeyRange);
     params.optionalKeyRange() = serializedKeyRange;
   } else {
     params.optionalKeyRange() = void_t();
   }
 
-  RefPtr<IDBRequest> request = GenerateRequest(this);
+  RefPtr<IDBRequest> request = GenerateRequest(aCx, this);
   MOZ_ASSERT(request);
 
   IDB_LOG_MARK("IndexedDB %s: Child  Transaction[%lld] Request[%llu]: "
                  "database(%s).transaction(%s).objectStore(%s).index(%s)."
                  "count(%s)",
                "IndexedDB %s: C T[%lld] R[%llu]: IDBObjectStore.count()",
                IDB_LOG_ID_STRING(),
                transaction->LoggingSerialNumber(),
--- a/dom/indexedDB/IDBObjectStore.cpp
+++ b/dom/indexedDB/IDBObjectStore.cpp
@@ -192,25 +192,25 @@ struct MOZ_STACK_CLASS GetAddInfoClosure
 
   ~GetAddInfoClosure()
   {
     MOZ_COUNT_DTOR(GetAddInfoClosure);
   }
 };
 
 already_AddRefed<IDBRequest>
-GenerateRequest(IDBObjectStore* aObjectStore)
+GenerateRequest(JSContext* aCx, IDBObjectStore* aObjectStore)
 {
   MOZ_ASSERT(aObjectStore);
   aObjectStore->AssertIsOnOwningThread();
 
   IDBTransaction* transaction = aObjectStore->Transaction();
 
   RefPtr<IDBRequest> request =
-    IDBRequest::Create(aObjectStore, transaction->Database(), transaction);
+    IDBRequest::Create(aCx, aObjectStore, transaction->Database(), transaction);
   MOZ_ASSERT(request);
 
   return request.forget();
 }
 
 bool
 StructuredCloneWriteCallback(JSContext* aCx,
                              JSStructuredCloneWriter* aWriter,
@@ -1392,17 +1392,17 @@ IDBObjectStore::AddOrPut(JSContext* aCx,
 
   RequestParams params;
   if (aOverwrite) {
     params = ObjectStorePutParams(commonParams);
   } else {
     params = ObjectStoreAddParams(commonParams);
   }
 
-  RefPtr<IDBRequest> request = GenerateRequest(this);
+  RefPtr<IDBRequest> request = GenerateRequest(aCx, this);
   MOZ_ASSERT(request);
 
   if (!aFromCursor) {
     if (aOverwrite) {
       IDB_LOG_MARK("IndexedDB %s: Child  Transaction[%lld] Request[%llu]: "
                      "database(%s).transaction(%s).objectStore(%s).put(%s)",
                    "IndexedDB %s: C T[%lld] R[%llu]: IDBObjectStore.put()",
                    IDB_LOG_ID_STRING(),
@@ -1471,17 +1471,17 @@ IDBObjectStore::GetAllInternal(bool aKey
 
   RequestParams params;
   if (aKeysOnly) {
     params = ObjectStoreGetAllKeysParams(id, optionalKeyRange, limit);
   } else {
     params = ObjectStoreGetAllParams(id, optionalKeyRange, limit);
   }
 
-  RefPtr<IDBRequest> request = GenerateRequest(this);
+  RefPtr<IDBRequest> request = GenerateRequest(aCx, this);
   MOZ_ASSERT(request);
 
   if (aKeysOnly) {
     IDB_LOG_MARK("IndexedDB %s: Child  Transaction[%lld] Request[%llu]: "
                    "database(%s).transaction(%s).objectStore(%s)."
                    "getAllKeys(%s, %s)",
                  "IndexedDB %s: C T[%lld] R[%llu]: IDBObjectStore.getAllKeys()",
                  IDB_LOG_ID_STRING(),
@@ -1508,17 +1508,17 @@ IDBObjectStore::GetAllInternal(bool aKey
   }
 
   mTransaction->StartRequest(request, params);
 
   return request.forget();
 }
 
 already_AddRefed<IDBRequest>
-IDBObjectStore::Clear(ErrorResult& aRv)
+IDBObjectStore::Clear(JSContext* aCx, ErrorResult& aRv)
 {
   AssertIsOnOwningThread();
 
   if (mDeletedSpec) {
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_NOT_ALLOWED_ERR);
     return nullptr;
   }
 
@@ -1530,17 +1530,17 @@ IDBObjectStore::Clear(ErrorResult& aRv)
   if (!mTransaction->IsWriteAllowed()) {
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_READ_ONLY_ERR);
     return nullptr;
   }
 
   ObjectStoreClearParams params;
   params.objectStoreId() = Id();
 
-  RefPtr<IDBRequest> request = GenerateRequest(this);
+  RefPtr<IDBRequest> request = GenerateRequest(aCx, this);
   MOZ_ASSERT(request);
 
   IDB_LOG_MARK("IndexedDB %s: Child  Transaction[%lld] Request[%llu]: "
                  "database(%s).transaction(%s).objectStore(%s).clear()",
                "IndexedDB %s: C T[%lld] R[%llu]: IDBObjectStore.clear()",
                IDB_LOG_ID_STRING(),
                mTransaction->LoggingSerialNumber(),
                request->LoggingSerialNumber(),
@@ -1729,17 +1729,17 @@ IDBObjectStore::Get(JSContext* aCx,
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
     return nullptr;
   }
 
   ObjectStoreGetParams params;
   params.objectStoreId() = Id();
   keyRange->ToSerialized(params.keyRange());
 
-  RefPtr<IDBRequest> request = GenerateRequest(this);
+  RefPtr<IDBRequest> request = GenerateRequest(aCx, this);
   MOZ_ASSERT(request);
 
   IDB_LOG_MARK("IndexedDB %s: Child  Transaction[%lld] Request[%llu]: "
                  "database(%s).transaction(%s).objectStore(%s).get(%s)",
                "IndexedDB %s: C T[%lld] R[%llu]: IDBObjectStore.get()",
                IDB_LOG_ID_STRING(),
                mTransaction->LoggingSerialNumber(),
                request->LoggingSerialNumber(),
@@ -1787,17 +1787,17 @@ IDBObjectStore::DeleteInternal(JSContext
     aRv.Throw(NS_ERROR_DOM_INDEXEDDB_DATA_ERR);
     return nullptr;
   }
 
   ObjectStoreDeleteParams params;
   params.objectStoreId() = Id();
   keyRange->ToSerialized(params.keyRange());
 
-  RefPtr<IDBRequest> request = GenerateRequest(this);
+  RefPtr<IDBRequest> request = GenerateRequest(aCx, this);
   MOZ_ASSERT(request);
 
   if (!aFromCursor) {
     IDB_LOG_MARK("IndexedDB %s: Child  Transaction[%lld] Request[%llu]: "
                    "database(%s).transaction(%s).objectStore(%s).delete(%s)",
                  "IndexedDB %s: C T[%lld] R[%llu]: IDBObjectStore.delete()",
                  IDB_LOG_ID_STRING(),
                  mTransaction->LoggingSerialNumber(),
@@ -2056,17 +2056,17 @@ IDBObjectStore::Count(JSContext* aCx,
   if (keyRange) {
     SerializedKeyRange serializedKeyRange;
     keyRange->ToSerialized(serializedKeyRange);
     params.optionalKeyRange() = serializedKeyRange;
   } else {
     params.optionalKeyRange() = void_t();
   }
 
-  RefPtr<IDBRequest> request = GenerateRequest(this);
+  RefPtr<IDBRequest> request = GenerateRequest(aCx, this);
   MOZ_ASSERT(request);
 
   IDB_LOG_MARK("IndexedDB %s: Child  Transaction[%lld] Request[%llu]: "
                  "database(%s).transaction(%s).objectStore(%s).count(%s)",
                "IndexedDB %s: C T[%lld] R[%llu]: IDBObjectStore.count()",
                IDB_LOG_ID_STRING(),
                mTransaction->LoggingSerialNumber(),
                request->LoggingSerialNumber(),
@@ -2133,17 +2133,17 @@ IDBObjectStore::OpenCursorInternal(bool 
     ObjectStoreOpenCursorParams openParams;
     openParams.objectStoreId() = objectStoreId;
     openParams.optionalKeyRange() = Move(optionalKeyRange);
     openParams.direction() = direction;
 
     params = Move(openParams);
   }
 
-  RefPtr<IDBRequest> request = GenerateRequest(this);
+  RefPtr<IDBRequest> request = GenerateRequest(aCx, this);
   MOZ_ASSERT(request);
 
   if (aKeysOnly) {
     IDB_LOG_MARK("IndexedDB %s: Child  Transaction[%lld] Request[%llu]: "
                    "database(%s).transaction(%s).objectStore(%s)."
                    "openKeyCursor(%s, %s)",
                  "IndexedDB %s: C T[%lld] R[%llu]: "
                    "IDBObjectStore.openKeyCursor()",
--- a/dom/indexedDB/IDBObjectStore.h
+++ b/dom/indexedDB/IDBObjectStore.h
@@ -197,17 +197,17 @@ public:
 
     return DeleteInternal(aCx, aKey, /* aFromCursor */ false, aRv);
   }
 
   already_AddRefed<IDBRequest>
   Get(JSContext* aCx, JS::Handle<JS::Value> aKey, ErrorResult& aRv);
 
   already_AddRefed<IDBRequest>
-  Clear(ErrorResult& aRv);
+  Clear(JSContext* aCx, ErrorResult& aRv);
 
   already_AddRefed<IDBIndex>
   CreateIndex(const nsAString& aName,
               const nsAString& aKeyPath,
               const IDBIndexParameters& aOptionalParameters,
               ErrorResult& aRv);
 
   already_AddRefed<IDBIndex>
--- a/dom/indexedDB/IDBRequest.cpp
+++ b/dom/indexedDB/IDBRequest.cpp
@@ -107,57 +107,60 @@ IDBRequest::InitMembers()
   mErrorCode = NS_OK;
   mLineNo = 0;
   mColumn = 0;
   mHaveResultOrErrorCode = false;
 }
 
 // static
 already_AddRefed<IDBRequest>
-IDBRequest::Create(IDBDatabase* aDatabase,
+IDBRequest::Create(JSContext* aCx,
+                   IDBDatabase* aDatabase,
                    IDBTransaction* aTransaction)
 {
   MOZ_ASSERT(aDatabase);
   aDatabase->AssertIsOnOwningThread();
 
   RefPtr<IDBRequest> request = new IDBRequest(aDatabase);
-  CaptureCaller(request->mFilename, &request->mLineNo, &request->mColumn);
+  CaptureCaller(aCx, request->mFilename, &request->mLineNo, &request->mColumn);
 
   request->mTransaction = aTransaction;
   request->SetScriptOwner(aDatabase->GetScriptOwner());
 
   return request.forget();
 }
 
 // static
 already_AddRefed<IDBRequest>
-IDBRequest::Create(IDBObjectStore* aSourceAsObjectStore,
+IDBRequest::Create(JSContext* aCx,
+                   IDBObjectStore* aSourceAsObjectStore,
                    IDBDatabase* aDatabase,
                    IDBTransaction* aTransaction)
 {
   MOZ_ASSERT(aSourceAsObjectStore);
   aSourceAsObjectStore->AssertIsOnOwningThread();
 
-  RefPtr<IDBRequest> request = Create(aDatabase, aTransaction);
+  RefPtr<IDBRequest> request = Create(aCx, aDatabase, aTransaction);
 
   request->mSourceAsObjectStore = aSourceAsObjectStore;
 
   return request.forget();
 }
 
 // static
 already_AddRefed<IDBRequest>
-IDBRequest::Create(IDBIndex* aSourceAsIndex,
+IDBRequest::Create(JSContext* aCx,
+                   IDBIndex* aSourceAsIndex,
                    IDBDatabase* aDatabase,
                    IDBTransaction* aTransaction)
 {
   MOZ_ASSERT(aSourceAsIndex);
   aSourceAsIndex->AssertIsOnOwningThread();
 
-  RefPtr<IDBRequest> request = Create(aDatabase, aTransaction);
+  RefPtr<IDBRequest> request = Create(aCx, aDatabase, aTransaction);
 
   request->mSourceAsIndex = aSourceAsIndex;
 
   return request.forget();
 }
 
 // static
 uint64_t
@@ -178,25 +181,24 @@ IDBRequest::SetLoggingSerialNumber(uint6
 {
   AssertIsOnOwningThread();
   MOZ_ASSERT(aLoggingSerialNumber > mLoggingSerialNumber);
 
   mLoggingSerialNumber = aLoggingSerialNumber;
 }
 
 void
-IDBRequest::CaptureCaller(nsAString& aFilename, uint32_t* aLineNo,
-                          uint32_t* aColumn)
+IDBRequest::CaptureCaller(JSContext* aCx, nsAString& aFilename,
+                          uint32_t* aLineNo, uint32_t* aColumn)
 {
   MOZ_ASSERT(aFilename.IsEmpty());
   MOZ_ASSERT(aLineNo);
   MOZ_ASSERT(aColumn);
 
-  ThreadsafeAutoJSContext cx;
-  nsJSUtils::GetCallingLocation(cx, aFilename, aLineNo, aColumn);
+  nsJSUtils::GetCallingLocation(aCx, aFilename, aLineNo, aColumn);
 }
 
 void
 IDBRequest::GetSource(
              Nullable<OwningIDBObjectStoreOrIDBIndexOrIDBCursor>& aSource) const
 {
   AssertIsOnOwningThread();
 
@@ -369,17 +371,16 @@ IDBRequest::SetResultCallback(ResultCall
     // Otherwise our owner is a window and we use that to initialize.
     MOZ_ASSERT(GetOwner());
     if (!autoJS.Init(GetOwner())) {
       IDB_WARNING("Failed to initialize AutoJSAPI!");
       SetError(NS_ERROR_DOM_INDEXEDDB_UNKNOWN_ERR);
       return;
     }
   }
-  autoJS.TakeOwnershipOfErrorReporting();
 
   JSContext* cx = autoJS.cx();
 
   AssertIsRooted();
 
   JS::Rooted<JS::Value> result(cx);
   nsresult rv = aCallback->GetResult(cx, &result);
   if (NS_WARN_IF(NS_FAILED(rv))) {
@@ -525,50 +526,52 @@ IDBOpenDBRequest::IDBOpenDBRequest(IDBFa
 
 IDBOpenDBRequest::~IDBOpenDBRequest()
 {
   AssertIsOnOwningThread();
 }
 
 // static
 already_AddRefed<IDBOpenDBRequest>
-IDBOpenDBRequest::CreateForWindow(IDBFactory* aFactory,
+IDBOpenDBRequest::CreateForWindow(JSContext* aCx,
+                                  IDBFactory* aFactory,
                                   nsPIDOMWindowInner* aOwner,
                                   JS::Handle<JSObject*> aScriptOwner)
 {
   MOZ_ASSERT(aFactory);
   aFactory->AssertIsOnOwningThread();
   MOZ_ASSERT(aOwner);
   MOZ_ASSERT(aScriptOwner);
 
   bool fileHandleDisabled = !IndexedDatabaseManager::IsFileHandleEnabled();
 
   RefPtr<IDBOpenDBRequest> request =
     new IDBOpenDBRequest(aFactory, aOwner, fileHandleDisabled);
-  CaptureCaller(request->mFilename, &request->mLineNo, &request->mColumn);
+  CaptureCaller(aCx, request->mFilename, &request->mLineNo, &request->mColumn);
 
   request->SetScriptOwner(aScriptOwner);
 
   return request.forget();
 }
 
 // static
 already_AddRefed<IDBOpenDBRequest>
-IDBOpenDBRequest::CreateForJS(IDBFactory* aFactory,
+IDBOpenDBRequest::CreateForJS(JSContext* aCx,
+                              IDBFactory* aFactory,
                               JS::Handle<JSObject*> aScriptOwner)
 {
   MOZ_ASSERT(aFactory);
   aFactory->AssertIsOnOwningThread();
   MOZ_ASSERT(aScriptOwner);
 
   bool fileHandleDisabled = !IndexedDatabaseManager::IsFileHandleEnabled();
 
   RefPtr<IDBOpenDBRequest> request =
     new IDBOpenDBRequest(aFactory, nullptr, fileHandleDisabled);
-  CaptureCaller(request->mFilename, &request->mLineNo, &request->mColumn);
+  CaptureCaller(aCx, request->mFilename, &request->mLineNo, &request->mColumn);
 
   request->SetScriptOwner(aScriptOwner);
 
   if (!NS_IsMainThread()) {
     WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
     MOZ_ASSERT(workerPrivate);
 
     workerPrivate->AssertIsOnWorkerThread();
--- a/dom/indexedDB/IDBRequest.h
+++ b/dom/indexedDB/IDBRequest.h
@@ -62,30 +62,33 @@ protected:
   uint32_t mLineNo;
   uint32_t mColumn;
   bool mHaveResultOrErrorCode;
 
 public:
   class ResultCallback;
 
   static already_AddRefed<IDBRequest>
-  Create(IDBDatabase* aDatabase, IDBTransaction* aTransaction);
+  Create(JSContext* aCx, IDBDatabase* aDatabase, IDBTransaction* aTransaction);
 
   static already_AddRefed<IDBRequest>
-  Create(IDBObjectStore* aSource,
+  Create(JSContext* aCx,
+         IDBObjectStore* aSource,
          IDBDatabase* aDatabase,
          IDBTransaction* aTransaction);
 
   static already_AddRefed<IDBRequest>
-  Create(IDBIndex* aSource,
+  Create(JSContext* aCx,
+         IDBIndex* aSource,
          IDBDatabase* aDatabase,
          IDBTransaction* aTransaction);
 
   static void
-  CaptureCaller(nsAString& aFilename, uint32_t* aLineNo, uint32_t* aColumn);
+  CaptureCaller(JSContext* aCx, nsAString& aFilename, uint32_t* aLineNo,
+                uint32_t* aColumn);
 
   static uint64_t
   NextSerialNumber();
 
   // nsIDOMEventTarget
   virtual nsresult
   PreHandleEvent(EventChainPreVisitor& aVisitor) override;
 
@@ -229,22 +232,24 @@ class IDBOpenDBRequest final
   RefPtr<IDBFactory> mFactory;
 
   nsAutoPtr<WorkerFeature> mWorkerFeature;
 
   const bool mFileHandleDisabled;
 
 public:
   static already_AddRefed<IDBOpenDBRequest>
-  CreateForWindow(IDBFactory* aFactory,
+  CreateForWindow(JSContext* aCx,
+                  IDBFactory* aFactory,
                   nsPIDOMWindowInner* aOwner,
                   JS::Handle<JSObject*> aScriptOwner);
 
   static already_AddRefed<IDBOpenDBRequest>
-  CreateForJS(IDBFactory* aFactory,
+  CreateForJS(JSContext* aCx,
+              IDBFactory* aFactory,
               JS::Handle<JSObject*> aScriptOwner);
 
   bool
   IsFileHandleDisabled() const
   {
     return mFileHandleDisabled;
   }
 
--- a/dom/indexedDB/IDBTransaction.cpp
+++ b/dom/indexedDB/IDBTransaction.cpp
@@ -202,31 +202,31 @@ IDBTransaction::CreateVersionChange(
   aDatabase->RegisterTransaction(transaction);
   transaction->mRegistered = true;
 
   return transaction.forget();
 }
 
 // static
 already_AddRefed<IDBTransaction>
-IDBTransaction::Create(IDBDatabase* aDatabase,
+IDBTransaction::Create(JSContext* aCx, IDBDatabase* aDatabase,
                        const nsTArray<nsString>& aObjectStoreNames,
                        Mode aMode)
 {
   MOZ_ASSERT(aDatabase);
   aDatabase->AssertIsOnOwningThread();
   MOZ_ASSERT(!aObjectStoreNames.IsEmpty());
   MOZ_ASSERT(aMode == READ_ONLY ||
              aMode == READ_WRITE ||
              aMode == READ_WRITE_FLUSH ||
              aMode == CLEANUP);
 
   RefPtr<IDBTransaction> transaction =
     new IDBTransaction(aDatabase, aObjectStoreNames, aMode);
-  IDBRequest::CaptureCaller(transaction->mFilename, &transaction->mLineNo,
+  IDBRequest::CaptureCaller(aCx, transaction->mFilename, &transaction->mLineNo,
                             &transaction->mColumn);
 
   transaction->SetScriptOwner(aDatabase->GetScriptOwner());
 
   nsCOMPtr<nsIRunnable> runnable = do_QueryObject(transaction);
   nsContentUtils::RunInMetastableState(runnable.forget());
 
   transaction->mCreating = true;
--- a/dom/indexedDB/IDBTransaction.h
+++ b/dom/indexedDB/IDBTransaction.h
@@ -119,17 +119,17 @@ public:
   static already_AddRefed<IDBTransaction>
   CreateVersionChange(IDBDatabase* aDatabase,
                       indexedDB::BackgroundVersionChangeTransactionChild* aActor,
                       IDBOpenDBRequest* aOpenRequest,
                       int64_t aNextObjectStoreId,
                       int64_t aNextIndexId);
 
   static already_AddRefed<IDBTransaction>
-  Create(IDBDatabase* aDatabase,
+  Create(JSContext* aCx, IDBDatabase* aDatabase,
          const nsTArray<nsString>& aObjectStoreNames,
          Mode aMode);
 
   static IDBTransaction*
   GetCurrent();
 
   void
   AssertIsOnOwningThread() const
--- a/dom/indexedDB/IndexedDatabaseManager.cpp
+++ b/dom/indexedDB/IndexedDatabaseManager.cpp
@@ -479,18 +479,17 @@ IndexedDatabaseManager::CommonPostHandle
 
   RefPtr<DOMError> error = request->GetErrorAfterResult();
 
   nsString errorName;
   if (error) {
     error->GetName(errorName);
   }
 
-  ThreadsafeAutoJSContext cx;
-  RootedDictionary<ErrorEventInit> init(cx);
+  RootedDictionary<ErrorEventInit> init(nsContentUtils::RootingCxForThread());
   request->GetCallerLocation(init.mFilename, &init.mLineno, &init.mColno);
 
   init.mMessage = errorName;
   init.mCancelable = true;
   init.mBubbles = true;
 
   nsEventStatus status = nsEventStatus_eIgnore;
 
--- a/dom/media/IdpSandbox.jsm
+++ b/dom/media/IdpSandbox.jsm
@@ -226,17 +226,17 @@ IdpSandbox.prototype = {
       // as being IdP errors by the IdP and we drop line numbers as a result.
       if (e.name === 'IdpError' || e.name === 'IdpLoginError') {
         throw e;
       }
       this._logError(e);
       throw new Error('Error in IdP, check console for details');
     }
 
-    if (!registrar.idp) {
+    if (!registrar.hasIdp) {
       throw new Error('IdP failed to call rtcIdentityProvider.register()');
     }
     return registrar;
   },
 
   // Capture all the details from the error and log them to the console.  This
   // can't rethrow anything else because that could leak information about the
   // internal workings of the IdP across origins.
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -319,9 +319,14 @@ if CONFIG['MOZ_GONK_MEDIACODEC']:
 include('/ipc/chromium/chromium-config.mozbuild')
 
 # Suppress some GCC warnings being treated as errors:
 #  - about attributes on forward declarations for types that are already
 #    defined, which complains about an important MOZ_EXPORT for android::AString
 if CONFIG['GNU_CC']:
   CXXFLAGS += ['-Wno-error=attributes']
 
+if CONFIG['_MSC_VER']:
+    # This is intended as a temporary workaround to unblock compilation
+    # on VS2015 in warnings as errors mode.
+    CXXFLAGS += ['-wd4312']
+
 FINAL_LIBRARY = 'xul'
--- a/dom/media/systemservices/moz.build
+++ b/dom/media/systemservices/moz.build
@@ -52,16 +52,21 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk
     else:
         LOCAL_INCLUDES += [
             '%' + '%s/system/media/wilhelm/include' % CONFIG['ANDROID_SOURCE'],
         ]
 
 if CONFIG['_MSC_VER']:
     DEFINES['__PRETTY_FUNCTION__'] = '__FUNCSIG__'
 
+    # This is intended as a temporary workaround to enable building with VS2015.
+    # media\webrtc\trunk\webrtc/base/criticalsection.h(59): warning C4312:
+    # 'reinterpret_cast': conversion from 'DWORD' to 'HANDLE' of greater size
+    CXXFLAGS += ['-wd4312']
+
 EXPORTS.mozilla += ['ShmemPool.h',]
 
 EXPORTS.mozilla.media += ['MediaChild.h',
                           'MediaParent.h',
                           'MediaSystemResourceClient.h',
                           'MediaSystemResourceManager.h',
                           'MediaSystemResourceManagerChild.h',
                           'MediaSystemResourceManagerParent.h',
--- a/dom/media/test/external/external_media_harness/testcase.py
+++ b/dom/media/test/external/external_media_harness/testcase.py
@@ -1,36 +1,36 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 import os
 
-from marionette import BrowserMobProxyTestCaseMixin
+from marionette import BrowserMobProxyTestCaseMixin, MarionetteTestCase
 from marionette_driver import Wait
 from marionette_driver.errors import TimeoutException
 from marionette.marionette_test import SkipTest
 
-from firefox_puppeteer.testcases import FirefoxTestCase
+from firefox_puppeteer.testcases import BaseFirefoxTestCase
 from external_media_tests.utils import (timestamp_now, verbose_until)
 from external_media_tests.media_utils.video_puppeteer import (playback_done, playback_started,
                                          VideoException, VideoPuppeteer as VP)
 
 
-class MediaTestCase(FirefoxTestCase):
+class MediaTestCase(BaseFirefoxTestCase, MarionetteTestCase):
 
     """
     Necessary methods for MSE playback
 
     :param video_urls: Urls you are going to play as part of the tests.
     """
 
     def __init__(self, *args, **kwargs):
         self.video_urls = kwargs.pop('video_urls', False)
-        FirefoxTestCase.__init__(self, *args, **kwargs)
+        super(MediaTestCase, self).__init__(*args, **kwargs)
 
     def save_screenshot(self):
         """
         Make a screenshot of the window that is currently playing the video
         element.
         """
         screenshot_dir = os.path.join(self.marionette.instance.workspace or '',
                                       'screenshots')
@@ -102,27 +102,27 @@ class NetworkBandwidthTestCase(MediaTest
     """
     Test MSE playback while network bandwidth is limited. Uses browsermobproxy
     (https://bmp.lightbody.net/). Please see
     https://developer.mozilla.org/en-US/docs/Mozilla/QA/external-media-tests
     for more information on setting up browsermob_proxy.
     """
 
     def __init__(self, *args, **kwargs):
-        MediaTestCase.__init__(self, *args, **kwargs)
+        super(NetworkBandwidthTestCase, self).__init__(*args, **kwargs)
         BrowserMobProxyTestCaseMixin.__init__(self, *args, **kwargs)
         self.proxy = None
 
     def setUp(self):
-        MediaTestCase.setUp(self)
+        super(NetworkBandwidthTestCase, self).setUp()
         BrowserMobProxyTestCaseMixin.setUp(self)
         self.proxy = self.create_browsermob_proxy()
 
     def tearDown(self):
-        MediaTestCase.tearDown(self)
+        super(NetworkBandwidthTestCase, self).tearDown()
         BrowserMobProxyTestCaseMixin.tearDown(self)
         self.proxy = None
 
     def run_videos(self):
         """
         Run each of the videos in the video list. Raises if something goes
         wrong in playback.
         """
--- a/dom/media/test/external/requirements.txt
+++ b/dom/media/test/external/requirements.txt
@@ -10,14 +10,11 @@ mozInstall==1.12
 mozlog==3.1
 moznetwork==0.27
 mozprocess==0.22
 mozprofile==0.28
 mozrunner==6.11
 moztest==0.7
 mozversion==1.4
 wptserve==1.3.0
-marionette-client == 2.2.0
-marionette-driver == 1.3.0
-firefox-puppeteer==3.2.0
-
-# Install the firefox media tests package
-./
+marionette-client==2.2.0
+marionette-driver==1.3.0
+firefox-puppeteer==4.0.0
--- a/dom/media/test/external/setup.py
+++ b/dom/media/test/external/setup.py
@@ -1,23 +1,23 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
+import os
 from setuptools import setup, find_packages
 
-PACKAGE_VERSION = '1.0'
+PACKAGE_VERSION = '2.0'
+
+THIS_DIR = os.path.dirname(os.path.realpath(__name__))
 
-deps = [
-    'marionette-client == 2.2.0',
-    'marionette-driver == 1.3.0',
-    'mozlog == 3.1',
-    'manifestparser == 1.1',
-    'firefox-puppeteer >= 3.2.0, <4.0.0',
-]
+
+def read(*parts):
+    with open(os.path.join(THIS_DIR, *parts)) as f:
+        return f.read()
 
 setup(name='external-media-tests',
       version=PACKAGE_VERSION,
       description=('A collection of Mozilla Firefox media playback tests run '
                    'with Marionette'),
       classifiers=[
           'Environment :: Console',
           'Intended Audience :: Developers',
@@ -29,14 +29,14 @@ setup(name='external-media-tests',
       ],
       keywords='mozilla',
       author='Mozilla Automation and Tools Team',
       author_email='tools@lists.mozilla.org',
       url='https://hg.mozilla.org/mozilla-central/dom/media/test/external/',
       license='MPL 2.0',
       packages=find_packages(),
       zip_safe=False,
-      install_requires=deps,
+      install_requires=read('requirements.txt').splitlines(),
       include_package_data=True,
       entry_points="""
         [console_scripts]
         external-media-tests = external_media_harness:cli
     """)
--- a/dom/media/tests/mochitest/identity/idp.js
+++ b/dom/media/tests/mochitest/identity/idp.js
@@ -96,11 +96,15 @@
           identity: assertion.username,
           contents: assertion.contents
         });
     }
   };
 
   if (!instructions.some(is('not_ready'))) {
     dump('registering idp.js' + global.location.hash + '\n');
-    global.rtcIdentityProvider.register(new IDPJS());
+    var idp = new IDPJS();
+    global.rtcIdentityProvider.register({
+      generateAssertion: idp.generateAssertion.bind(idp),
+      validateAssertion: idp.validateAssertion.bind(idp)
+    });
   }
 }(this));
--- a/dom/media/webrtc/RTCIdentityProviderRegistrar.cpp
+++ b/dom/media/webrtc/RTCIdentityProviderRegistrar.cpp
@@ -1,36 +1,38 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-*/
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "RTCIdentityProviderRegistrar.h"
 #include "mozilla/Attributes.h"
-#include "mozilla/dom/RTCIdentityProviderBinding.h"
 #include "nsCycleCollectionParticipant.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(RTCIdentityProviderRegistrar)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTING_ADDREF(RTCIdentityProviderRegistrar)
 NS_IMPL_CYCLE_COLLECTING_RELEASE(RTCIdentityProviderRegistrar)
 
 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(RTCIdentityProviderRegistrar,
-                                      mGlobal, mIdp)
+                                      mGlobal,
+                                      mGenerateAssertionCallback,
+                                      mValidateAssertionCallback)
 
 RTCIdentityProviderRegistrar::RTCIdentityProviderRegistrar(
     nsIGlobalObject* aGlobal)
     : mGlobal(aGlobal)
-    , mIdp(nullptr)
+    , mGenerateAssertionCallback(nullptr)
+    , mValidateAssertionCallback(nullptr)
 {
   MOZ_COUNT_CTOR(RTCIdentityProviderRegistrar);
 }
 
 RTCIdentityProviderRegistrar::~RTCIdentityProviderRegistrar()
 {
   MOZ_COUNT_DTOR(RTCIdentityProviderRegistrar);
 }
@@ -43,46 +45,46 @@ RTCIdentityProviderRegistrar::GetParentO
 
 JSObject*
 RTCIdentityProviderRegistrar::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return RTCIdentityProviderRegistrarBinding::Wrap(aCx, this, aGivenProto);
 }
 
 void
-RTCIdentityProviderRegistrar::Register(RTCIdentityProvider& aIdp)
+RTCIdentityProviderRegistrar::Register(const RTCIdentityProvider& aIdp)
 {
-  mIdp = &aIdp;
+  mGenerateAssertionCallback = aIdp.mGenerateAssertion;
+  mValidateAssertionCallback = aIdp.mValidateAssertion;
 }
 
-already_AddRefed<RTCIdentityProvider>
-RTCIdentityProviderRegistrar::GetIdp()
+bool
+RTCIdentityProviderRegistrar::HasIdp() const
 {
-  RefPtr<RTCIdentityProvider> idp = mIdp;
-  return idp.forget();
+  return mGenerateAssertionCallback && mValidateAssertionCallback;
 }
 
 already_AddRefed<Promise>
 RTCIdentityProviderRegistrar::GenerateAssertion(
   const nsAString& aContents, const nsAString& aOrigin,
   const Optional<nsAString>& aUsernameHint, ErrorResult& aRv)
 {
-  if (!mIdp) {
+  if (!mGenerateAssertionCallback) {
     aRv.Throw(NS_ERROR_NOT_INITIALIZED);
     return nullptr;
   }
-  return mIdp->GenerateAssertion(aContents, aOrigin, aUsernameHint, aRv);
+  return mGenerateAssertionCallback->Call(aContents, aOrigin, aUsernameHint, aRv);
 }
 already_AddRefed<Promise>
 RTCIdentityProviderRegistrar::ValidateAssertion(
   const nsAString& aAssertion, const nsAString& aOrigin, ErrorResult& aRv)
 {
-  if (!mIdp) {
+  if (!mValidateAssertionCallback) {
     aRv.Throw(NS_ERROR_NOT_INITIALIZED);
     return nullptr;
   }
-  return mIdp->ValidateAssertion(aAssertion, aOrigin, aRv);
+  return mValidateAssertionCallback->Call(aAssertion, aOrigin, aRv);
 }
 
 
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/media/webrtc/RTCIdentityProviderRegistrar.h
+++ b/dom/media/webrtc/RTCIdentityProviderRegistrar.h
@@ -9,49 +9,51 @@
 #include "mozilla/RefPtr.h"
 #include "nsCOMPtr.h"
 #include "nsISupportsImpl.h"
 #include "nsIGlobalObject.h"
 #include "nsWrapperCache.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/BindingDeclarations.h"
+#include "mozilla/dom/RTCIdentityProviderBinding.h"
 
 namespace mozilla {
 namespace dom {
 
-class RTCIdentityProvider;
+struct RTCIdentityProvider;
 
 class RTCIdentityProviderRegistrar final : public nsISupports,
                                            public nsWrapperCache
 {
 public:
   NS_DECL_CYCLE_COLLECTING_ISUPPORTS
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(RTCIdentityProviderRegistrar)
 
   explicit RTCIdentityProviderRegistrar(nsIGlobalObject* aGlobal);
 
   // As required
   nsIGlobalObject* GetParentObject() const;
   virtual JSObject* WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
-  // setter and getter
-  void Register(RTCIdentityProvider& aIdp);
-  already_AddRefed<RTCIdentityProvider> GetIdp();
+  // setter and checker
+  void Register(const RTCIdentityProvider& aIdp);
+  bool HasIdp() const;
 
   already_AddRefed<Promise>
   GenerateAssertion(const nsAString& aContents, const nsAString& aOrigin,
                     const Optional<nsAString>& aUsernameHint, ErrorResult& aRv);
   already_AddRefed<Promise>
   ValidateAssertion(const nsAString& assertion, const nsAString& origin,
                     ErrorResult& aRv);
 
 private:
   ~RTCIdentityProviderRegistrar();
 
   nsCOMPtr<nsIGlobalObject> mGlobal;
-  RefPtr<RTCIdentityProvider> mIdp;
+  RefPtr<GenerateAssertionCallback> mGenerateAssertionCallback;
+  RefPtr<ValidateAssertionCallback> mValidateAssertionCallback;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif /* RTCIDENTITYPROVIDER_H_ */
--- a/dom/mobilemessage/MobileMessageCallback.cpp
+++ b/dom/mobilemessage/MobileMessageCallback.cpp
@@ -253,17 +253,16 @@ MobileMessageCallback::NotifyMessageDele
     JS::Rooted<JS::Value> val(cx, JS::BooleanValue(*aDeleted));
     return NotifySuccess(val);
   }
 
   AutoJSAPI jsapi;
   if (NS_WARN_IF(!jsapi.Init(mDOMRequest->GetOwner()))) {
     return NS_ERROR_FAILURE;
   }
-  jsapi.TakeOwnershipOfErrorReporting();
   JSContext* cx = jsapi.cx();
 
   JS::Rooted<JSObject*> deleteArrayObj(cx, JS_NewArrayObject(cx, aSize));
   if (!deleteArrayObj) {
     return NS_ERROR_OUT_OF_MEMORY;
   }
   for (uint32_t i = 0; i < aSize; i++) {
     if (!JS_DefineElement(cx, deleteArrayObj, i, aDeleted[i],
--- a/dom/permission/Permissions.cpp
+++ b/dom/permission/Permissions.cpp
@@ -47,17 +47,17 @@ already_AddRefed<PermissionStatus>
 CreatePushPermissionStatus(JSContext* aCx,
                            JS::Handle<JSObject*> aPermission,
                            nsPIDOMWindowInner* aWindow,
                            ErrorResult& aRv)
 {
   PushPermissionDescriptor permission;
   JS::Rooted<JS::Value> value(aCx, JS::ObjectOrNullValue(aPermission));
   if (NS_WARN_IF(!permission.Init(aCx, value))) {
-    aRv.Throw(NS_ERROR_UNEXPECTED);
+    aRv.NoteJSContextException(aCx);
     return nullptr;
   }
 
   if (permission.mUserVisible) {
     aRv.Throw(NS_ERROR_NOT_IMPLEMENTED);
     return nullptr;
   }
 
@@ -68,17 +68,17 @@ already_AddRefed<PermissionStatus>
 CreatePermissionStatus(JSContext* aCx,
                        JS::Handle<JSObject*> aPermission,
                        nsPIDOMWindowInner* aWindow,
                        ErrorResult& aRv)
 {
   PermissionDescriptor permission;
   JS::Rooted<JS::Value> value(aCx, JS::ObjectOrNullValue(aPermission));
   if (NS_WARN_IF(!permission.Init(aCx, value))) {
-    aRv.Throw(NS_ERROR_UNEXPECTED);
+    aRv.NoteJSContextException(aCx);
     return nullptr;
   }
 
   switch (permission.mName) {
     case PermissionName::Geolocation:
     case PermissionName::Notifications:
       return PermissionStatus::Create(aWindow, permission.mName, aRv);
 
@@ -100,30 +100,30 @@ Permissions::Query(JSContext* aCx,
                    ErrorResult& aRv)
 {
   nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(mWindow);
   if (!global) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
+  RefPtr<PermissionStatus> status =
+    CreatePermissionStatus(aCx, aPermission, mWindow, aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
+    MOZ_ASSERT(!status);
+    return nullptr;
+  }
+
+  MOZ_ASSERT(status);
   RefPtr<Promise> promise = Promise::Create(global, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
-  RefPtr<PermissionStatus> status =
-    CreatePermissionStatus(aCx, aPermission, mWindow, aRv);
-  if (NS_WARN_IF(aRv.Failed())) {
-    MOZ_ASSERT(!status);
-    promise->MaybeReject(aRv);
-  } else {
-    MOZ_ASSERT(status);
-    promise->MaybeResolve(status);
-  }
+  promise->MaybeResolve(status);
   return promise.forget();
 }
 
 /* static */ nsresult
 Permissions::RemovePermission(nsIPrincipal* aPrincipal, const char* aPermissionType)
 {
   MOZ_ASSERT(XRE_IsParentProcess());
 
@@ -144,17 +144,17 @@ Permissions::Revoke(JSContext* aCx,
   if (!global) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
   }
 
   PermissionDescriptor permission;
   JS::Rooted<JS::Value> value(aCx, JS::ObjectOrNullValue(aPermission));
   if (NS_WARN_IF(!permission.Init(aCx, value))) {
-    aRv.Throw(NS_ERROR_UNEXPECTED);
+    aRv.NoteJSContextException(aCx);
     return nullptr;
   }
 
   RefPtr<Promise> promise = Promise::Create(global, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
@@ -187,18 +187,18 @@ Permissions::Revoke(JSContext* aCx,
     promise->MaybeReject(rv);
     return promise.forget();
   }
 
   RefPtr<PermissionStatus> status =
     CreatePermissionStatus(aCx, aPermission, mWindow, aRv);
   if (NS_WARN_IF(aRv.Failed())) {
     MOZ_ASSERT(!status);
-    promise->MaybeReject(aRv);
-  } else {
-    MOZ_ASSERT(status);
-    promise->MaybeResolve(status);
+    return nullptr;
   }
+
+  MOZ_ASSERT(status);
+  promise->MaybeResolve(status);
   return promise.forget();
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/plugins/base/nsJSNPRuntime.cpp
+++ b/dom/plugins/base/nsJSNPRuntime.cpp
@@ -139,17 +139,16 @@ NPObjectIsOutOfProcessProxy(NPObject *ob
 
 class MOZ_STACK_CLASS AutoJSExceptionSuppressor
 {
 public:
   AutoJSExceptionSuppressor(dom::AutoEntryScript& aes, nsJSObjWrapper* aWrapper)
     : mAes(aes)
     , mIsDestroyPending(aWrapper->mDestroyPending)
   {
-    MOZ_ASSERT(aes.OwnsErrorReporting());
   }
 
   ~AutoJSExceptionSuppressor()
   {
     if (mIsDestroyPending) {
       mAes.ClearException();
     }
   }
--- a/dom/plugins/ipc/moz.build
+++ b/dom/plugins/ipc/moz.build
@@ -147,8 +147,11 @@ else:
     CXXFLAGS += CONFIG['MOZ_GTK2_CFLAGS']
 
 CXXFLAGS += CONFIG['MOZ_CAIRO_CFLAGS']
 
 if CONFIG['_MSC_VER']:
     # This is intended as a temporary hack to support building with VS2015.
     # conversion from 'X' to 'Y' requires a narrowing conversion
     CXXFLAGS += ['-wd4838']
+
+    # 'type cast': conversion from 'unsigned int' to 'HIMC' of greater size
+    CXXFLAGS += ['-wd4312']
--- a/dom/promise/Promise.cpp
+++ b/dom/promise/Promise.cpp
@@ -74,20 +74,24 @@ public:
     MOZ_COUNT_DTOR(PromiseReactionJob);
   }
 
 protected:
   NS_IMETHOD
   Run() override
   {
     NS_ASSERT_OWNINGTHREAD(PromiseReactionJob);
-    ThreadsafeAutoJSContext cx;
-    JS::Rooted<JSObject*> wrapper(cx, mPromise->GetWrapper());
-    MOZ_ASSERT(wrapper); // It was preserved!
-    JSAutoCompartment ac(cx, wrapper);
+
+    MOZ_ASSERT(mPromise->GetWrapper()); // It was preserved!
+
+    AutoJSAPI jsapi;
+    if (!jsapi.Init(mPromise->GetWrapper())) {
+      return NS_ERROR_FAILURE;
+    }
+    JSContext* cx = jsapi.cx();
 
     JS::Rooted<JS::Value> value(cx, mValue);
     if (!MaybeWrapValue(cx, &value)) {
       NS_WARNING("Failed to wrap value into the right compartment.");
       JS_ClearPendingException(cx);
       return NS_OK;
     }
 
@@ -200,22 +204,26 @@ public:
     MOZ_COUNT_DTOR(PromiseResolveThenableJob);
   }
 
 protected:
   NS_IMETHOD
   Run() override
   {
     NS_ASSERT_OWNINGTHREAD(PromiseResolveThenableJob);
-    ThreadsafeAutoJSContext cx;
-    JS::Rooted<JSObject*> wrapper(cx, mPromise->GetWrapper());
-    MOZ_ASSERT(wrapper); // It was preserved!
+
+    MOZ_ASSERT(mPromise->GetWrapper()); // It was preserved!
+
+    AutoJSAPI jsapi;
     // If we ever change which compartment we're working in here, make sure to
     // fix the fast-path for resolved-with-a-Promise in ResolveInternal.
-    JSAutoCompartment ac(cx, wrapper);
+    if (!jsapi.Init(mPromise->GetWrapper())) {
+      return NS_ERROR_FAILURE;
+    }
+    JSContext* cx = jsapi.cx();
 
     JS::Rooted<JSObject*> resolveFunc(cx,
       mPromise->CreateThenableFunction(cx, mPromise, PromiseCallback::Resolve));
 
     if (!resolveFunc) {
       mPromise->HandleException(cx);
       return NS_OK;
     }
@@ -770,17 +778,16 @@ void
 Promise::AppendNativeHandler(PromiseNativeHandler* aRunnable)
 {
   AutoJSAPI jsapi;
   if (NS_WARN_IF(!jsapi.Init(mGlobal))) {
     // Our API doesn't allow us to return a useful error.  Not like this should
     // happen anyway.
     return;
   }
-  jsapi.TakeOwnershipOfErrorReporting();
 
   JSContext* cx = jsapi.cx();
   JS::Rooted<JSObject*> handlerWrapper(cx);
   // Note: PromiseNativeHandler is NOT wrappercached.  So we can't use
   // ToJSValue here, because it will try to do XPConnect wrapping on it, sadly.
   if (NS_WARN_IF(!aRunnable->WrapObject(cx, nullptr, &handlerWrapper))) {
     // Again, no way to report errors.
     jsapi.ClearException();
@@ -1761,17 +1768,16 @@ public:
   void SetValue(uint32_t index, const JS::Handle<JS::Value> aValue)
   {
     MOZ_ASSERT(mCountdown > 0);
 
     AutoJSAPI jsapi;
     if (!jsapi.Init(mValues)) {
       return;
     }
-    jsapi.TakeOwnershipOfErrorReporting();
     JSContext* cx = jsapi.cx();
 
     JS::Rooted<JS::Value> value(cx, aValue);
     JS::Rooted<JSObject*> values(cx, mValues);
     if (!JS_WrapValue(cx, &value) ||
         !JS_DefineElement(cx, values, index, value, JSPROP_ENUMERATE)) {
       MOZ_ASSERT(JS_IsExceptionPending(cx));
       JS::Rooted<JS::Value> exn(cx);
--- a/dom/promise/Promise.h
+++ b/dom/promise/Promise.h
@@ -417,21 +417,24 @@ private:
   void ResolveInternal(JSContext* aCx,
                        JS::Handle<JS::Value> aValue);
   void RejectInternal(JSContext* aCx,
                       JS::Handle<JS::Value> aValue);
 #endif // SPIDERMONKEY_PROMISE
 
   template <typename T>
   void MaybeSomething(T& aArgument, MaybeFunc aFunc) {
-    ThreadsafeAutoJSContext cx;
-    JSObject* wrapper = PromiseObj();
-    MOZ_ASSERT(wrapper); // We preserved it!
+    MOZ_ASSERT(PromiseObj()); // It was preserved!
 
-    JSAutoCompartment ac(cx, wrapper);
+    AutoJSAPI jsapi;
+    if (!jsapi.Init(mGlobal)) {
+      return;
+    }
+    JSContext* cx = jsapi.cx();
+
     JS::Rooted<JS::Value> val(cx);
     if (!ToJSValue(cx, aArgument, &val)) {
       HandleException(cx);
       return;
     }
 
     (this->*aFunc)(cx, val);
   }
--- a/dom/smil/nsSMILCSSValueType.cpp
+++ b/dom/smil/nsSMILCSSValueType.cpp
@@ -354,23 +354,25 @@ ValueFromStringHelper(nsCSSProperty aPro
   // they're more complicated than our simple negation logic here can handle.
   if (aPropID != eCSSProperty_stroke_dasharray) {
     int32_t absValuePos = nsSMILParserUtils::CheckForNegativeNumber(aString);
     if (absValuePos > 0) {
       isNegative = true;
       subStringBegin = (uint32_t)absValuePos; // Start parsing after '-' sign
     }
   }
+  RefPtr<nsStyleContext> styleContext =
+    nsComputedDOMStyle::GetStyleContextForElement(aTargetElement, nullptr,
+                                                  aPresContext->PresShell());
+  if (!styleContext) {
+    return false;
+  }
   nsDependentSubstring subString(aString, subStringBegin);
-  if (!StyleAnimationValue::ComputeValue(aPropID,
-                                         aTargetElement,
-                                         CSSPseudoElementType::NotPseudo,
-                                         subString,
-                                         true,
-                                         aStyleAnimValue,
+  if (!StyleAnimationValue::ComputeValue(aPropID, aTargetElement, styleContext,
+                                         subString, true, aStyleAnimValue,
                                          aIsContextSensitive)) {
     return false;
   }
   if (isNegative) {
     InvertSign(aStyleAnimValue);
   }
 
   if (aPropID == eCSSProperty_font_size) {
--- a/dom/svg/crashtests/crashtests.list
+++ b/dom/svg/crashtests/crashtests.list
@@ -70,9 +70,11 @@ load 880544-1.svg
 load 880544-2.svg
 load 880544-3.svg
 load 880544-4.svg
 load 880544-5.svg
 load 898915-1.svg
 load 1035248-1.svg
 load 1035248-2.svg
 load 1244898-1.xhtml
+# Disabled for now due to it taking a very long time to run - bug 1259356
+#load long-clipPath-reference-chain.svg
 load zero-size-image.svg
new file mode 100644
--- /dev/null
+++ b/dom/svg/crashtests/long-clipPath-reference-chain.svg
@@ -0,0 +1,53 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+  <title>Test very long clipPath chain - MAY CRASH</title>
+  <script><![CDATA[
+
+// This script creates a very long chain of clipPath elements to test whether
+// that causes a stack overflow that crashes the UA.  The first clipPath clips
+// to a 50x100 rect, the last to a 25x100 rect.  If a UA was to apply the
+// entire clipPath chain (unlikely) a rect 25x100 would be rendered.
+//
+// At the time of writing, Firefox would treat the entire chain of clipPaths as
+// invalid due to its excessive length, and refuse to render the referencing
+// element at all.  One alternative would be to ignore the clipPath reference
+// and render the referencing element without any clipping.  Another
+// alternative would be to break the chain and clip the referencing element,
+// but only using the first X clipPaths in the chain (in which case a 50x100
+// rect would be rendered).
+
+var chainLength = 100000;
+
+var SVG_NS = "http://www.w3.org/2000/svg";
+var template = document.createElementNS(SVG_NS, "clipPath");
+var templatesRect = document.createElementNS(SVG_NS, "rect");
+templatesRect.setAttribute("width", "100");
+templatesRect.setAttribute("height", "100");
+template.appendChild(templatesRect);
+
+function createClipPath(index) {
+  var cp = template.cloneNode(true);
+  cp.id = "c" + index;
+  cp.setAttribute("clip-path", "url(#c" + (index + 1) + ")");
+  return cp;
+}
+
+var de = document.documentElement;
+
+for (var i = chainLength; i > 0; --i) {
+  var cp = createClipPath(i);
+
+  if (i == chainLength) {
+    cp.firstChild.setAttribute("width", "25");
+  }
+  else if (i == 1) {
+    cp.firstChild.setAttribute("width", "50");
+  }
+
+  de.appendChild(cp);
+}
+
+  ]]></script>
+  <rect width="100%" height="100%" fill="lime"/>
+  <!-- We don't expect the following element to render at all -->
+  <rect width="500" height="500" clip-path="url(#c1)"/>
+</svg>
rename from dom/webidl/Keyframe.webidl
rename to dom/webidl/BaseKeyframeTypes.webidl
--- a/dom/webidl/Keyframe.webidl
+++ b/dom/webidl/BaseKeyframeTypes.webidl
@@ -1,25 +1,35 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/.
  *
  * The origin of this IDL file is
  * https://w3c.github.io/web-animations/#the-compositeoperation-enumeration
- * https://w3c.github.io/web-animations/#the-keyframe-dictionary
- * https://w3c.github.io/web-animations/#the-computedkeyframe-dictionary
+ * https://w3c.github.io/web-animations/#dictdef-basepropertyindexedkeyframe
+ * https://w3c.github.io/web-animations/#dictdef-basekeyframe
+ * https://w3c.github.io/web-animations/#dictdef-basecomputedkeyframe
  *
- * Copyright © 2015 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
+ * Copyright © 2016 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
 enum CompositeOperation { "replace", "add", "accumulate" };
 
-dictionary Keyframe {
+// The following dictionary types are not referred to by other .webidl files,
+// but we use it for manual JS->IDL and IDL->JS conversions in
+// KeyframeEffectReadOnly's implementation.
+
+dictionary BasePropertyIndexedKeyframe {
+  DOMString easing = "linear";
+  CompositeOperation composite;
+};
+
+dictionary BaseKeyframe {
   double? offset = null;
   DOMString easing = "linear";
-  CompositeOperation? composite = null;
+  CompositeOperation composite;
 };
 
-dictionary ComputedKeyframe : Keyframe {
+dictionary BaseComputedKeyframe : BaseKeyframe {
   double computedOffset;
 };
deleted file mode 100644
--- a/dom/webidl/PropertyIndexedKeyframes.webidl
+++ /dev/null
@@ -1,18 +0,0 @@
-/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/.
- *
- * The origin of this IDL file is
- * https://w3c.github.io/web-animations/#the-propertyindexedkeyframe-dictionary
- *
- * Copyright © 2015 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
- * liability, trademark and document use rules apply.
- */
-
-// No other .webidl files references this dictionary, but we use it for
-// manual JS->IDL conversions in KeyframeEffectReadOnly's implementation.
-dictionary PropertyIndexedKeyframes {
-  DOMString           easing = "linear";
-  CompositeOperation? composite = null;
-};
--- a/dom/webidl/RTCIdentityProvider.webidl
+++ b/dom/webidl/RTCIdentityProvider.webidl
@@ -5,19 +5,19 @@
  *
  * http://w3c.github.io/webrtc-pc/ (with https://github.com/w3c/webrtc-pc/pull/178)
  */
 
 [NoInterfaceObject]
 interface RTCIdentityProviderRegistrar {
   void register(RTCIdentityProvider idp);
 
-  /* The IdP that was passed to register() to chrome code, if any. */
+  /* Whether an IdP was passed to register() to chrome code. */
   [ChromeOnly]
-  readonly attribute RTCIdentityProvider? idp;
+  readonly attribute boolean hasIdp;
   /* The following two chrome-only functions forward to the corresponding
    * function on the registered IdP.  This is necessary because the
    * JS-implemented WebIDL can't see these functions on `idp` above, chrome JS
    * gets an Xray onto the content code that suppresses functions, see
    * https://developer.mozilla.org/en-US/docs/Xray_vision#Xrays_for_JavaScript_objects
    */
   /* Forward to idp.generateAssertion() */
   [ChromeOnly, Throws]
@@ -25,23 +25,26 @@ interface RTCIdentityProviderRegistrar {
   generateAssertion(DOMString contents, DOMString origin,
                     optional DOMString usernameHint);
   /* Forward to idp.validateAssertion() */
   [ChromeOnly, Throws]
   Promise<RTCIdentityValidationResult>
   validateAssertion(DOMString assertion, DOMString origin);
 };
 
-callback interface RTCIdentityProvider {
+dictionary RTCIdentityProvider {
+  required GenerateAssertionCallback generateAssertion;
+  required ValidateAssertionCallback validateAssertion;
+};
+
+callback GenerateAssertionCallback =
   Promise<RTCIdentityAssertionResult>
-  generateAssertion(DOMString contents, DOMString origin,
-                    optional DOMString usernameHint);
-  Promise<RTCIdentityValidationResult>
-  validateAssertion(DOMString assertion, DOMString origin);
-};
+    (DOMString contents, DOMString origin, optional DOMString usernameHint);
+callback ValidateAssertionCallback =
+  Promise<RTCIdentityValidationResult> (DOMString assertion, DOMString origin);
 
 dictionary RTCIdentityAssertionResult {
   required RTCIdentityProviderDetails idp;
   required DOMString assertion;
 };
 
 dictionary RTCIdentityProviderDetails {
   required DOMString domain;
--- a/dom/webidl/WorkerDebuggerGlobalScope.webidl
+++ b/dom/webidl/WorkerDebuggerGlobalScope.webidl
@@ -20,16 +20,22 @@ interface WorkerDebuggerGlobalScope : Ev
   void postMessage(DOMString message);
 
   attribute EventHandler onmessage;
 
   [Throws]
   void setImmediate(Function handler);
 
   void reportError(DOMString message);
+
+  [Throws]
+  sequence<any> retrieveConsoleEvents();
+
+  [Throws]
+  void setConsoleEventHandler(AnyCallback handler);
 };
 
 // So you can debug while you debug
 partial interface WorkerDebuggerGlobalScope {
   void dum