Merge m-c to graphics
authorKartikaya Gupta <kgupta@mozilla.com>
Fri, 09 Dec 2016 15:23:01 -0500
changeset 459873 73bdd29461aef3297b099ba25e8b317a18a0a4b9
parent 459872 ed2fa5b96829ae548df302f29920f47a39a80234 (current diff)
parent 448260 8404d26166a35406f46ff237ed132735c98882b2 (diff)
child 459874 1399cee329f2b4e6dc18f671e76134fc62bc8379
push id41343
push userkgupta@mozilla.com
push dateThu, 12 Jan 2017 20:13:44 +0000
milestone53.0a1
Merge m-c to graphics MozReview-Commit-ID: Cx7uECrZfsq
devtools/client/package.json
devtools/client/webpack.config.js
dom/ipc/TabChild.cpp
gfx/layers/ipc/CompositorBridgeChild.cpp
gfx/layers/ipc/CompositorBridgeChild.h
gfx/thebes/gfxPrefs.h
modules/libpref/init/all.js
taskcluster/ci/build/linux.yml
taskcluster/ci/build/macosx.yml
taskcluster/ci/build/windows.yml
taskcluster/taskgraph/transforms/gecko_v2_whitelist.py
testing/web-platform/meta/encrypted-media/__dir__.ini
testing/web-platform/meta/encrypted-media/clearkey-mp4-playback-temporary-multikey-sequential.html.ini
testing/web-platform/meta/encrypted-media/clearkey-mp4-syntax-mediakeysession.html.ini
testing/web-platform/meta/referrer-policy/same-origin/attr-referrer/cross-origin/http-http/img-tag/cross-origin.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/same-origin/attr-referrer/cross-origin/http-http/img-tag/cross-origin.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/same-origin/attr-referrer/cross-origin/http-http/img-tag/cross-origin.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/same-origin/attr-referrer/cross-origin/http-https/img-tag/cross-origin.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/same-origin/attr-referrer/cross-origin/http-https/img-tag/cross-origin.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/same-origin/attr-referrer/cross-origin/http-https/img-tag/cross-origin.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/same-origin/attr-referrer/same-origin/http-http/img-tag/same-origin-insecure.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/same-origin/attr-referrer/same-origin/http-http/img-tag/same-origin-insecure.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/same-origin/attr-referrer/same-origin/http-http/img-tag/same-origin-insecure.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/same-origin/attr-referrer/same-origin/http-https/img-tag/same-origin-insecure.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/same-origin/http-rp/cross-origin/http-http/img-tag/cross-origin.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/same-origin/http-rp/cross-origin/http-http/img-tag/cross-origin.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/same-origin/http-rp/cross-origin/http-http/img-tag/cross-origin.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/same-origin/http-rp/cross-origin/http-https/img-tag/cross-origin.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/same-origin/http-rp/cross-origin/http-https/img-tag/cross-origin.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/same-origin/http-rp/cross-origin/http-https/img-tag/cross-origin.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/same-origin/http-rp/same-origin/http-http/img-tag/same-origin-insecure.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/same-origin/http-rp/same-origin/http-http/img-tag/same-origin-insecure.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/same-origin/http-rp/same-origin/http-http/img-tag/same-origin-insecure.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/same-origin/http-rp/same-origin/http-https/img-tag/same-origin-insecure.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/same-origin/meta-referrer/cross-origin/http-http/img-tag/cross-origin.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/same-origin/meta-referrer/cross-origin/http-http/img-tag/cross-origin.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/same-origin/meta-referrer/cross-origin/http-http/img-tag/cross-origin.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/same-origin/meta-referrer/cross-origin/http-https/img-tag/cross-origin.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/same-origin/meta-referrer/cross-origin/http-https/img-tag/cross-origin.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/same-origin/meta-referrer/cross-origin/http-https/img-tag/cross-origin.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/same-origin/meta-referrer/same-origin/http-http/img-tag/same-origin-insecure.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/same-origin/meta-referrer/same-origin/http-http/img-tag/same-origin-insecure.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/same-origin/meta-referrer/same-origin/http-http/img-tag/same-origin-insecure.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/same-origin/meta-referrer/same-origin/http-https/img-tag/same-origin-insecure.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/attr-referrer/cross-origin/http-http/img-tag/cross-insecure.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/attr-referrer/cross-origin/http-http/img-tag/cross-insecure.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/attr-referrer/cross-origin/http-http/img-tag/cross-insecure.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/attr-referrer/cross-origin/http-https/img-tag/upgrade-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/attr-referrer/cross-origin/http-https/img-tag/upgrade-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/attr-referrer/cross-origin/http-https/img-tag/upgrade-protocol.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/attr-referrer/same-origin/http-http/img-tag/same-insecure.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/attr-referrer/same-origin/http-http/img-tag/same-insecure.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/attr-referrer/same-origin/http-http/img-tag/same-insecure.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/attr-referrer/same-origin/http-https/img-tag/upgrade-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/attr-referrer/same-origin/http-https/img-tag/upgrade-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/attr-referrer/same-origin/http-https/img-tag/upgrade-protocol.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/http-rp/cross-origin/http-http/img-tag/cross-insecure.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/http-rp/cross-origin/http-http/img-tag/cross-insecure.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/http-rp/cross-origin/http-http/img-tag/cross-insecure.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/http-rp/cross-origin/http-https/img-tag/upgrade-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/http-rp/cross-origin/http-https/img-tag/upgrade-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/http-rp/cross-origin/http-https/img-tag/upgrade-protocol.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/http-rp/same-origin/http-http/img-tag/same-insecure.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/http-rp/same-origin/http-http/img-tag/same-insecure.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/http-rp/same-origin/http-http/img-tag/same-insecure.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/http-rp/same-origin/http-https/img-tag/upgrade-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/http-rp/same-origin/http-https/img-tag/upgrade-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/http-rp/same-origin/http-https/img-tag/upgrade-protocol.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/meta-referrer/cross-origin/http-http/img-tag/cross-insecure.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/meta-referrer/cross-origin/http-http/img-tag/cross-insecure.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/meta-referrer/cross-origin/http-http/img-tag/cross-insecure.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/meta-referrer/cross-origin/http-https/img-tag/upgrade-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/meta-referrer/cross-origin/http-https/img-tag/upgrade-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/meta-referrer/cross-origin/http-https/img-tag/upgrade-protocol.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/meta-referrer/same-origin/http-http/img-tag/same-insecure.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/meta-referrer/same-origin/http-http/img-tag/same-insecure.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/meta-referrer/same-origin/http-http/img-tag/same-insecure.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/meta-referrer/same-origin/http-https/img-tag/upgrade-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/meta-referrer/same-origin/http-https/img-tag/upgrade-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin-when-cross-origin/meta-referrer/same-origin/http-https/img-tag/upgrade-protocol.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/attr-referrer/cross-origin/http-http/img-tag/insecure-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/attr-referrer/cross-origin/http-http/img-tag/insecure-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/attr-referrer/cross-origin/http-http/img-tag/insecure-protocol.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/attr-referrer/cross-origin/http-https/img-tag/upgrade-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/attr-referrer/cross-origin/http-https/img-tag/upgrade-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/attr-referrer/cross-origin/http-https/img-tag/upgrade-protocol.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/attr-referrer/same-origin/http-http/img-tag/insecure-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/attr-referrer/same-origin/http-http/img-tag/insecure-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/attr-referrer/same-origin/http-http/img-tag/insecure-protocol.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/attr-referrer/same-origin/http-https/img-tag/upgrade-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/attr-referrer/same-origin/http-https/img-tag/upgrade-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/attr-referrer/same-origin/http-https/img-tag/upgrade-protocol.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/http-rp/cross-origin/http-http/img-tag/insecure-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/http-rp/cross-origin/http-http/img-tag/insecure-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/http-rp/cross-origin/http-http/img-tag/insecure-protocol.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/http-rp/cross-origin/http-https/img-tag/upgrade-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/http-rp/cross-origin/http-https/img-tag/upgrade-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/http-rp/cross-origin/http-https/img-tag/upgrade-protocol.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/http-rp/same-origin/http-http/img-tag/insecure-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/http-rp/same-origin/http-http/img-tag/insecure-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/http-rp/same-origin/http-http/img-tag/insecure-protocol.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/http-rp/same-origin/http-https/img-tag/upgrade-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/http-rp/same-origin/http-https/img-tag/upgrade-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/http-rp/same-origin/http-https/img-tag/upgrade-protocol.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/meta-referrer/cross-origin/http-http/img-tag/insecure-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/meta-referrer/cross-origin/http-http/img-tag/insecure-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/meta-referrer/cross-origin/http-http/img-tag/insecure-protocol.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/meta-referrer/cross-origin/http-https/img-tag/upgrade-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/meta-referrer/cross-origin/http-https/img-tag/upgrade-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/meta-referrer/cross-origin/http-https/img-tag/upgrade-protocol.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/meta-referrer/same-origin/http-http/img-tag/insecure-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/meta-referrer/same-origin/http-http/img-tag/insecure-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/meta-referrer/same-origin/http-http/img-tag/insecure-protocol.swap-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/meta-referrer/same-origin/http-https/img-tag/upgrade-protocol.keep-origin-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/meta-referrer/same-origin/http-https/img-tag/upgrade-protocol.no-redirect.http.html.ini
testing/web-platform/meta/referrer-policy/strict-origin/meta-referrer/same-origin/http-https/img-tag/upgrade-protocol.swap-origin-redirect.http.html.ini
--- a/.eslintignore
+++ b/.eslintignore
@@ -120,24 +120,16 @@ devtools/server/actors/**
 !devtools/server/actors/webbrowser.js
 !devtools/server/actors/webextension.js
 !devtools/server/actors/webextension-inspected-window.js
 devtools/server/performance/**
 devtools/server/tests/browser/**
 !devtools/server/tests/browser/browser_webextension_inspected_window.js
 devtools/server/tests/mochitest/**
 devtools/server/tests/unit/**
-devtools/shared/*.js
-!devtools/shared/async-storage.js
-!devtools/shared/async-utils.js
-!devtools/shared/defer.js
-!devtools/shared/event-emitter.js
-!devtools/shared/indentation.js
-!devtools/shared/loader-plugin-raw.jsm
-!devtools/shared/task.js
 devtools/shared/apps/**
 devtools/shared/client/**
 devtools/shared/discovery/**
 devtools/shared/gcli/**
 !devtools/shared/gcli/templater.js
 devtools/shared/heapsnapshot/**
 devtools/shared/layout/**
 devtools/shared/locales/**
--- a/.flake8
+++ b/.flake8
@@ -1,5 +1,5 @@
 [flake8]
 # See http://pep8.readthedocs.io/en/latest/intro.html#configuration
-ignore = E121, E123, E126, E133, E226, E241, E242, E704, W503, E402
+ignore = E121, E123, E126, E129, E133, E226, E241, E242, E704, W503, E402
 max-line-length = 99
 filename = *.py, +.lint
--- a/accessible/ipc/DocAccessibleParent.cpp
+++ b/accessible/ipc/DocAccessibleParent.cpp
@@ -491,17 +491,19 @@ mozilla::ipc::IPCResult
 DocAccessibleParent::RecvGetWindowedPluginIAccessible(
       const WindowsHandle& aHwnd, IAccessibleHolder* aPluginCOMProxy)
 {
 #if defined(MOZ_CONTENT_SANDBOX)
   // We don't actually want the accessible object for aHwnd, but rather the
   // one that belongs to its child (see HTMLWin32ObjectAccessible).
   HWND childWnd = ::GetWindow(reinterpret_cast<HWND>(aHwnd), GW_CHILD);
   if (!childWnd) {
-    return IPC_FAIL(this, "GetWindow failed");
+    // We're seeing this in the wild - the plugin is windowed but we no longer
+    // have a window.
+    return IPC_OK();
   }
 
   IAccessible* rawAccPlugin = nullptr;
   HRESULT hr = ::AccessibleObjectFromWindow(childWnd, OBJID_WINDOW,
                                             IID_IAccessible,
                                             (void**)&rawAccPlugin);
   if (FAILED(hr)) {
     // This might happen if the plugin doesn't handle WM_GETOBJECT properly.
--- a/accessible/ipc/DocAccessibleParent.h
+++ b/accessible/ipc/DocAccessibleParent.h
@@ -71,22 +71,21 @@ public:
                                                      const uint32_t& aType) override;
 
   virtual mozilla::ipc::IPCResult RecvRoleChangedEvent(const uint32_t& aRole) override final;
 
   virtual mozilla::ipc::IPCResult RecvBindChildDoc(PDocAccessibleParent* aChildDoc, const uint64_t& aID) override;
 
   void Unbind()
   {
-    mParent = nullptr;
     if (DocAccessibleParent* parent = ParentDoc()) {
-      parent->mChildDocs.RemoveElement(this);
+      parent->RemoveChildDoc(this);
     }
 
-    mParentDoc = nullptr;
+    mParent = nullptr;
   }
 
   virtual mozilla::ipc::IPCResult RecvShutdown() override;
   void Destroy();
   virtual void ActorDestroy(ActorDestroyReason aWhy) override
   {
     MOZ_DIAGNOSTIC_ASSERT(CheckDocTree());
     if (!mShutdown)
--- a/accessible/tests/mochitest/tree/test_tabbrowser.xul
+++ b/accessible/tests/mochitest/tree/test_tabbrowser.xul
@@ -103,27 +103,37 @@
           // NB: The (3) buttons are not visible, unless manually hovered,
           //     probably due to size reduction in this test.
           tabsAccTree.children.splice(0, 0,
             {
               // xul:tab ("about:")
               role: ROLE_PAGETAB,
               children: [
                 {
+                  // xul:text, i.e. the tab label text
+                  role: ROLE_TEXT_LEAF,
+                  children: []
+                },
+                {
                   // xul:toolbarbutton ("Close Tab")
                   role: ROLE_PUSHBUTTON,
                   children: []
                 }
               ]
             },
             {
               // tab ("about:mozilla")
               role: ROLE_PAGETAB,
               children: [
                 {
+                  // xul:text, i.e. the tab label text
+                  role: ROLE_TEXT_LEAF,
+                  children: []
+                },
+                {
                   // xul:toolbarbutton ("Close Tab")
                   role: ROLE_PUSHBUTTON,
                   children: []
                 }
               ]
             },
             {
               // xul:toolbarbutton ("Open a new tab")
--- a/accessible/windows/msaa/HTMLWin32ObjectAccessible.cpp
+++ b/accessible/windows/msaa/HTMLWin32ObjectAccessible.cpp
@@ -80,20 +80,17 @@ HTMLWin32ObjectAccessible::HTMLWin32Obje
       return;
     }
 #endif
 
     // The plugin is not windowless. In this situation we use 
     // use its inner child owned by the plugin so that we don't get
     // in an infinite loop, where the WM_GETOBJECT's get forwarded
     // back to us and create another HTMLWin32ObjectAccessible
-    HWND childWnd = ::GetWindow((HWND)aHwnd, GW_CHILD);
-    if (childWnd) {
-      mHwnd = childWnd;
-    }
+    mHwnd = ::GetWindow((HWND)aHwnd, GW_CHILD);
   }
 }
 
 void
 HTMLWin32ObjectAccessible::GetNativeInterface(void** aNativeAccessible)
 {
 #if defined(MOZ_CONTENT_SANDBOX)
   if (XRE_IsContentProcess()) {
--- a/addon-sdk/source/lib/sdk/util/object.js
+++ b/addon-sdk/source/lib/sdk/util/object.js
@@ -4,16 +4,20 @@
 "use strict";
 
 module.metadata = {
   "stability": "unstable"
 };
 
 const { flatten } = require('./array');
 
+// Create a shortcut for Array.prototype.slice.call().
+const unbind = Function.call.bind(Function.bind, Function.call);
+const slice = unbind(Array.prototype.slice);
+
 /**
  * Merges all the properties of all arguments into first argument. If two or
  * more argument objects have own properties with the same name, the property
  * is overridden, with precedence from right to left, implying, that properties
  * of the object on the left are overridden by a same named property of the
  * object on the right.
  *
  * Any argument given with "falsy" value - commonly `null` and `undefined` in
@@ -29,33 +33,33 @@ const { flatten } = require('./array');
  *    b.name    // 'b'
  */
 function merge(source) {
   let descriptor = {};
 
   // `Boolean` converts the first parameter to a boolean value. Any object is
   // converted to `true` where `null` and `undefined` becames `false`. Therefore
   // the `filter` method will keep only objects that are defined and not null.
-  Array.slice(arguments, 1).filter(Boolean).forEach(function onEach(properties) {
+  slice(arguments, 1).filter(Boolean).forEach(function onEach(properties) {
     getOwnPropertyIdentifiers(properties).forEach(function(name) {
       descriptor[name] = Object.getOwnPropertyDescriptor(properties, name);
     });
   });
   return Object.defineProperties(source, descriptor);
 }
 exports.merge = merge;
 
 /**
  * Returns an object that inherits from the first argument and contains all the
  * properties from all following arguments.
  * `extend(source1, source2, source3)` is equivalent of
  * `merge(Object.create(source1), source2, source3)`.
  */
 function extend(source) {
-  let rest = Array.slice(arguments, 1);
+  let rest = slice(arguments, 1);
   rest.unshift(Object.create(source));
   return merge.apply(null, rest);
 }
 exports.extend = extend;
 
 function has(obj, key) {
   return obj.hasOwnProperty(key);
 }
@@ -67,17 +71,17 @@ function each(obj, fn) {
 exports.each = each;
 
 /**
  * Like `merge`, except no property descriptors are manipulated, for use
  * with platform objects. Identical to underscore's `extend`. Useful for
  * merging XPCOM objects
  */
 function safeMerge(source) {
-  Array.slice(arguments, 1).forEach(function onEach (obj) {
+  slice(arguments, 1).forEach(function onEach (obj) {
     for (let prop in obj) source[prop] = obj[prop];
   });
   return source;
 }
 exports.safeMerge = safeMerge;
 
 /*
  * Returns a copy of the object without omitted properties
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -173,17 +173,17 @@
     <!-- for select dropdowns. The menupopup is what shows the list of options,
          and the popuponly menulist makes things like the menuactive attributes
          work correctly on the menupopup. ContentSelectDropdown expects the
          popuponly menulist to be its immediate parent. -->
     <menulist popuponly="true" id="ContentSelectDropdown" hidden="true">
       <menupopup rolluponmousewheel="true"
                  activateontab="true" position="after_start"
 #ifdef XP_WIN
-                 consumeoutsideclicks="false" ignorekeys="handled"
+                 consumeoutsideclicks="false" ignorekeys="shortcuts"
 #endif
         />
     </menulist>
 
     <!-- for invalid form error message -->
     <panel id="invalid-form-popup" type="arrow" orient="vertical" noautofocus="true" hidden="true" level="parent">
       <description/>
     </panel>
--- a/browser/base/content/tabbrowser.css
+++ b/browser/base/content/tabbrowser.css
@@ -34,22 +34,34 @@
 .tab-sharing-icon-overlay[sharing]:not([selected]),
 .tab-icon-overlay[soundplaying][pinned],
 .tab-icon-overlay[muted][pinned],
 .tab-icon-overlay[blocked][pinned],
 .tab-icon-overlay[crashed] {
   display: -moz-box;
 }
 
-.tab-label[pinned] {
+.tab-label {
+  white-space: nowrap;
+}
+
+.tab-label-container {
+  overflow: hidden;
+}
+
+.tab-label-container[pinned] {
   width: 0;
-  margin-left: 0 !important;
-  margin-right: 0 !important;
-  padding-left: 0 !important;
-  padding-right: 0 !important;
+}
+
+.tab-label-container[textoverflow]:not([pinned]) {
+  mask-image: linear-gradient(to left, transparent, black 1em);
+}
+
+.tab-label-container[textoverflow]:not([pinned]):-moz-locale-dir(rtl) {
+  mask-image: linear-gradient(to right, transparent, black 1em);
 }
 
 .tab-stack {
   vertical-align: top; /* for pinned tabs */
 }
 
 tabpanels {
   background-color: transparent;
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1433,28 +1433,26 @@
       </method>
 
 
       <method name="setTabTitleLoading">
         <parameter name="aTab"/>
         <body>
           <![CDATA[
             aTab.label = this.mStringBundle.getString("tabs.connecting");
-            aTab.crop = "end";
-            this._tabAttrModified(aTab, ["label", "crop"]);
+            this._tabAttrModified(aTab, ["label"]);
           ]]>
         </body>
       </method>
 
       <method name="setTabTitle">
         <parameter name="aTab"/>
         <body>
           <![CDATA[
             var browser = this.getBrowserForTab(aTab);
-            var crop = "end";
             var title = browser.contentTitle;
 
             if (!title) {
               if (browser.currentURI.spec) {
                 try {
                   title = this.mURIFixup.createExposableURI(browser.currentURI).spec;
                 } catch (ex) {
                   title = browser.currentURI.spec;
@@ -1466,35 +1464,30 @@
                 // Let's try to unescape it using a character set
                 // in case the URI is not ASCII.
                 try {
                   var characterSet = browser.characterSet;
                   const textToSubURI = Components.classes["@mozilla.org/intl/texttosuburi;1"]
                                                  .getService(Components.interfaces.nsITextToSubURI);
                   title = textToSubURI.unEscapeNonAsciiURI(characterSet, title);
                 } catch (ex) { /* Do nothing. */ }
-
-                crop = "center";
-
               } else if (aTab.hasAttribute("customizemode")) {
                 let brandBundle = document.getElementById("bundle_brand");
                 let brandShortName = brandBundle.getString("brandShortName");
                 title = gNavigatorBundle.getFormattedString("customizeMode.tabTitle",
                                                             [ brandShortName ]);
               } else // Still no title?  Fall back to our untitled string.
                 title = this.mStringBundle.getString("tabs.emptyTabTitle");
             }
 
-            if (aTab.label == title &&
-                aTab.crop == crop)
+            if (aTab.label == title)
               return false;
 
             aTab.label = title;
-            aTab.crop = crop;
-            this._tabAttrModified(aTab, ["label", "crop"]);
+            this._tabAttrModified(aTab, ["label"]);
 
             if (aTab.selected)
               this.updateTitlebar();
 
             return true;
           ]]>
         </body>
       </method>
@@ -2192,17 +2185,16 @@
               t.setAttribute("label", aURI);
             }
 
             if (aUserContextId) {
               t.setAttribute("usercontextid", aUserContextId);
               ContextualIdentityService.setTabStyle(t);
             }
 
-            t.setAttribute("crop", "end");
             t.setAttribute("onerror", "this.removeAttribute('image');");
             t.className = "tabbrowser-tab";
 
             this.tabContainer._unlockTabSizing();
 
             // When overflowing, new tabs are scrolled into view smoothly, which
             // doesn't go well together with the width transition. So we skip the
             // transition in that case.
@@ -4805,32 +4797,41 @@
                 let fl = browser.QueryInterface(Ci.nsIFrameLoaderOwner).frameLoader;
                 if (fl && fl.tabParent) {
                   fl.tabParent.suppressDisplayport(false);
                   this.mActiveResizeDisplayportSuppression = null;
                 }
               }
               break;
             }
+            case "contextual-identity-updated": {
+              for (let tab of this.tabs) {
+                if (tab.getAttribute("usercontextid") == aData) {
+                  ContextualIdentityService.setTabStyle(tab);
+                }
+              }
+              break;
+            }
             case "nsPref:changed": {
               // This is the only pref observed.
               this._findAsYouType = Services.prefs.getBoolPref("accessibility.typeaheadfind");
               break;
             }
           }
         ]]></body>
       </method>
 
       <constructor>
         <![CDATA[
           this.mCurrentBrowser = document.getAnonymousElementByAttribute(this, "anonid", "initialBrowser");
           this.mCurrentBrowser.permanentKey = {};
 
           Services.obs.addObserver(this, "live-resize-start", false);
           Services.obs.addObserver(this, "live-resize-end", false);
+          Services.obs.addObserver(this, "contextual-identity-updated", false);
 
           this.mCurrentTab = this.tabContainer.firstChild;
           const nsIEventListenerService =
             Components.interfaces.nsIEventListenerService;
           let els = Components.classes["@mozilla.org/eventlistenerservice;1"]
                               .getService(nsIEventListenerService);
           els.addSystemEventListener(document, "keydown", this, false);
           if (this.AppConstants.platform == "macosx") {
@@ -4920,16 +4921,17 @@
           return "panel-" + outerID + "-" + (++this._uniquePanelIDCounter);
         ]]></body>
       </method>
 
       <destructor>
         <![CDATA[
           Services.obs.removeObserver(this, "live-resize-start", false);
           Services.obs.removeObserver(this, "live-resize-end", false);
+          Services.obs.removeObserver(this, "contextual-identity-updated", false);
 
           for (let tab of this.tabs) {
             let browser = tab.linkedBrowser;
             if (browser.registeredOpenURI) {
               this._unifiedComplete.unregisterOpenPage(browser.registeredOpenURI,
                                                        browser.getAttribute("usercontextid") || 0);
               delete browser.registeredOpenURI;
             }
@@ -5272,31 +5274,37 @@
           }
           return [tabStart, tabEnd];
         ]]></body>
       </method>
     </implementation>
 
     <handlers>
       <handler event="underflow" phase="capturing"><![CDATA[
+        if (event.target != this)
+          return;
+
         if (event.detail == 0)
           return; // Ignore vertical events
 
         var tabs = document.getBindingParent(this);
         tabs.removeAttribute("overflow");
 
         if (tabs._lastTabClosedByMouse)
           tabs._expandSpacerBy(this._scrollButtonDown.clientWidth);
 
         for (let tab of Array.from(tabs.tabbrowser._removingTabs))
           tabs.tabbrowser.removeTab(tab);
 
         tabs._positionPinnedTabs();
       ]]></handler>
       <handler event="overflow"><![CDATA[
+        if (event.target != this)
+          return;
+
         if (event.detail == 0)
           return; // Ignore vertical events
 
         var tabs = document.getBindingParent(this);
         tabs.setAttribute("overflow", "true");
         tabs._positionPinnedTabs();
         tabs._handleTabSelect(false);
       ]]></handler>
@@ -5340,17 +5348,16 @@
 
     <implementation implements="nsIDOMEventListener, nsIObserver">
       <constructor>
         <![CDATA[
           this.mTabClipWidth = Services.prefs.getIntPref("browser.tabs.tabClipWidth");
 
           var tab = this.firstChild;
           tab.label = this.tabbrowser.mStringBundle.getString("tabs.emptyTabTitle");
-          tab.setAttribute("crop", "end");
           tab.setAttribute("onerror", "this.removeAttribute('image');");
 
           window.addEventListener("resize", this, false);
           window.addEventListener("load", this, false);
 
           try {
             this._tabAnimationLoggingEnabled = Services.prefs.getBoolPref("browser.tabs.animationLogging.enabled");
           } catch (ex) {
@@ -6715,20 +6722,25 @@
           <xul:image xbl:inherits="sharing,selected=visuallyselected"
                      anonid="sharing-icon"
                      class="tab-sharing-icon-overlay"
                      role="presentation"/>
           <xul:image xbl:inherits="crashed,busy,soundplaying,soundplaying-scheduledremoval,pinned,muted,blocked,selected=visuallyselected"
                      anonid="overlay-icon"
                      class="tab-icon-overlay"
                      role="presentation"/>
-          <xul:label flex="1"
-                     xbl:inherits="value=label,crop,accesskey,fadein,pinned,selected=visuallyselected,attention"
-                     class="tab-text tab-label"
-                     role="presentation"/>
+          <xul:hbox class="tab-label-container"
+                    xbl:inherits="pinned,selected=visuallyselected"
+                    onoverflow="this.setAttribute('textoverflow', 'true');"
+                    onunderflow="this.removeAttribute('textoverflow');"
+                    flex="1">
+            <xul:label class="tab-text tab-label"
+                       xbl:inherits="xbl:text=label,accesskey,fadein,pinned,selected=visuallyselected,attention"
+                       role="presentation"/>
+          </xul:hbox>
           <xul:image xbl:inherits="soundplaying,soundplaying-scheduledremoval,pinned,muted,blocked,selected=visuallyselected"
                      anonid="soundplaying-icon"
                      class="tab-icon-sound"
                      role="presentation"/>
           <xul:toolbarbutton anonid="close-button"
                              xbl:inherits="fadein,pinned,selected=visuallyselected"
                              class="tab-close-button close-icon"/>
         </xul:hbox>
@@ -7097,17 +7109,17 @@
         ]]></body>
       </method>
 
       <method name="_setMenuitemAttributes">
         <parameter name="aMenuitem"/>
         <parameter name="aTab"/>
         <body><![CDATA[
           aMenuitem.setAttribute("label", aTab.label);
-          aMenuitem.setAttribute("crop", aTab.getAttribute("crop"));
+          aMenuitem.setAttribute("crop", "end");
 
           if (aTab.hasAttribute("busy")) {
             aMenuitem.setAttribute("busy", aTab.getAttribute("busy"));
             aMenuitem.removeAttribute("image");
           } else {
             aMenuitem.setAttribute("image", aTab.getAttribute("image"));
             aMenuitem.removeAttribute("busy");
           }
--- a/browser/base/content/test/general/browser_overflowScroll.js
+++ b/browser/base/content/test/general/browser_overflowScroll.js
@@ -32,17 +32,18 @@ function doTest() {
   while (tabs.length < tabCountForOverflow)
     gBrowser.addTab("about:blank", {skipAnimation: true});
   gBrowser.pinTab(tabs[0]);
 
   tabstrip.addEventListener("overflow", runOverflowTests, false);
 }
 
 function runOverflowTests(aEvent) {
-  if (aEvent.detail != 1)
+  if (aEvent.detail != 1 ||
+      aEvent.target != tabstrip)
     return;
 
   tabstrip.removeEventListener("overflow", runOverflowTests, false);
 
   var upButton = tabstrip._scrollButtonUp;
   var downButton = tabstrip._scrollButtonDown;
   var element;
 
--- a/browser/base/content/test/general/browser_selectpopup.js
+++ b/browser/base/content/test/general/browser_selectpopup.js
@@ -159,16 +159,24 @@ function* doSelectTests(contentType, dtd
   is((yield getChangeEvents()), 0, "Before closed - number of change events");
 
   EventUtils.synthesizeKey("a", { accelKey: true });
   yield ContentTask.spawn(gBrowser.selectedBrowser, { isWindows }, function(args) {
     Assert.equal(String(content.getSelection()), args.isWindows ? "Text" : "",
       "Select all while popup is open");
   });
 
+  // Backspace should not go back
+  let handleKeyPress = function(event) {
+    ok(false, "Should not get keypress event");
+  }
+  window.addEventListener("keypress", handleKeyPress);
+  EventUtils.synthesizeKey("VK_BACK_SPACE", { });
+  window.removeEventListener("keypress", handleKeyPress);
+
   yield hideSelectPopup(selectPopup);
 
   is(menulist.selectedIndex, 3, "Item 3 still selected");
   is((yield getInputEvents()), 1, "After closed - number of input events");
   is((yield getChangeEvents()), 1, "After closed - number of change events");
 
   // Opening and closing the popup without changing the value should not fire a change event.
   yield openSelectPopup(selectPopup, "click");
--- a/browser/config/mozconfigs/macosx64/artifact
+++ b/browser/config/mozconfigs/macosx64/artifact
@@ -1,10 +1,13 @@
 MOZ_AUTOMATION_BUILD_SYMBOLS=0
 MOZ_AUTOMATION_L10N_CHECK=0
 
+# Needed to set SourceRepository in application.ini (used by Talos)
+export MOZILLA_OFFICIAL=1
+
 . "$topsrcdir/build/macosx/mozconfig.common"
 . "$topsrcdir/build/mozconfig.common.override"
 
 ac_add_options --enable-artifact-builds
 ac_add_options --enable-artifact-build-symbols
 unset CC
 unset CXX
copy from browser/config/mozconfigs/macosx-universal/beta
copy to browser/config/mozconfigs/macosx64/beta
--- a/browser/config/mozconfigs/macosx-universal/beta
+++ b/browser/config/mozconfigs/macosx64/beta
@@ -1,15 +1,15 @@
 MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-1}
 
 if [ -n "$ENABLE_RELEASE_PROMOTION" ]; then
   MOZ_AUTOMATION_UPLOAD_SYMBOLS=1
   MOZ_AUTOMATION_UPDATE_PACKAGING=1
 fi
 
-. "$topsrcdir/browser/config/mozconfigs/macosx-universal/common-opt"
+. "$topsrcdir/browser/config/mozconfigs/macosx64/common-opt"
 
 ac_add_options --enable-official-branding
 ac_add_options --enable-verify-mar
 
 . "$topsrcdir/build/mozconfig.rust"
 . "$topsrcdir/build/mozconfig.common.override"
 . "$topsrcdir/build/mozconfig.cache"
copy from browser/config/mozconfigs/macosx-universal/common-opt
copy to browser/config/mozconfigs/macosx64/common-opt
--- a/browser/config/mozconfigs/macosx-universal/common-opt
+++ b/browser/config/mozconfigs/macosx64/common-opt
@@ -1,14 +1,11 @@
 # This file is sourced by the nightly, beta, and release mozconfigs.
 
-. $topsrcdir/build/macosx/universal/mozconfig
-
-# Universal builds override the default of browser (bug 575283 comment 29)
-ac_add_options --enable-application=browser
+. $topsrcdir/build/macosx/mozconfig.common
 
 ac_add_options --enable-update-channel=${MOZ_UPDATE_CHANNEL}
 ac_add_options --with-google-api-keyfile=/builds/gapi.data
 ac_add_options --with-mozilla-api-keyfile=/builds/mozilla-desktop-geoloc-api.key
 
 # Needed to enable breakpad in application.ini
 export MOZILLA_OFFICIAL=1
 
--- a/browser/config/mozconfigs/macosx64/nightly
+++ b/browser/config/mozconfigs/macosx64/nightly
@@ -1,22 +1,21 @@
-. $topsrcdir/build/macosx/mozconfig.common
-
-ac_add_options --enable-verify-mar
+. "$topsrcdir/browser/config/mozconfigs/macosx64/common-opt"
 
-# Needed to enable breakpad in application.ini
-export MOZILLA_OFFICIAL=1
+ac_add_options --disable-install-strip
+ac_add_options --enable-verify-mar
+ac_add_options --enable-profiling
+ac_add_options --enable-instruments
 
-# Enable Telemetry
-export MOZ_TELEMETRY_REPORTING=1
+# Cross-compiled builds fail when dtrace is enabled
+if test `uname -s` != Linux; then
+  ac_add_options --enable-dtrace
+fi
 
 if test "${MOZ_UPDATE_CHANNEL}" = "nightly"; then
 ac_add_options --with-macbundlename-prefix=Firefox
 fi
 
-# Package js shell.
-export MOZ_PACKAGE_JSSHELL=1
-
 ac_add_options --with-branding=browser/branding/nightly
 
 . "$topsrcdir/build/mozconfig.rust"
 . "$topsrcdir/build/mozconfig.common.override"
 . "$topsrcdir/build/mozconfig.cache"
copy from browser/config/mozconfigs/macosx-universal/release
copy to browser/config/mozconfigs/macosx64/release
--- a/browser/config/mozconfigs/macosx-universal/release
+++ b/browser/config/mozconfigs/macosx64/release
@@ -2,17 +2,17 @@
 # safeguard below
 MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-1}
 
 if [ -n "$ENABLE_RELEASE_PROMOTION" ]; then
   MOZ_AUTOMATION_UPLOAD_SYMBOLS=1
   MOZ_AUTOMATION_UPDATE_PACKAGING=1
 fi
 
-. "$topsrcdir/browser/config/mozconfigs/macosx-universal/common-opt"
+. "$topsrcdir/browser/config/mozconfigs/macosx64/common-opt"
 
 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
 
--- a/browser/config/mozconfigs/whitelist
+++ b/browser/config/mozconfigs/whitelist
@@ -1,26 +1,26 @@
 # 'nightly' contains things that are in nightly mozconfigs and allowed to be missing from release builds.
 # Other keys in whitelist contain things are in that branches mozconfigs and allowed to be missing from nightly builds.
 whitelist = {
     'release': {},
     'nightly': {},
     }
 
-all_platforms = ['win64', 'win32', 'linux32', 'linux64', 'macosx-universal']
+all_platforms = ['win64', 'win32', 'linux32', 'linux64', 'macosx64']
 
 for platform in all_platforms:
     whitelist['nightly'][platform] = [
         'ac_add_options --enable-update-channel=nightly',
         'ac_add_options --with-branding=browser/branding/nightly',
         'ac_add_options --enable-profiling',
         'mk_add_options CLIENT_PY_ARGS="--hg-options=\'--verbose --time\' --hgtool=../tools/buildfarm/utils/hgtool.py --skip-chatzilla --skip-comm --skip-inspector --tinderbox-print"'
     ]
 
-for platform in ['linux32', 'linux64', 'macosx-universal']:
+for platform in ['linux32', 'linux64', 'macosx64']:
     whitelist['nightly'][platform] += [
         'mk_add_options MOZ_MAKE_FLAGS="-j4"',
     ]
 
 whitelist['nightly']['linux32'] += [
     'CXX=$REAL_CXX',
     'CXX="ccache $REAL_CXX"',
     'CC="ccache $REAL_CC"',
@@ -37,17 +37,17 @@ whitelist['nightly']['linux64'] += [
     'export MOZILLA_OFFICIAL=1',
     'export MOZ_TELEMETRY_REPORTING=1',
     "mk_add_options PROFILE_GEN_SCRIPT='$(PYTHON) @MOZ_OBJDIR@/_profile/pgo/profileserver.py 10'",
     'STRIP_FLAGS="--strip-debug"',
     'ac_add_options --with-ccache=/usr/bin/ccache',
     '. "$topsrcdir/build/mozconfig.cache"',
 ]
 
-whitelist['nightly']['macosx-universal'] += [
+whitelist['nightly']['macosx64'] += [
     'if test "${MOZ_UPDATE_CHANNEL}" = "nightly"; then',
     'ac_add_options --with-macbundlename-prefix=Firefox',
     'fi',
     'mk_add_options MOZ_MAKE_FLAGS="-j12"',
     'ac_add_options --with-ccache',
     '. "$topsrcdir/build/mozconfig.cache"',
     'ac_add_options --disable-install-strip',
     'ac_add_options --enable-instruments',
--- a/browser/config/mozconfigs/win32/artifact
+++ b/browser/config/mozconfigs/win32/artifact
@@ -1,10 +1,13 @@
 MOZ_AUTOMATION_BUILD_SYMBOLS=0
 MOZ_AUTOMATION_L10N_CHECK=0
 
+# Needed to set SourceRepository in application.ini (used by Talos)
+export MOZILLA_OFFICIAL=1
+
 . "$topsrcdir/browser/config/mozconfigs/common"
 . "$topsrcdir/build/mozconfig.win-common"
 . "$topsrcdir/build/win32/mozconfig.vs-latest"
 . "$topsrcdir/build/mozconfig.common.override"
 
 ac_add_options --enable-artifact-builds
 ac_add_options --enable-artifact-build-symbols
--- a/browser/config/mozconfigs/win64/artifact
+++ b/browser/config/mozconfigs/win64/artifact
@@ -1,11 +1,14 @@
 MOZ_AUTOMATION_BUILD_SYMBOLS=0
 MOZ_AUTOMATION_L10N_CHECK=0
 
+# Needed to set SourceRepository in application.ini (used by Talos)
+export MOZILLA_OFFICIAL=1
+
 . "$topsrcdir/browser/config/mozconfigs/win64/common-win64"
 . "$topsrcdir/browser/config/mozconfigs/common"
 . "$topsrcdir/build/mozconfig.win-common"
 . "$topsrcdir/build/win64/mozconfig.vs-latest"
 . "$topsrcdir/build/mozconfig.common.override"
 
 ac_add_options --enable-artifact-builds
 ac_add_options --enable-artifact-build-symbols
--- a/browser/extensions/pdfjs/README.mozilla
+++ b/browser/extensions/pdfjs/README.mozilla
@@ -1,3 +1,3 @@
 This is the pdf.js project output, https://github.com/mozilla/pdf.js
 
-Current extension version is: 1.6.355
+Current extension version is: 1.6.372
--- a/browser/extensions/pdfjs/content/build/pdf.js
+++ b/browser/extensions/pdfjs/content/build/pdf.js
@@ -18,18 +18,18 @@
   define('pdfjs-dist/build/pdf', ['exports'], factory);
  } else if (typeof exports !== 'undefined') {
   factory(exports);
  } else {
   factory(root['pdfjsDistBuildPdf'] = {});
  }
 }(this, function (exports) {
  'use strict';
- var pdfjsVersion = '1.6.355';
- var pdfjsBuild = '451956c';
+ var pdfjsVersion = '1.6.372';
+ var pdfjsBuild = 'aaec4908';
  var pdfjsFilePath = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : null;
  var pdfjsLibs = {};
  (function pdfjsWrapper() {
   (function (root, factory) {
    factory(root.pdfjsSharedUtil = {});
   }(this, function (exports) {
    var globalScope = typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : this;
    var FONT_IDENTITY_MATRIX = [
@@ -1717,17 +1717,17 @@
    function fixMetadata(meta) {
     return meta.replace(/>\\376\\377([^<]+)/g, function (all, codes) {
      var bytes = codes.replace(/\\([0-3])([0-7])([0-7])/g, function (code, d1, d2, d3) {
       return String.fromCharCode(d1 * 64 + d2 * 8 + d3 * 1);
      });
      var chars = '';
      for (var i = 0; i < bytes.length; i += 2) {
       var code = bytes.charCodeAt(i) * 256 + bytes.charCodeAt(i + 1);
-      chars += code >= 32 && code < 127 && code !== 60 && code !== 62 && code !== 38 && false ? String.fromCharCode(code) : '&#x' + (0x10000 + code).toString(16).substring(1) + ';';
+      chars += code >= 32 && code < 127 && code !== 60 && code !== 62 && code !== 38 ? String.fromCharCode(code) : '&#x' + (0x10000 + code).toString(16).substring(1) + ';';
      }
      return '>' + chars;
     });
    }
    function Metadata(meta) {
     if (typeof meta === 'string') {
      meta = fixMetadata(meta);
      var parser = new DOMParser();
--- a/browser/extensions/pdfjs/content/build/pdf.worker.js
+++ b/browser/extensions/pdfjs/content/build/pdf.worker.js
@@ -18,18 +18,18 @@
   define('pdfjs-dist/build/pdf.worker', ['exports'], factory);
  } else if (typeof exports !== 'undefined') {
   factory(exports);
  } else {
   factory(root['pdfjsDistBuildPdfWorker'] = {});
  }
 }(this, function (exports) {
  'use strict';
- var pdfjsVersion = '1.6.355';
- var pdfjsBuild = '451956c';
+ var pdfjsVersion = '1.6.372';
+ var pdfjsBuild = 'aaec4908';
  var pdfjsFilePath = typeof document !== 'undefined' && document.currentScript ? document.currentScript.src : null;
  var pdfjsLibs = {};
  (function pdfjsWrapper() {
   (function (root, factory) {
    factory(root.pdfjsCoreArithmeticDecoder = {});
   }(this, function (exports) {
    var ArithmeticDecoder = function ArithmeticDecoderClosure() {
     var QeTable = [
@@ -40449,41 +40449,37 @@
       if (isDict(colorSpaces)) {
        var refcs = colorSpaces.get(cs.name);
        if (refcs) {
         cs = refcs;
        }
       }
      }
      cs = xref.fetchIfRef(cs);
-     var mode;
      if (isName(cs)) {
-      mode = cs.name;
-      this.mode = mode;
-      switch (mode) {
+      switch (cs.name) {
       case 'DeviceGray':
       case 'G':
        return 'DeviceGrayCS';
       case 'DeviceRGB':
       case 'RGB':
        return 'DeviceRgbCS';
       case 'DeviceCMYK':
       case 'CMYK':
        return 'DeviceCmykCS';
       case 'Pattern':
        return [
         'PatternCS',
         null
        ];
       default:
-       error('unrecognized colorspace ' + mode);
+       error('unrecognized colorspace ' + cs.name);
       }
      } else if (isArray(cs)) {
-      mode = xref.fetchIfRef(cs[0]).name;
-      this.mode = mode;
+      var mode = xref.fetchIfRef(cs[0]).name;
       var numComps, params, alt, whitePoint, blackPoint, gamma;
       switch (mode) {
       case 'DeviceGray':
       case 'G':
        return 'DeviceGrayCS';
       case 'DeviceRGB':
       case 'RGB':
        return 'DeviceRgbCS';
@@ -40556,22 +40552,17 @@
         'IndexedCS',
         baseIndexedCS,
         hiVal,
         lookup
        ];
       case 'Separation':
       case 'DeviceN':
        var name = xref.fetchIfRef(cs[1]);
-       numComps = 1;
-       if (isName(name)) {
-        numComps = 1;
-       } else if (isArray(name)) {
-        numComps = name.length;
-       }
+       numComps = isArray(name) ? name.length : 1;
        alt = ColorSpace.parseToIR(cs[2], xref, res);
        var tintFnIR = PDFFunction.getIR(xref, xref.fetchIfRef(cs[3]));
        return [
         'AlternateCS',
         numComps,
         alt,
         tintFnIR
        ];
@@ -40649,32 +40640,26 @@
       var usesZeroToOneRange = base.usesZeroToOneRange;
       var isPassthrough = (base.isPassthrough(8) || !usesZeroToOneRange) && alpha01 === 0;
       var pos = isPassthrough ? destOffset : 0;
       var baseBuf = isPassthrough ? dest : new Uint8Array(baseNumComps * count);
       var numComps = this.numComps;
       var scaled = new Float32Array(numComps);
       var tinted = new Float32Array(baseNumComps);
       var i, j;
-      if (usesZeroToOneRange) {
-       for (i = 0; i < count; i++) {
-        for (j = 0; j < numComps; j++) {
-         scaled[j] = src[srcOffset++] * scale;
-        }
-        tintFn(scaled, 0, tinted, 0);
+      for (i = 0; i < count; i++) {
+       for (j = 0; j < numComps; j++) {
+        scaled[j] = src[srcOffset++] * scale;
+       }
+       tintFn(scaled, 0, tinted, 0);
+       if (usesZeroToOneRange) {
         for (j = 0; j < baseNumComps; j++) {
          baseBuf[pos++] = tinted[j] * 255;
         }
-       }
-      } else {
-       for (i = 0; i < count; i++) {
-        for (j = 0; j < numComps; j++) {
-         scaled[j] = src[srcOffset++] * scale;
-        }
-        tintFn(scaled, 0, tinted, 0);
+       } else {
         base.getRgbItem(tinted, 0, baseBuf, pos);
         pos += baseNumComps;
        }
       }
       if (!isPassthrough) {
        base.getRgbBuffer(baseBuf, 0, count, dest, destOffset, 8, alpha01);
       }
      },
@@ -40697,37 +40682,35 @@
     }
     PatternCS.prototype = {};
     return PatternCS;
    }();
    var IndexedCS = function IndexedCSClosure() {
     function IndexedCS(base, highVal, lookup) {
      this.name = 'Indexed';
      this.numComps = 1;
-     this.defaultColor = new Uint8Array([0]);
+     this.defaultColor = new Uint8Array(this.numComps);
      this.base = base;
      this.highVal = highVal;
      var baseNumComps = base.numComps;
      var length = baseNumComps * highVal;
-     var lookupArray;
      if (isStream(lookup)) {
-      lookupArray = new Uint8Array(length);
+      this.lookup = new Uint8Array(length);
       var bytes = lookup.getBytes(length);
-      lookupArray.set(bytes);
+      this.lookup.set(bytes);
      } else if (isString(lookup)) {
-      lookupArray = new Uint8Array(length);
+      this.lookup = new Uint8Array(length);
       for (var i = 0; i < length; ++i) {
-       lookupArray[i] = lookup.charCodeAt(i);
+       this.lookup[i] = lookup.charCodeAt(i);
       }
      } else if (lookup instanceof Uint8Array || lookup instanceof Array) {
-      lookupArray = lookup;
+      this.lookup = lookup;
      } else {
       error('Unrecognized lookup table: ' + lookup);
      }
-     this.lookup = lookupArray;
     }
     IndexedCS.prototype = {
      getRgb: ColorSpace.prototype.getRgb,
      getRgbItem: function IndexedCS_getRgbItem(src, srcOffset, dest, destOffset) {
       var numComps = this.base.numComps;
       var start = src[srcOffset] * numComps;
       this.base.getRgbItem(this.lookup, start, dest, destOffset);
      },
@@ -40753,17 +40736,17 @@
      usesZeroToOneRange: true
     };
     return IndexedCS;
    }();
    var DeviceGrayCS = function DeviceGrayCSClosure() {
     function DeviceGrayCS() {
      this.name = 'DeviceGray';
      this.numComps = 1;
-     this.defaultColor = new Float32Array([0]);
+     this.defaultColor = new Float32Array(this.numComps);
     }
     DeviceGrayCS.prototype = {
      getRgb: ColorSpace.prototype.getRgb,
      getRgbItem: function DeviceGrayCS_getRgbItem(src, srcOffset, dest, destOffset) {
       var c = src[srcOffset] * 255 | 0;
       c = c < 0 ? 0 : c > 255 ? 255 : c;
       dest[destOffset] = dest[destOffset + 1] = dest[destOffset + 2] = c;
      },
@@ -40789,21 +40772,17 @@
      usesZeroToOneRange: true
     };
     return DeviceGrayCS;
    }();
    var DeviceRgbCS = function DeviceRgbCSClosure() {
     function DeviceRgbCS() {
      this.name = 'DeviceRGB';
      this.numComps = 3;
-     this.defaultColor = new Float32Array([
-      0,
-      0,
-      0
-     ]);
+     this.defaultColor = new Float32Array(this.numComps);
     }
     DeviceRgbCS.prototype = {
      getRgb: ColorSpace.prototype.getRgb,
      getRgbItem: function DeviceRgbCS_getRgbItem(src, srcOffset, dest, destOffset) {
       var r = src[srcOffset] * 255 | 0;
       var g = src[srcOffset + 1] * 255 | 0;
       var b = src[srcOffset + 2] * 255 | 0;
       dest[destOffset] = r < 0 ? 0 : r > 255 ? 255 : r;
@@ -40849,22 +40828,18 @@
      var b = c * (0.8842522430003296 * c + 8.078677503112928 * m + 30.89978309703729 * y - 0.23883238689178934 * k + -14.183576799673286) + m * (10.49593273432072 * m + 63.02378494754052 * y + 50.606957656360734 * k - 112.23884253719248) + y * (0.03296041114873217 * y + 115.60384449646641 * k + -193.58209356861505) + k * (-22.33816807309886 * k - 180.12613974708367) + 255 | 0;
      dest[destOffset] = r > 255 ? 255 : r < 0 ? 0 : r;
      dest[destOffset + 1] = g > 255 ? 255 : g < 0 ? 0 : g;
      dest[destOffset + 2] = b > 255 ? 255 : b < 0 ? 0 : b;
     }
     function DeviceCmykCS() {
      this.name = 'DeviceCMYK';
      this.numComps = 4;
-     this.defaultColor = new Float32Array([
-      0,
-      0,
-      0,
-      1
-     ]);
+     this.defaultColor = new Float32Array(this.numComps);
+     this.defaultColor[3] = 1;
     }
     DeviceCmykCS.prototype = {
      getRgb: ColorSpace.prototype.getRgb,
      getRgbItem: function DeviceCmykCS_getRgbItem(src, srcOffset, dest, destOffset) {
       convertToRgb(src, srcOffset, 1, dest, destOffset);
      },
      getRgbBuffer: function DeviceCmykCS_getRgbBuffer(src, srcOffset, count, dest, destOffset, bits, alpha01) {
       var scale = 1 / ((1 << bits) - 1);
@@ -40885,17 +40860,17 @@
      usesZeroToOneRange: true
     };
     return DeviceCmykCS;
    }();
    var CalGrayCS = function CalGrayCSClosure() {
     function CalGrayCS(whitePoint, blackPoint, gamma) {
      this.name = 'CalGray';
      this.numComps = 1;
-     this.defaultColor = new Float32Array([0]);
+     this.defaultColor = new Float32Array(this.numComps);
      if (!whitePoint) {
       error('WhitePoint missing - required for color space CalGray');
      }
      blackPoint = blackPoint || [
       0,
       0,
       0
      ];
@@ -40997,17 +40972,17 @@
     ]);
     var tempNormalizeMatrix = new Float32Array(3);
     var tempConvertMatrix1 = new Float32Array(3);
     var tempConvertMatrix2 = new Float32Array(3);
     var DECODE_L_CONSTANT = Math.pow((8 + 16) / 116, 3) / 8.0;
     function CalRGBCS(whitePoint, blackPoint, gamma, matrix) {
      this.name = 'CalRGB';
      this.numComps = 3;
-     this.defaultColor = new Float32Array(3);
+     this.defaultColor = new Float32Array(this.numComps);
      if (!whitePoint) {
       error('WhitePoint missing - required for color space CalRGB');
      }
      blackPoint = blackPoint || new Float32Array(3);
      gamma = gamma || new Float32Array([
       1,
       1,
       1
@@ -41197,21 +41172,17 @@
      usesZeroToOneRange: true
     };
     return CalRGBCS;
    }();
    var LabCS = function LabCSClosure() {
     function LabCS(whitePoint, blackPoint, range) {
      this.name = 'Lab';
      this.numComps = 3;
-     this.defaultColor = new Float32Array([
-      0,
-      0,
-      0
-     ]);
+     this.defaultColor = new Float32Array(this.numComps);
      if (!whitePoint) {
       error('WhitePoint missing - required for color space Lab');
      }
      blackPoint = blackPoint || [
       0,
       0,
       0
      ];
@@ -45616,73 +45587,73 @@
       var glyphsVMetrics = [];
       var defaultVMetrics;
       var i, ii, j, jj, start, code, widths;
       if (properties.composite) {
        defaultWidth = dict.get('DW') || 1000;
        widths = dict.get('W');
        if (widths) {
         for (i = 0, ii = widths.length; i < ii; i++) {
-         start = widths[i++];
+         start = xref.fetchIfRef(widths[i++]);
          code = xref.fetchIfRef(widths[i]);
          if (isArray(code)) {
           for (j = 0, jj = code.length; j < jj; j++) {
-           glyphsWidths[start++] = code[j];
+           glyphsWidths[start++] = xref.fetchIfRef(code[j]);
           }
          } else {
-          var width = widths[++i];
+          var width = xref.fetchIfRef(widths[++i]);
           for (j = start; j <= code; j++) {
            glyphsWidths[j] = width;
           }
          }
         }
        }
        if (properties.vertical) {
-        var vmetrics = dict.get('DW2') || [
+        var vmetrics = dict.getArray('DW2') || [
          880,
          -1000
         ];
         defaultVMetrics = [
          vmetrics[1],
          defaultWidth * 0.5,
          vmetrics[0]
         ];
         vmetrics = dict.get('W2');
         if (vmetrics) {
          for (i = 0, ii = vmetrics.length; i < ii; i++) {
-          start = vmetrics[i++];
+          start = xref.fetchIfRef(vmetrics[i++]);
           code = xref.fetchIfRef(vmetrics[i]);
           if (isArray(code)) {
            for (j = 0, jj = code.length; j < jj; j++) {
             glyphsVMetrics[start++] = [
-             code[j++],
-             code[j++],
-             code[j]
+             xref.fetchIfRef(code[j++]),
+             xref.fetchIfRef(code[j++]),
+             xref.fetchIfRef(code[j])
             ];
            }
           } else {
            var vmetric = [
-            vmetrics[++i],
-            vmetrics[++i],
-            vmetrics[++i]
+            xref.fetchIfRef(vmetrics[++i]),
+            xref.fetchIfRef(vmetrics[++i]),
+            xref.fetchIfRef(vmetrics[++i])
            ];
            for (j = start; j <= code; j++) {
             glyphsVMetrics[j] = vmetric;
            }
           }
          }
         }
        }
       } else {
        var firstChar = properties.firstChar;
        widths = dict.get('Widths');
        if (widths) {
         j = firstChar;
         for (i = 0, ii = widths.length; i < ii; i++) {
-         glyphsWidths[j++] = widths[i];
+         glyphsWidths[j++] = xref.fetchIfRef(widths[i]);
         }
         defaultWidth = parseFloat(descriptor.get('MissingWidth')) || 0;
        } else {
         var baseFontName = dict.get('BaseFont');
         if (isName(baseFontName)) {
          var metrics = this.getBaseFontMetrics(baseFontName.name);
          glyphsWidths = this.buildCharCodeToWidth(metrics.widths, properties);
          defaultWidth = metrics.defaultWidth;
--- a/browser/extensions/pdfjs/content/web/viewer.js
+++ b/browser/extensions/pdfjs/content/web/viewer.js
@@ -1302,17 +1302,17 @@ var pdfjsWebLibs;
     }
     return delta;
    }
    var animationStarted = new Promise(function (resolve) {
     window.requestAnimationFrame(resolve);
    });
    var localized = new Promise(function (resolve, reject) {
     if (!mozL10n) {
-     reject(new Error('mozL10n service is not available.'));
+     resolve();
      return;
     }
     if (mozL10n.getReadyState() !== 'loading') {
      resolve();
      return;
     }
     window.addEventListener('localized', function localized(evt) {
      resolve();
@@ -6712,17 +6712,17 @@ var pdfjsWebLibs;
     if (PDFViewerApplication.pdfViewer.containsElement(evt.target) || appConfig.toolbar.container.contains(evt.target) && evt.target !== appConfig.secondaryToolbar.toggleButton) {
      PDFViewerApplication.secondaryToolbar.close();
     }
    }, true);
    window.addEventListener('keydown', function keydown(evt) {
     if (OverlayManager.active) {
      return;
     }
-    var handled = false;
+    var handled = false, ensureViewerFocused = false;
     var cmd = (evt.ctrlKey ? 1 : 0) | (evt.altKey ? 2 : 0) | (evt.shiftKey ? 4 : 0) | (evt.metaKey ? 8 : 0);
     var pdfViewer = PDFViewerApplication.pdfViewer;
     var isViewerInPresentationMode = pdfViewer && pdfViewer.isInPresentationMode;
     if (cmd === 1 || cmd === 8 || cmd === 5 || cmd === 12) {
      switch (evt.keyCode) {
      case 70:
       if (!PDFViewerApplication.supportsIntegratedFind) {
        PDFViewerApplication.findBar.open();
@@ -6765,42 +6765,58 @@ var pdfjsWebLibs;
      case 96:
       if (!isViewerInPresentationMode) {
        setTimeout(function () {
         pdfViewer.currentScaleValue = DEFAULT_SCALE_VALUE;
        });
        handled = false;
       }
       break;
+     case 38:
+      if (isViewerInPresentationMode || PDFViewerApplication.page > 1) {
+       PDFViewerApplication.page = 1;
+       handled = true;
+       ensureViewerFocused = true;
+      }
+      break;
+     case 40:
+      if (isViewerInPresentationMode || PDFViewerApplication.page < PDFViewerApplication.pagesCount) {
+       PDFViewerApplication.page = PDFViewerApplication.pagesCount;
+       handled = true;
+       ensureViewerFocused = true;
+      }
+      break;
      }
     }
     if (cmd === 3 || cmd === 10) {
      switch (evt.keyCode) {
      case 80:
       PDFViewerApplication.requestPresentationMode();
       handled = true;
       break;
      case 71:
       PDFViewerApplication.appConfig.toolbar.pageNumber.select();
       handled = true;
       break;
      }
     }
     if (handled) {
+     if (ensureViewerFocused && !isViewerInPresentationMode) {
+      pdfViewer.focus();
+     }
      evt.preventDefault();
      return;
     }
     var curElement = document.activeElement || document.querySelector(':focus');
     var curElementTagName = curElement && curElement.tagName.toUpperCase();
     if (curElementTagName === 'INPUT' || curElementTagName === 'TEXTAREA' || curElementTagName === 'SELECT') {
      if (evt.keyCode !== 27) {
       return;
      }
     }
-    var ensureViewerFocused = false;
     if (cmd === 0) {
      switch (evt.keyCode) {
      case 38:
      case 33:
      case 8:
       if (!isViewerInPresentationMode && pdfViewer.currentScaleValue !== 'page-fit') {
        break;
       }
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -2445,16 +2445,17 @@ toolbarbutton.chevron > .toolbarbutton-m
   margin: 0;
 }
 
 %include ../shared/tabs.inc.css
 
 .tab-label {
   margin-top: 1px;
   margin-bottom: 0;
+  -moz-box-flex: 1;
   text-align: center;
 }
 
 @media (-moz-mac-yosemite-theme) {
   /* image preloading hack from shared/tabs.inc.css */
   #tabbrowser-tabs::before {
     background-image:
       url(chrome://browser/skin/yosemite/tab-selected-end-inactive.svg),
@@ -2544,17 +2545,17 @@ toolbarbutton.chevron > .toolbarbutton-m
 /*
  * Force the overlay to create a new stacking context so it always appears on
  * top of the icon.
  */
 .tab-icon-overlay {
   opacity: 0.9999;
 }
 
-.tab-label:not([selected="true"]) {
+.tab-label-container:not([selected="true"]) {
   opacity: .7;
 }
 
 .tabbrowser-tab,
 .tabs-newtab-button {
   font: message-box;
   border: none;
 }
@@ -2570,17 +2571,17 @@ toolbarbutton.chevron > .toolbarbutton-m
 }
 
 .tabs-newtab-button > .toolbarbutton-icon {
   -moz-box-align: center;
   border: solid transparent;
   border-width: 0 11px;
 }
 
-.tabbrowser-tab:focus > .tab-stack > .tab-content > .tab-label:not([pinned]),
+.tabbrowser-tab:focus > .tab-stack > .tab-content > .tab-label-container:not([pinned]),
 .tabbrowser-tab:focus > .tab-stack > .tab-content > .tab-icon-image[pinned],
 .tabbrowser-tab:focus > .tab-stack > .tab-content > .tab-throbber[pinned] {
   box-shadow: @focusRingShadow@;
 }
 
 #TabsToolbar {
   -moz-appearance: none;
   /* overlap the nav-bar's top border */
--- a/browser/themes/shared/incontentprefs/preferences.inc.css
+++ b/browser/themes/shared/incontentprefs/preferences.inc.css
@@ -508,17 +508,17 @@ description > html|a {
   text-align: center;
   padding-left: 11px;
   padding-right: 11px;
   margin: 0;
   min-width: 0;
 }
 
 .fxaAccountBoxButtons > button:first-child {
-  margin-right: 14px !important;
+  margin-inline-end: 14px !important;
 }
 
 .fxaSyncIllustration {
   width: 231px;
   list-style-image: url(chrome://browser/skin/fxa/sync-illustration.png)
 }
 
 #fxaLoginStatus[hasName] #fxaEmailAddress1 {
--- a/browser/themes/shared/tabs.inc.css
+++ b/browser/themes/shared/tabs.inc.css
@@ -186,17 +186,17 @@
 }
 
 .tab-label {
   margin-inline-end: 0;
   margin-inline-start: 0;
 }
 
 .tab-close-button {
-  margin-inline-start: 4px;
+  margin-inline-start: 2px;
   margin-inline-end: -2px;
   padding: 0;
 }
 
 .tab-icon-sound {
   margin-inline-start: 4px;
   width: 16px;
   height: 16px;
--- a/build/clang-plugin/clang-plugin.cpp
+++ b/build/clang-plugin/clang-plugin.cpp
@@ -846,17 +846,18 @@ AST_MATCHER(BinaryOperator, isInSystemHe
   return ASTIsInSystemHeader(Finder->getASTContext(), Node);
 }
 
 /// This matcher will match a list of files.  These files contain
 /// known NaN-testing expressions which we would like to whitelist.
 AST_MATCHER(BinaryOperator, isInWhitelistForNaNExpr) {
   const char* whitelist[] = {
     "SkScalar.h",
-    "json_writer.cpp"
+    "json_writer.cpp",
+    "State.cpp"
   };
 
   SourceLocation Loc = Node.getOperatorLoc();
   auto &SourceManager = Finder->getASTContext().getSourceManager();
   SmallString<1024> FileName = SourceManager.getFilename(Loc);
 
   for (auto itr = std::begin(whitelist); itr != std::end(whitelist); itr++) {
     if (llvm::sys::path::rbegin(FileName)->equals(*itr)) {
--- a/build/compare-mozconfig/compare-mozconfigs-wrapper.py
+++ b/build/compare-mozconfig/compare-mozconfigs-wrapper.py
@@ -14,18 +14,17 @@ import unittest
 from os import path
 from buildconfig import substs
 
 log = logging.getLogger(__name__)
 
 def determine_platform():
     platform_mapping = {'WINNT': {'x86_64': 'win64',
                                   'i686': 'win32'},
-                        'Darwin': {'x86_64': 'macosx-universal',
-                                   'i386':'macosx-universal'},
+                        'Darwin': {'x86_64': 'macosx64'},
                         'Linux': {'x86_64': 'linux64',
                                   'i686': 'linux32'}}
 
     os_type = substs['OS_TARGET']
     cpu_type = substs['TARGET_CPU']
     return platform_mapping.get(os_type, {}).get(cpu_type, None)
 
 
--- a/build/macosx/cross-mozconfig.common
+++ b/build/macosx/cross-mozconfig.common
@@ -10,16 +10,22 @@ if [ "x$IS_NIGHTLY" = "xyes" ]; then
   MOZ_AUTOMATION_SDK=${MOZ_AUTOMATION_SDK-1}
 fi
 . "$topsrcdir/build/mozconfig.common"
 
 # ld needs libLTO.so from llvm
 mk_add_options "export LD_LIBRARY_PATH=$topsrcdir/clang/lib"
 
 CROSS_CCTOOLS_PATH=$topsrcdir/cctools
+# This SDK was copied from a local XCode install and uploaded to tooltool.
+# Generate the tarball by running this command with the proper SDK version:
+#   sdk_path=$(xcrun --sdk macosx10.12 --show-sdk-path)
+#   tar -C $(dirname ${sdk_path}) -cHjf /tmp/$(basename ${sdk_path}).tar.bz2 $(basename ${sdk_path})
+# Upload the resulting tarball from /tmp to tooltool, and change the entry in
+# `browser/config/tooltool-manifests/macosx64/cross-releng.manifest`.
 CROSS_SYSROOT=$topsrcdir/MacOSX10.7.sdk
 CROSS_PRIVATE_FRAMEWORKS=$CROSS_SYSROOT/System/Library/PrivateFrameworks
 FLAGS="-target x86_64-apple-darwin10 -mlinker-version=136 -B $CROSS_CCTOOLS_PATH/bin -isysroot $CROSS_SYSROOT"
 
 export CC="$topsrcdir/clang/bin/clang $FLAGS"
 export CXX="$topsrcdir/clang/bin/clang++ $FLAGS"
 export CPP="$topsrcdir/clang/bin/clang $FLAGS -E"
 export LLVMCONFIG=$topsrcdir/clang/bin/llvm-config
--- a/caps/BasePrincipal.cpp
+++ b/caps/BasePrincipal.cpp
@@ -346,16 +346,29 @@ OriginAttributes::IsFirstPartyEnabled()
   if (!sCachedFirstPartyPref) {
     sCachedFirstPartyPref = true;
     Preferences::AddBoolVarCache(&sFirstPartyIsolation, "privacy.firstparty.isolate");
   }
 
   return sFirstPartyIsolation;
 }
 
+/* static */
+bool
+OriginAttributes::IsPrivateBrowsing(const nsACString& aOrigin)
+{
+  nsAutoCString dummy;
+  PrincipalOriginAttributes attrs;
+  if (NS_WARN_IF(!attrs.PopulateFromOrigin(aOrigin, dummy))) {
+    return false;
+  }
+
+  return !!attrs.mPrivateBrowsingId;
+}
+
 BasePrincipal::BasePrincipal()
 {}
 
 BasePrincipal::~BasePrincipal()
 {}
 
 NS_IMETHODIMP
 BasePrincipal::GetOrigin(nsACString& aOrigin)
--- a/caps/BasePrincipal.h
+++ b/caps/BasePrincipal.h
@@ -63,16 +63,20 @@ public:
   // flags. Once all other flags are removed, this can be removed too.
   void SyncAttributesWithPrivateBrowsing(bool aInPrivateBrowsing);
 
   void SetFromGenericAttributes(const GenericOriginAttributes& aAttrs);
 
   // check if "privacy.firstparty.isolate" is enabled.
   static bool IsFirstPartyEnabled();
 
+  // returns true if the originAttributes suffix has mPrivateBrowsingId value
+  // different than 0.
+  static bool IsPrivateBrowsing(const nsACString& aOrigin);
+
 protected:
   OriginAttributes() {}
   explicit OriginAttributes(const OriginAttributesDictionary& aOther)
     : OriginAttributesDictionary(aOther) {}
 };
 
 class PrincipalOriginAttributes;
 class DocShellOriginAttributes;
--- a/caps/nsPrincipal.cpp
+++ b/caps/nsPrincipal.cpp
@@ -69,17 +69,17 @@ nsPrincipal::InitializeStatics()
 
 nsPrincipal::nsPrincipal()
   : mCodebaseImmutable(false)
   , mDomainImmutable(false)
   , mInitialized(false)
 { }
 
 nsPrincipal::~nsPrincipal()
-{ 
+{
   // let's clear the principal within the csp to avoid a tangling pointer
   if (mCSP) {
     static_cast<nsCSPContext*>(mCSP.get())->clearLoadingPrincipal();
   }
 }
 
 nsresult
 nsPrincipal::Init(nsIURI *aCodebase, const PrincipalOriginAttributes& aOriginAttributes)
@@ -156,30 +156,41 @@ nsPrincipal::GetOriginInternal(nsACStrin
     return NS_OK;
   }
 
   if (NS_SUCCEEDED(rv) && !isChrome) {
     rv = origin->GetScheme(aOrigin);
     NS_ENSURE_SUCCESS(rv, rv);
     aOrigin.AppendLiteral("://");
     aOrigin.Append(hostPort);
+    return NS_OK;
   }
-  else {
-    // If we reached this branch, we can only create an origin if we have a nsIStandardURL.
-    // So, we query to a nsIStandardURL, and fail if we aren't an instance of an nsIStandardURL
-    // nsIStandardURLs have the good property of escaping the '^' character in their specs,
-    // which means that we can be sure that the caret character (which is reserved for delimiting
-    // the end of the spec, and the beginning of the origin attributes) is not present in the
-    // origin string
-    nsCOMPtr<nsIStandardURL> standardURL = do_QueryInterface(origin);
-    NS_ENSURE_TRUE(standardURL, NS_ERROR_FAILURE);
-    rv = origin->GetAsciiSpec(aOrigin);
-    NS_ENSURE_SUCCESS(rv, rv);
+
+  // This URL can be a blobURL. In this case, we should use the 'parent'
+  // principal instead.
+  nsCOMPtr<nsIURIWithPrincipal> uriWithPrincipal = do_QueryInterface(origin);
+  if (uriWithPrincipal) {
+    nsCOMPtr<nsIPrincipal> uriPrincipal;
+    if (uriWithPrincipal) {
+      return uriPrincipal->GetOriginNoSuffix(aOrigin);
+    }
   }
 
+  // If we reached this branch, we can only create an origin if we have a
+  // nsIStandardURL.  So, we query to a nsIStandardURL, and fail if we aren't
+  // an instance of an nsIStandardURL nsIStandardURLs have the good property
+  // of escaping the '^' character in their specs, which means that we can be
+  // sure that the caret character (which is reserved for delimiting the end
+  // of the spec, and the beginning of the origin attributes) is not present
+  // in the origin string
+  nsCOMPtr<nsIStandardURL> standardURL = do_QueryInterface(origin);
+  NS_ENSURE_TRUE(standardURL, NS_ERROR_FAILURE);
+  rv = origin->GetAsciiSpec(aOrigin);
+  NS_ENSURE_SUCCESS(rv, rv);
+
   return NS_OK;
 }
 
 bool
 nsPrincipal::SubsumesInternal(nsIPrincipal* aOther,
                               BasePrincipal::DocumentDomainConsideration aConsideration)
 {
   MOZ_ASSERT(aOther);
--- a/devtools/client/animationinspector/test/browser_animation_animated_properties_displayed.js
+++ b/devtools/client/animationinspector/test/browser_animation_animated_properties_displayed.js
@@ -77,15 +77,15 @@ function hasExpectedProperties(container
 
   return true;
 }
 
 function hasExpectedWarnings(containerEl) {
   let warnings = [...containerEl.querySelectorAll(".warning")];
   for (let warning of warnings) {
     let warningID =
-      "CompositorAnimationWarningTransformWithGeometricProperties";
+      "CompositorAnimationWarningTransformWithSyncGeometricAnimations";
     if (warning.getAttribute("title") == LAYOUT_ERRORS_L10N.getStr(warningID)) {
       return true;
     }
   }
   return false;
 }
--- a/devtools/client/animationinspector/test/browser_animation_running_on_compositor.js
+++ b/devtools/client/animationinspector/test/browser_animation_running_on_compositor.js
@@ -10,17 +10,17 @@ requestLongerTimeout(2);
 // compositor, they get a special icon and information in the tooltip.
 
 add_task(function* () {
   yield addTab(URL_ROOT + "doc_simple_animation.html");
   let {inspector, panel} = yield openAnimationInspector();
   let timeline = panel.animationsTimelineComponent;
 
   info("Select a test node we know has an animation running on the compositor");
-  yield selectNodeAndWaitForAnimations(".animated", inspector);
+  yield selectNodeAndWaitForAnimations(".compositor-all", inspector);
 
   let animationEl = timeline.animationsEl.querySelector(".animation");
   ok(animationEl.classList.contains("fast-track"),
      "The animation element has the fast-track css class");
   ok(hasTooltip(animationEl,
                 ANIMATION_L10N.getStr("player.allPropertiesOnCompositorTooltip")),
      "The animation element has the right tooltip content");
 
--- a/devtools/client/animationinspector/test/doc_simple_animation.html
+++ b/devtools/client/animationinspector/test/doc_simple_animation.html
@@ -77,16 +77,20 @@
     .no-compositor {
       top: 0;
       right: 10px;
       background: gold;
 
       animation: no-compositor 10s cubic-bezier(.57,-0.02,1,.31) forwards;
     }
 
+    .compositor-all {
+      animation: compositor-all 2s infinite;
+    }
+
     .compositor-notall {
       animation: compositor-notall 2s infinite;
     }
 
     @keyframes simple-animation {
       100% {
         transform: translateX(300px);
       }
@@ -99,16 +103,20 @@
     }
 
     @keyframes no-compositor {
       100% {
         margin-right: 600px;
       }
     }
 
+    @keyframes compositor-all {
+      to { opacity: 0.5 }
+    }
+
     @keyframes compositor-notall {
       from {
         opacity: 0;
         width: 0px;
         transform: translate(0px);
       }
       to {
         opacity: 1;
@@ -125,16 +133,17 @@
   <div class="ball multi"></div>
   <div class="ball delayed"></div>
   <div class="ball multi-finite"></div>
   <div class="ball short"></div>
   <div class="ball long"></div>
   <div class="ball negative-delay"></div>
   <div class="ball no-compositor"></div>
   <div class="ball" id="endDelayed"></div>
+  <div class="ball compositor-all"></div>
   <div class="ball compositor-notall"></div>
   <script>
     /* globals KeyframeEffect, Animation */
     "use strict";
 
     var el = document.getElementById("endDelayed");
     let effect = new KeyframeEffect(el, [
       { opacity: 0, offset: 0 },
--- a/devtools/client/framework/devtools.js
+++ b/devtools/client/framework/devtools.js
@@ -23,17 +23,17 @@ const {Task} = require("devtools/shared/
 
 const FORBIDDEN_IDS = new Set(["toolbox", ""]);
 const MAX_ORDINAL = 99;
 
 /**
  * DevTools is a class that represents a set of developer tools, it holds a
  * set of tools and keeps track of open toolboxes in the browser.
  */
-this.DevTools = function DevTools() {
+function DevTools() {
   this._tools = new Map();     // Map<toolId, tool>
   this._themes = new Map();    // Map<themeId, theme>
   this._toolboxes = new Map(); // Map<target, toolbox>
   // List of toolboxes that are still in process of creation
   this._creatingToolboxes = new Map(); // Map<target, toolbox Promise>
 
   // destroy() is an observer's handler so we need to preserve context.
   this.destroy = this.destroy.bind(this);
--- a/devtools/client/framework/toolbox-highlighter-utils.js
+++ b/devtools/client/framework/toolbox-highlighter-utils.js
@@ -235,17 +235,17 @@ exports.getHighlighterUtils = function (
     if (isRemoteHighlightable()) {
       yield toolbox.highlighter.showBoxModel(nodeFront, options);
     } else {
       // If the target doesn't have the highlighter actor, revert to the
       // walker's highlight method, which draws a simple outline
       yield toolbox.walker.highlight(nodeFront);
     }
 
-    toolbox.emit("node-highlight", nodeFront, options.toSource());
+    toolbox.emit("node-highlight", nodeFront);
   });
 
   /**
    * This is a convenience method in case you don't have a nodeFront but a
    * valueGrip. This is often the case with VariablesView properties.
    * This method will simply translate the grip into a nodeFront and call
    * highlightNodeFront, so it has the same signature.
    * @see highlightNodeFront
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/bin/dev-server.js
@@ -0,0 +1,65 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=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/. */
+
+/* global __dirname */
+
+"use strict";
+
+const toolbox = require("../node_modules/devtools-local-toolbox/index");
+const feature = require("devtools-config");
+const envConfig = require("../configs/development.json");
+
+const fs = require("fs");
+const path = require("path");
+
+feature.setConfig(envConfig);
+const webpackConfig = require("../webpack.config")(envConfig);
+
+let {app} = toolbox.startDevServer(envConfig, webpackConfig);
+
+function sendFile(res, src, encoding) {
+  const filePath = path.join(__dirname, src);
+  const file = encoding ? fs.readFileSync(filePath, encoding) : fs.readFileSync(filePath);
+  res.send(file);
+}
+
+function addFileRoute(from, to) {
+  app.get(from, function (req, res) {
+    sendFile(res, to, "utf-8");
+  });
+}
+
+// Routes
+addFileRoute("/", "../inspector.xhtml");
+addFileRoute("/markup/markup.xhtml", "../markup/markup.xhtml");
+
+app.get("/devtools/skin/images/:file.png", function (req, res) {
+  res.contentType("image/png");
+  sendFile(res, "../../themes/images/" + req.params.file + ".png");
+});
+
+app.get("/devtools/skin/images/:file.svg", function (req, res) {
+  res.contentType("image/svg+xml");
+  sendFile(res, "../../themes/images/" + req.params.file + ".svg", "utf-8");
+});
+
+app.get("/images/:file.svg", function (req, res) {
+  res.contentType("image/svg+xml");
+  sendFile(res, "../../themes/images/" + req.params.file + ".svg", "utf-8");
+});
+
+// Redirect chrome:devtools/skin/file.css to ../../themes/file.css
+app.get("/devtools/skin/:file.css", function (req, res) {
+  res.contentType("text/css; charset=utf-8");
+  sendFile(res, "../../themes/" + req.params.file + ".css", "utf-8");
+});
+
+// Redirect chrome:devtools/client/path/to/file.css to ../../path/to/file.css
+//      and chrome:devtools/content/path/to/file.css to ../../path/to/file.css
+app.get(/^\/devtools\/(?:client|content)\/(.*)\.css$/, function (req, res) {
+  res.contentType("text/css; charset=utf-8");
+  sendFile(res, "../../" + req.params[0] + ".css");
+});
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/configs/development.json
@@ -0,0 +1,22 @@
+{
+  "title": "Inspector",
+  "environment": "development",
+  "baseWorkerURL": "public/build/",
+  "theme": "light",
+  "host": "",
+  "logging": {
+    "client": false,
+    "firefoxProxy": false
+  },
+  "features": {
+  },
+  "firefox": {
+    "proxyHost": "localhost:9000",
+    "webSocketConnection": false,
+    "webSocketHost": "localhost:6080"
+  },
+  "development": {
+    "serverPort": 8000,
+    "customIndex": true
+  }
+}
\ No newline at end of file
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -1,20 +1,18 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=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/. */
 
-/* global window */
+/* global window, BrowserLoader */
 
 "use strict";
 
-var Cu = Components.utils;
-var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
 var Services = require("Services");
 var promise = require("promise");
 var defer = require("devtools/shared/defer");
 var EventEmitter = require("devtools/shared/event-emitter");
 const {executeSoon} = require("devtools/shared/DevToolsUtils");
 var {KeyShortcuts} = require("devtools/client/shared/key-shortcuts");
 var {Task} = require("devtools/shared/task");
 const {initCssProperties} = require("devtools/shared/fronts/css-properties");
@@ -1349,25 +1347,25 @@ Inspector.prototype = {
   _initMarkup: function () {
     let doc = this.panelDoc;
 
     this._markupBox = doc.getElementById("markup-box");
 
     // create tool iframe
     this._markupFrame = doc.createElement("iframe");
     this._markupFrame.setAttribute("flex", "1");
+    // This is needed to enable tooltips inside the iframe document.
     this._markupFrame.setAttribute("tooltip", "aHTMLTooltip");
     this._markupFrame.addEventListener("contextmenu", this._onContextMenu);
 
-    // This is needed to enable tooltips inside the iframe document.
-    this._markupFrame.addEventListener("load", this._onMarkupFrameLoad, true);
-
     this._markupBox.setAttribute("collapsed", true);
     this._markupBox.appendChild(this._markupFrame);
-    this._markupFrame.setAttribute("src", "chrome://devtools/content/inspector/markup/markup.xhtml");
+
+    this._markupFrame.addEventListener("load", this._onMarkupFrameLoad, true);
+    this._markupFrame.setAttribute("src", "markup/markup.xhtml");
     this._markupFrame.setAttribute("aria-label",
       INSPECTOR_L10N.getStr("inspector.panelLabel.markupView"));
   },
 
   _onMarkupFrameLoad: function () {
     this._markupFrame.removeEventListener("load", this._onMarkupFrameLoad, true);
 
     this._markupFrame.contentWindow.focus();
@@ -1886,94 +1884,116 @@ Inspector.prototype = {
     // When the inspector menu was setup on click (see _getNodeLinkMenuItems), we
     // already checked that resolveRelativeURL existed.
     this.inspector.resolveRelativeURL(link, this.selection.nodeFront).then(url => {
       clipboardHelper.copyString(url);
     }, console.error);
   }
 };
 
+/**
+ * Create a fake toolbox when running the inspector standalone, either in a chrome tab or
+ * in a content tab.
+ *
+ * @param {Target} target to debug
+ * @param {Function} createThreadClient
+ *        When supported the thread client needs a reference to the toolbox.
+ *        This callback will be called right after the toolbox object is created.
+ * @param {Object} dependencies
+ *        - react
+ *        - reactDOM
+ *        - browserRequire
+ */
+const buildFakeToolbox = Task.async(function* (
+  target, createThreadClient, {
+    React,
+    ReactDOM,
+    browserRequire
+  }) {
+  const { InspectorFront } = require("devtools/shared/fronts/inspector");
+  const { Selection } = require("devtools/client/framework/selection");
+  const { getHighlighterUtils } = require("devtools/client/framework/toolbox-highlighter-utils");
+
+  let notImplemented = function () {
+    throw new Error("Not implemented in a tab");
+  };
+  let fakeToolbox = {
+    target,
+    hostType: "bottom",
+    doc: window.document,
+    win: window,
+    on() {}, emit() {}, off() {},
+    initInspector() {},
+    browserRequire,
+    React,
+    ReactDOM,
+    isToolRegistered() {
+      return false;
+    },
+    currentToolId: "inspector",
+    getCurrentPanel() {
+      return "inspector";
+    },
+    get textboxContextMenuPopup() {
+      notImplemented();
+    },
+    getPanel: notImplemented,
+    openSplitConsole: notImplemented,
+    viewCssSourceInStyleEditor: notImplemented,
+    viewJsSourceInDebugger: notImplemented,
+    viewSource: notImplemented,
+    viewSourceInDebugger: notImplemented,
+    viewSourceInStyleEditor: notImplemented,
+
+    // For attachThread:
+    highlightTool() {},
+    unhighlightTool() {},
+    selectTool() {},
+    raise() {},
+    getNotificationBox() {}
+  };
+
+  fakeToolbox.threadClient = yield createThreadClient(fakeToolbox);
+
+  let inspector = InspectorFront(target.client, target.form);
+  let showAllAnonymousContent =
+    Services.prefs.getBoolPref("devtools.inspector.showAllAnonymousContent");
+  let walker = yield inspector.getWalker({ showAllAnonymousContent });
+  let selection = new Selection(walker);
+  let highlighter = yield inspector.getHighlighter(false);
+  fakeToolbox.highlighterUtils = getHighlighterUtils(fakeToolbox);
+
+  fakeToolbox.inspector = inspector;
+  fakeToolbox.walker = walker;
+  fakeToolbox.selection = selection;
+  fakeToolbox.highlighter = highlighter;
+  return fakeToolbox;
+});
+
 // URL constructor doesn't support chrome: scheme
 let href = window.location.href.replace(/chrome:/, "http://");
 let url = new window.URL(href);
 
-// Only use this method to attach the toolbox if some query parameters are given
-if (url.search.length > 1) {
+// If query parameters are given in a chrome tab, the inspector is running in standalone.
+if (window.location.protocol === "chrome:" && url.search.length > 1) {
   const { targetFromURL } = require("devtools/client/framework/target-from-url");
   const { attachThread } = require("devtools/client/framework/attach-thread");
-  const { BrowserLoader } =
-    Cu.import("resource://devtools/client/shared/browser-loader.js", {});
 
-  const { Selection } = require("devtools/client/framework/selection");
-  const { InspectorFront } = require("devtools/shared/fronts/inspector");
-  const { getHighlighterUtils } = require("devtools/client/framework/toolbox-highlighter-utils");
+  const browserRequire = BrowserLoader({ window, useOnlyShared: true }).require;
+  const React = browserRequire("devtools/client/shared/vendor/react");
+  const ReactDOM = browserRequire("devtools/client/shared/vendor/react-dom");
 
   Task.spawn(function* () {
     let target = yield targetFromURL(url);
-
-    let notImplemented = function () {
-      throw new Error("Not implemented in a tab");
-    };
-    let fakeToolbox = {
+    let fakeToolbox = yield buildFakeToolbox(
       target,
-      hostType: "bottom",
-      doc: window.document,
-      win: window,
-      on() {}, emit() {}, off() {},
-      initInspector() {},
-      browserRequire: BrowserLoader({
-        window: window,
-        useOnlyShared: true
-      }).require,
-      get React() {
-        return this.browserRequire("devtools/client/shared/vendor/react");
-      },
-      get ReactDOM() {
-        return this.browserRequire("devtools/client/shared/vendor/react-dom");
-      },
-      isToolRegistered() {
-        return false;
-      },
-      currentToolId: "inspector",
-      getCurrentPanel() {
-        return "inspector";
-      },
-      get textboxContextMenuPopup() {
-        notImplemented();
-      },
-      getPanel: notImplemented,
-      openSplitConsole: notImplemented,
-      viewCssSourceInStyleEditor: notImplemented,
-      viewJsSourceInDebugger: notImplemented,
-      viewSource: notImplemented,
-      viewSourceInDebugger: notImplemented,
-      viewSourceInStyleEditor: notImplemented,
-
-      // For attachThread:
-      highlightTool() {},
-      unhighlightTool() {},
-      selectTool() {},
-      raise() {},
-      getNotificationBox() {}
-    };
-
-    // attachThread also expect a toolbox as argument
-    fakeToolbox.threadClient = yield attachThread(fakeToolbox);
-
-    let inspector = InspectorFront(target.client, target.form);
-    let showAllAnonymousContent =
-      Services.prefs.getBoolPref("devtools.inspector.showAllAnonymousContent");
-    let walker = yield inspector.getWalker({ showAllAnonymousContent });
-    let selection = new Selection(walker);
-    let highlighter = yield inspector.getHighlighter(false);
-
-    fakeToolbox.inspector = inspector;
-    fakeToolbox.walker = walker;
-    fakeToolbox.selection = selection;
-    fakeToolbox.highlighter = highlighter;
-    fakeToolbox.highlighterUtils = getHighlighterUtils(fakeToolbox);
-
+      (toolbox) => attachThread(toolbox),
+      { React, ReactDOM, browserRequire }
+    );
     let inspectorUI = new Inspector(fakeToolbox);
     inspectorUI.init();
   }).then(null, e => {
     window.alert("Unable to start the inspector:" + e.message + "\n" + e.stack);
   });
 }
+
+exports.Inspector = Inspector;
+exports.buildFakeToolbox = buildFakeToolbox;
--- a/devtools/client/inspector/inspector.xhtml
+++ b/devtools/client/inspector/inspector.xhtml
@@ -21,16 +21,28 @@
   <link rel="stylesheet" href="resource://devtools/client/shared/components/tabs/tabs.css"/>
   <link rel="stylesheet" href="resource://devtools/client/shared/components/tabs/tabbar.css"/>
   <link rel="stylesheet" href="resource://devtools/client/inspector/components/inspector-tab-panel.css"/>
   <link rel="stylesheet" href="resource://devtools/client/shared/components/splitter/split-box.css"/>
   <link rel="stylesheet" href="resource://devtools/client/inspector/layout/components/Accordion.css"/>
 
   <script type="application/javascript;version=1.8"
           src="chrome://devtools/content/shared/theme-switching.js"></script>
+  <script type="text/javascript">
+    /* eslint-disable */
+    var isInChrome = window.location.href.includes("chrome:");
+    if (isInChrome) {
+      var exports = {};
+      var Cu = Components.utils;
+      var { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
+      var { BrowserLoader } = Cu.import("resource://devtools/client/shared/browser-loader.js", {});
+    }
+  </script>
+
+  <!-- in content, inspector.js is mapped to the dynamically generated webpack bundle -->
   <script type="application/javascript;version=1.8" src="inspector.js" defer="true"></script>
 </head>
 <body class="theme-body" role="application">
   <div class="inspector-responsive-container theme-body inspector">
 
     <!-- Main Panel Content -->
     <div id="inspector-main-content" class="devtools-main-content">
       <div id="inspector-toolbar" class="devtools-toolbar" nowindowdrag="true"
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/local-toolbox.js
@@ -0,0 +1,121 @@
+/* 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/. */
+
+/* global window, document */
+
+"use strict";
+
+const React = require("devtools/client/shared/vendor/react");
+const ReactDOM = require("devtools/client/shared/vendor/react-dom");
+const { appinfo } = require("Services");
+
+const { buildFakeToolbox, Inspector } = require("./inspector");
+
+function onConnect(arg) {
+  if (!arg || !arg.client) {
+    return;
+  }
+
+  let client = arg.client;
+
+  const tabTarget = client.getTabTarget();
+  let threadClient = { paused: false };
+  buildFakeToolbox(
+    tabTarget,
+    () => threadClient,
+    { React, ReactDOM, browserRequire: () => {} }
+  ).then(function (fakeToolbox) {
+    let inspector = new Inspector(fakeToolbox);
+    inspector.init();
+  });
+}
+
+/**
+ * Stylesheet links in devtools xhtml files are using chrome or resource URLs.
+ * Rewrite the href attribute to remove the protocol. web-server.js contains redirects
+ * to map CSS urls to the proper file. Supports urls using:
+ *   - devtools/client/
+ *   - devtools/content/
+ *   - skin/
+ * The css for the light-theme will additionally be loaded.
+ * Will also add mandatory classnames and attributes to be compatible with devtools theme
+ * stylesheet.
+ *
+ */
+function fixStylesheets(doc) {
+  let links = doc.head.querySelectorAll("link");
+  for (let link of links) {
+    link.href = link.href.replace(/(resource|chrome)\:\/\//, "/");
+  }
+
+  // Add the light theme stylesheet to compensate for the missing theme-switching.js
+  let themeLink = doc.createElement("link");
+  themeLink.setAttribute("rel", "stylesheet");
+  themeLink.setAttribute("href", "/devtools/skin/light-theme.css");
+
+  doc.head.appendChild(themeLink);
+  doc.documentElement.classList.add("theme-light");
+  doc.body.classList.add("theme-light");
+
+  if (appinfo.OS === "Darwin") {
+    doc.documentElement.setAttribute("platform", "mac");
+  } else if (appinfo.OS === "Linux") {
+    doc.documentElement.setAttribute("platform", "linux");
+  } else {
+    doc.documentElement.setAttribute("platform", "win");
+  }
+}
+
+/**
+ * Called each time a childList mutation is received on the main document.
+ * Check the iframes currently loaded in the document and call fixStylesheets if needed.
+ */
+function fixStylesheetsOnMutation() {
+  let frames = document.body.querySelectorAll("iframe");
+  for (let frame of frames) {
+    let doc = frame.contentDocument || frame.contentWindow.document;
+    if (doc.__fixStylesheetsFlag) {
+      continue;
+    }
+
+    // Mark the document as processed to avoid extra changes.
+    doc.__fixStylesheetsFlag = true;
+    if (doc.readyState !== "complete") {
+      // If the document is not loaded yet, wait for DOMContentLoaded.
+      frame.contentWindow.addEventListener("DOMContentLoaded", () => {
+        fixStylesheets(doc);
+      }, { once: true });
+    } else {
+      fixStylesheets(doc);
+    }
+  }
+}
+
+window.addEventListener("DOMContentLoaded", function onInspectorDOMLoaded() {
+  window.removeEventListener("DOMContentLoaded", onInspectorDOMLoaded);
+
+  // Add styling for the main document.
+  fixStylesheets(document);
+
+  // Add a mutation observer to check if new iframes have been loaded and need to have
+  //  their stylesheet links updated.
+  new window.MutationObserver(mutations => {
+    fixStylesheetsOnMutation();
+  }).observe(document.body, { childList: true, subtree: true });
+
+  const hasFirefoxTabParam = window.location.href.indexOf("firefox-tab") != -1;
+  if (!hasFirefoxTabParam) {
+    const inspectorRoot = document.querySelector(".inspector");
+    // Remove the inspector specific markup and add the landing page root element.
+    inspectorRoot.remove();
+    let mount = document.createElement("div");
+    mount.setAttribute("id", "mount");
+    document.body.appendChild(mount);
+  }
+
+  // Toolbox tries to add a theme classname on the documentElement and should only be
+  // required after DOMContentLoaded.
+  const { bootstrap } = require("devtools-local-toolbox");
+  bootstrap(React, ReactDOM).then(onConnect);
+});
--- a/devtools/client/inspector/markup/markup.xhtml
+++ b/devtools/client/inspector/markup/markup.xhtml
@@ -11,17 +11,16 @@
   <link rel="stylesheet" href="chrome://devtools/content/sourceeditor/codemirror/lib/codemirror.css" type="text/css"/>
   <link rel="stylesheet" href="chrome://devtools/content/sourceeditor/codemirror/addon/dialog/dialog.css" type="text/css"/>
   <link rel="stylesheet" href="chrome://devtools/content/sourceeditor/codemirror/mozilla.css" type="text/css"/>
 
   <script type="application/javascript;version=1.8"
           src="chrome://devtools/content/shared/theme-switching.js"></script>
   <script type="application/javascript;version=1.8"
           src="chrome://devtools/content/sourceeditor/codemirror/codemirror.bundle.js"></script>
-
 </head>
 <body class="theme-body devtools-monospace" role="application">
 
 <!-- NOTE THAT WE MAKE EXTENSIVE USE OF HTML COMMENTS IN THIS FILE IN ORDER -->
 <!-- TO MAKE SPANS READABLE WHILST AVOIDING SIGNIFICANT WHITESPACE          -->
 
   <div id="root-wrapper" role="presentation">
     <div id="root" role="presentation"></div>
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/package.json
@@ -0,0 +1,17 @@
+{
+  "name": "inspector.html",
+  "version": "0.0.1",
+  "description": "The Firefox Inspector",
+  "scripts": {
+    "start": "node bin/dev-server"
+  },
+  "author": "",
+  "dependencies": {
+    "devtools-local-toolbox": "0.0.10",
+    "devtools-modules": "0.0.9",
+    "devtools-sham-modules": "0.0.9",
+    "devtools-config": "0.0.9",
+    "raw-loader": "^0.5.1",
+    "json-loader": "^0.5.4"
+  }
+}
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/webpack.config.js
@@ -0,0 +1,136 @@
+/* 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/. */
+
+/* global __dirname */
+
+"use strict";
+
+const {toolboxConfig} = require("devtools-local-toolbox/index");
+
+const path = require("path");
+const webpack = require("webpack");
+
+module.exports = envConfig => {
+  let webpackConfig = {
+    bail: true,
+    entry: [
+      path.join(__dirname, "local-toolbox.js")
+    ],
+    output: {
+      path: path.join(__dirname, "assets/build"),
+      filename: "inspector.js",
+      publicPath: "/"
+    },
+    module: {
+      // Disable handling of unknown requires
+      unknownContextRegExp: /$^/,
+      unknownContextCritical: false,
+
+      // Disable handling of requires with a single expression
+      exprContextRegExp: /$^/,
+      exprContextCritical: false,
+
+      // Warn for every expression in require
+      wrappedContextCritical: true,
+
+      loaders: [
+        {
+          test: /event-emitter/,
+          exclude: /node_modules/,
+          loaders: [path.join(__dirname, "./webpack/rewrite-event-emitter")],
+        }, {
+          // Replace all references to this.browserRequire() by require() in
+          // client/inspector/*.js files
+          test: /client\/inspector\/.*\.js$/,
+          loaders: [path.join(__dirname, "./webpack/rewrite-browser-require")],
+        }
+      ]
+    },
+    resolveLoader: {
+      root: [
+        path.resolve("./node_modules"),
+        path.resolve("./webpack"),
+      ]
+    },
+    resolve: {
+      alias: {
+        "acorn/util/walk": path.join(__dirname, "../../shared/acorn/walk"),
+        "acorn": path.join(__dirname, "../../shared/acorn"),
+        "devtools/client/framework/about-devtools-toolbox":
+          path.join(__dirname, "./webpack/about-devtools-sham.js"),
+        "devtools/client/framework/attach-thread":
+          path.join(__dirname, "./webpack/attach-thread-sham.js"),
+        "devtools/client/framework/target-from-url":
+          path.join(__dirname, "./webpack/target-from-url-sham.js"),
+        "devtools/client/jsonview/main":
+          path.join(__dirname, "./webpack/jsonview-sham.js"),
+        "devtools/client/sourceeditor/editor":
+          path.join(__dirname, "./webpack/editor-sham.js"),
+        "devtools/client/locales": path.join(__dirname, "../locales/en-US"),
+        "devtools/shared/DevToolsUtils":
+          path.join(__dirname, "./webpack/devtools-utils-sham.js"),
+        "devtools/shared/locales": path.join(__dirname, "../../shared/locales/en-US"),
+        "devtools/shared/platform": path.join(__dirname, "../../shared/platform/content"),
+        "devtools": path.join(__dirname, "../../"),
+        "gcli": path.join(__dirname, "../../shared/gcli/source/lib/gcli"),
+        "method": path.join(__dirname, "../../../addon-sdk/source/lib/method"),
+        "modules/libpref/init/all":
+          path.join(__dirname, "../../../modules/libpref/init/all.js"),
+        "sdk/system/unload": path.join(__dirname, "./webpack/system-unload-sham.js"),
+        "sdk": path.join(__dirname, "../../../addon-sdk/source/lib/sdk"),
+        "Services": path.join(__dirname, "../shared/shim/Services.js"),
+        "toolkit/locales":
+          path.join(__dirname, "../../../toolkit/locales/en-US/chrome/global"),
+      },
+    },
+
+    plugins: [
+      new webpack.DefinePlugin({
+        "isWorker": JSON.stringify(false),
+        "reportError": "console.error",
+        "AppConstants": "{ DEBUG: true, DEBUG_JS_MODULES: true }",
+        "loader": `{
+                      lazyRequireGetter: () => {},
+                      lazyGetter: () => {}
+                    }`,
+        "dump": "console.log",
+      }),
+    ]
+  };
+
+  webpackConfig.externals = [
+    /codemirror\//,
+    {
+      "promise": "var Promise",
+      "devtools/server/main": "{}",
+
+      // Just trying to get build to work.  These should be removed eventually:
+      "chrome": "{}",
+
+      // In case you end up in chrome-land you can use this to help track down issues.
+      // SDK for instance does a bunch of this so if you somehow end up importing an SDK
+      // dependency this might help for debugging:
+      // "chrome": `{
+      //   Cc: {
+      //     "@mozilla.org/uuid-generator;1": { getService: () => { return {} } },
+      //     "@mozilla.org/observer-service;1": { getService: () => { return {} } },
+      //   },
+      //   Ci: {},
+      //   Cr: {},
+      //   Cm: {},
+      //   components: { classesByID: () => {} , ID: () => {} }
+      // }`,
+
+      "resource://gre/modules/XPCOMUtils.jsm": "{}",
+      "resource://devtools/client/styleeditor/StyleEditorUI.jsm": "{}",
+      "resource://devtools/client/styleeditor/StyleEditorUtil.jsm": "{}",
+      "devtools/client/shared/developer-toolbar": "{}",
+    },
+  ];
+
+  // Exclude all files from devtools/ or addon-sdk/ or modules/ .
+  webpackConfig.babelExcludes = /(devtools\/|addon-sdk\/|modules\/)/;
+
+  return toolboxConfig(webpackConfig, envConfig);
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/webpack/about-devtools-sham.js
@@ -0,0 +1,12 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=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/. */
+
+"use strict";
+
+module.exports = {
+  register: () => {},
+  unregister: () => {},
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/webpack/attach-thread-sham.js
@@ -0,0 +1,11 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=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/. */
+
+"use strict";
+
+module.exports = {
+  attachThread: () => {},
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/webpack/devtools-utils-sham.js
@@ -0,0 +1,13 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=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/. */
+
+/* global setImmediate */
+
+"use strict";
+
+module.exports = {
+  executeSoon: setImmediate,
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/webpack/editor-sham.js
@@ -0,0 +1,13 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=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/. */
+
+"use strict";
+
+function Editor() {}
+Editor.modes = {};
+Editor.prototype.appendToLocalElement = () => {};
+
+module.exports = Editor;
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/webpack/jsonview-sham.js
@@ -0,0 +1,14 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=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/. */
+
+"use strict";
+
+module.exports = {
+  JsonView: {
+    initialize: () => {},
+    destroy: () => {},
+  }
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/webpack/prefs-loader.js
@@ -0,0 +1,71 @@
+/* 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/. */
+
+// Rewrite devtools.js or all.js, leaving just the relevant pref() calls.
+
+"use strict";
+
+const PREF_WHITELIST = [
+  "devtools",
+  "layout.css.grid.enabled"
+];
+
+const acceptLine = function (line) {
+  let matches = line.match(/^ *pref\("([^"]+)"/);
+  if (!matches || !matches[1]) {
+    return false;
+  }
+
+  let [, prefName] = matches;
+  return PREF_WHITELIST.some(filter => prefName.startsWith(filter));
+};
+
+module.exports = function (content) {
+  this.cacheable && this.cacheable();
+
+  // If we're reading devtools.js we have to do some preprocessing.
+  // If we're reading all.js we just assume we can dump all the
+  // conditionals.
+  let isDevtools = this.request.endsWith("/devtools.js");
+
+  // This maps the text of a "#if" to its truth value.  This has to
+  // cover all uses of #if in devtools.js.
+  const ifMap = {
+    "#if MOZ_UPDATE_CHANNEL == beta": false,
+    "#if defined(NIGHTLY_BUILD)": false,
+    "#ifdef NIGHTLY_BUILD": false,
+    "#ifdef MOZ_DEV_EDITION": false,
+    "#ifdef RELEASE_OR_BETA": true,
+    "#ifdef RELEASE_BUILD": true,
+  };
+
+  let lines = content.split("\n");
+  let ignoring = false;
+  let newLines = [];
+  let continuation = false;
+  for (let line of lines) {
+    if (line.startsWith("sticky_pref")) {
+      line = line.slice(7);
+    }
+
+    if (isDevtools) {
+      if (line.startsWith("#if")) {
+        if (!(line in ifMap)) {
+          throw new Error("missing line in ifMap: " + line);
+        }
+        ignoring = !ifMap[line];
+      } else if (line.startsWith("#else")) {
+        ignoring = !ignoring;
+      }
+    }
+
+    if (continuation || (!ignoring && acceptLine(line))) {
+      newLines.push(line);
+
+      // The call to pref(...); might span more than one line.
+      continuation = !/\);/.test(line);
+    }
+  }
+  return newLines.join("\n");
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/webpack/rewrite-browser-require.js
@@ -0,0 +1,12 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Replace all occurrences of "this.browserRequire(" by "require(".
+
+"use strict";
+
+module.exports = function (content) {
+  this.cacheable && this.cacheable();
+  return content.replace(/this\.browserRequire\(/g, "require(");
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/webpack/rewrite-event-emitter.js
@@ -0,0 +1,26 @@
+/* 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/. */
+
+// Remove the header code from event-emitter.js.  This code confuses
+// webpack.
+
+"use strict";
+
+module.exports = function (content) {
+  this.cacheable && this.cacheable();
+
+  let lines = content.split("\n");
+  let ignoring = false;
+  let newLines = [];
+  for (let line of lines) {
+    if (/function \(factory\)/.test(line)) {
+      ignoring = true;
+    } else if (/call\(this, function /.test(line)) {
+      ignoring = false;
+    } else if (!ignoring && line !== "});") {
+      newLines.push(line);
+    }
+  }
+  return newLines.join("\n");
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/webpack/system-unload-sham.js
@@ -0,0 +1,11 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=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/. */
+
+"use strict";
+
+module.exports = {
+  when: () => {},
+};
new file mode 100644
--- /dev/null
+++ b/devtools/client/inspector/webpack/target-from-url-sham.js
@@ -0,0 +1,11 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=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/. */
+
+"use strict";
+
+module.exports = {
+  targetFromURL: () => {},
+};
--- a/devtools/client/jsonview/converter-child.js
+++ b/devtools/client/jsonview/converter-child.js
@@ -1,17 +1,17 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=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/. */
 
 "use strict";
 
-const {Cu, Cc, Ci, components} = require("chrome");
+const {Cc, Ci, components} = require("chrome");
 const Services = require("Services");
 const {Class} = require("sdk/core/heritage");
 const {Unknown} = require("sdk/platform/xpcom");
 const xpcom = require("sdk/platform/xpcom");
 const Events = require("sdk/dom/events");
 const Clipboard = require("sdk/clipboard");
 
 loader.lazyRequireGetter(this, "NetworkHelper",
@@ -135,19 +135,18 @@ let Converter = Class({
           return undefined;
         }
       }
     };
 
     JsonViewUtils.exportIntoContentScope(win, Locale, "Locale");
 
     Events.once(win, "DOMContentLoaded", event => {
-      Cu.exportFunction(this.postChromeMessage.bind(this), win, {
-        defineAs: "postChromeMessage"
-      });
+      win.addEventListener("contentMessage",
+        this.onContentMessage.bind(this), false, true);
     });
 
     // The request doesn't have to be always nsIHttpChannel
     // (e.g. in case of data: URLs)
     if (request instanceof Ci.nsIHttpChannel) {
       request.visitResponseHeaders({
         visitHeader: function (name, value) {
           headers.response.push({name: name, value: value});
@@ -258,20 +257,25 @@ let Converter = Class({
       "<base href=\"" + this.htmlEncode(this.data.url()) + "\">" +
       "</head><body>" +
       output +
       "</body></html>";
   },
 
   // Chrome <-> Content communication
 
-  postChromeMessage: function (type, args, objects) {
-    let value = args;
+  onContentMessage: function (e) {
+    // Do not handle events from different documents.
+    let win = NetworkHelper.getWindowForRequest(this.channel);
+    if (win != e.target) {
+      return;
+    }
 
-    switch (type) {
+    let value = e.detail.value;
+    switch (e.detail.type) {
       case "copy":
         Clipboard.set(value, "text");
         break;
 
       case "copy-headers":
         this.copyHeaders(value);
         break;
 
--- a/devtools/client/jsonview/json-viewer.js
+++ b/devtools/client/jsonview/json-viewer.js
@@ -1,14 +1,13 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
 /* vim: set ft=javascript ts=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/. */
-/* global postChromeMessage */
 
 "use strict";
 
 define(function (require, exports, module) {
   const { render } = require("devtools/client/shared/vendor/react-dom");
   const { createFactories } = require("devtools/client/shared/components/reps/rep-utils");
   const { MainTabbedArea } = createFactories(require("./components/main-tabbed-area"));
 
@@ -37,27 +36,25 @@ define(function (require, exports, modul
   headers.remove();
 
   /**
    * Application actions/commands. This list implements all commands
    * available for the JSON viewer.
    */
   input.actions = {
     onCopyJson: function () {
-      let value = input.prettified ? input.jsonPretty : input.jsonText;
-      postChromeMessage("copy", value);
+      dispatchEvent("copy", input.prettified ? input.jsonPretty : input.jsonText);
     },
 
     onSaveJson: function () {
-      let value = input.prettified ? input.jsonPretty : input.jsonText;
-      postChromeMessage("save", value);
+      dispatchEvent("save", input.prettified ? input.jsonPretty : input.jsonText);
     },
 
     onCopyHeaders: function () {
-      postChromeMessage("copy-headers", input.headers);
+      dispatchEvent("copy-headers", input.headers);
     },
 
     onSearch: function (value) {
       theApp.setState({searchFilter: value});
     },
 
     onPrettify: function (data) {
       if (input.prettified) {
@@ -69,16 +66,34 @@ define(function (require, exports, modul
         theApp.setState({jsonText: input.jsonPretty});
       }
 
       input.prettified = !input.prettified;
     },
   };
 
   /**
+   * Helper for dispatching an event. It's handled in chrome scope.
+   *
+   * @param {String} type Event detail type
+   * @param {Object} value Event detail value
+   */
+  function dispatchEvent(type, value) {
+    let data = {
+      detail: {
+        type,
+        value,
+      }
+    };
+
+    let contentMessageEvent = new CustomEvent("contentMessage", data);
+    window.dispatchEvent(contentMessageEvent);
+  }
+
+  /**
    * Render the main application component. It's the main tab bar displayed
    * at the top of the window. This component also represents ReacJS root.
    */
   let content = document.getElementById("content");
   let theApp = render(MainTabbedArea(input), content);
 
   let onResize = event => {
     window.document.body.style.height = window.innerHeight + "px";
--- a/devtools/client/shared/shim/Services.js
+++ b/devtools/client/shared/shim/Services.js
@@ -601,16 +601,24 @@ const Services = {
       // sufficient for our purposes.
       return {
         openUILinkIn: function (url) {
           window.open(url, "_blank");
         },
       };
     },
   },
+
+  /**
+   * Shims for Services.obs.add/removeObserver.
+   */
+  obs: {
+    addObserver: () => {},
+    removeObserver: () => {},
+  },
 };
 
 /**
  * Create a new preference.  This is used during startup (see
  * devtools/client/preferences/devtools.js) to install the
  * default preferences.
  *
  * @param {String} name the name of the preference
--- a/devtools/client/shared/undo.js
+++ b/devtools/client/shared/undo.js
@@ -148,18 +148,24 @@ UndoStack.prototype = {
   /**
    * ViewController implementation for undo/redo.
    */
 
   /**
    * Install this object as a command controller.
    */
   installController: function (controllerWindow) {
+    let controllers = controllerWindow.controllers;
+    // Only available when running in a Firefox panel.
+    if (!controllers || !controllers.appendController) {
+      return;
+    }
+
     this._controllerWindow = controllerWindow;
-    controllerWindow.controllers.appendController(this);
+    controllers.appendController(this);
   },
 
   /**
    * Uninstall this object from the command controller.
    */
   uninstallController: function () {
     if (!this._controllerWindow) {
       return;
rename from devtools/client/package.json
rename to devtools/client/sourceeditor/package.json
--- a/devtools/client/package.json
+++ b/devtools/client/sourceeditor/package.json
@@ -1,10 +1,10 @@
 {
-  "name": "devtools",
+  "name": "sourceeditor",
   "version": "0.0.1",
   "description": "",
   "main": "",
   "scripts": {
     "build": "webpack"
   },
   "babel":  {
     "presets": [ "es2015" ]
rename from devtools/client/webpack.config.js
rename to devtools/client/sourceeditor/webpack.config.js
--- a/devtools/client/webpack.config.js
+++ b/devtools/client/sourceeditor/webpack.config.js
@@ -2,38 +2,38 @@
  * 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/. */
 
 "use strict";
 
 module.exports = [{
   bail: true,
   entry: [
-    "./sourceeditor/codemirror/addon/dialog/dialog.js",
-    "./sourceeditor/codemirror/addon/search/searchcursor.js",
-    "./sourceeditor/codemirror/addon/search/search.js",
-    "./sourceeditor/codemirror/addon/edit/matchbrackets.js",
-    "./sourceeditor/codemirror/addon/edit/closebrackets.js",
-    "./sourceeditor/codemirror/addon/comment/comment.js",
-    "./sourceeditor/codemirror/mode/javascript/javascript.js",
-    "./sourceeditor/codemirror/mode/xml/xml.js",
-    "./sourceeditor/codemirror/mode/css/css.js",
-    "./sourceeditor/codemirror/mode/htmlmixed/htmlmixed.js",
-    "./sourceeditor/codemirror/mode/clike/clike.js",
-    "./sourceeditor/codemirror/mode/wasm/wasm.js",
-    "./sourceeditor/codemirror/addon/selection/active-line.js",
-    "./sourceeditor/codemirror/addon/edit/trailingspace.js",
-    "./sourceeditor/codemirror/keymap/emacs.js",
-    "./sourceeditor/codemirror/keymap/vim.js",
-    "./sourceeditor/codemirror/keymap/sublime.js",
-    "./sourceeditor/codemirror/addon/fold/foldcode.js",
-    "./sourceeditor/codemirror/addon/fold/brace-fold.js",
-    "./sourceeditor/codemirror/addon/fold/comment-fold.js",
-    "./sourceeditor/codemirror/addon/fold/xml-fold.js",
-    "./sourceeditor/codemirror/addon/fold/foldgutter.js",
-    "./sourceeditor/codemirror/lib/codemirror.js",
+    "./codemirror/addon/dialog/dialog.js",
+    "./codemirror/addon/search/searchcursor.js",
+    "./codemirror/addon/search/search.js",
+    "./codemirror/addon/edit/matchbrackets.js",
+    "./codemirror/addon/edit/closebrackets.js",
+    "./codemirror/addon/comment/comment.js",
+    "./codemirror/mode/javascript/javascript.js",
+    "./codemirror/mode/xml/xml.js",
+    "./codemirror/mode/css/css.js",
+    "./codemirror/mode/htmlmixed/htmlmixed.js",
+    "./codemirror/mode/clike/clike.js",
+    "./codemirror/mode/wasm/wasm.js",
+    "./codemirror/addon/selection/active-line.js",
+    "./codemirror/addon/edit/trailingspace.js",
+    "./codemirror/keymap/emacs.js",
+    "./codemirror/keymap/vim.js",
+    "./codemirror/keymap/sublime.js",
+    "./codemirror/addon/fold/foldcode.js",
+    "./codemirror/addon/fold/brace-fold.js",
+    "./codemirror/addon/fold/comment-fold.js",
+    "./codemirror/addon/fold/xml-fold.js",
+    "./codemirror/addon/fold/foldgutter.js",
+    "./codemirror/lib/codemirror.js",
   ],
   output: {
-    filename: "./sourceeditor/codemirror/codemirror.bundle.js",
+    filename: "./codemirror/codemirror.bundle.js",
     libraryTarget: "var",
     library: "CodeMirror",
   },
 }];
--- a/devtools/shared/DevToolsUtils.js
+++ b/devtools/shared/DevToolsUtils.js
@@ -1,119 +1,125 @@
 /* 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/. */
 
+/* globals setImmediate, rpc */
+
 "use strict";
 
 /* General utilities used throughout devtools. */
 
-var { Ci, Cu, Cc, components } = require("chrome");
+var { Ci, Cu, components } = require("chrome");
 var Services = require("Services");
 var promise = require("promise");
 var defer = require("devtools/shared/defer");
 var flags = require("./flags");
 var {getStack, callFunctionWithAsyncStack} = require("devtools/shared/platform/stack");
 
 loader.lazyRequireGetter(this, "FileUtils",
                          "resource://gre/modules/FileUtils.jsm", true);
 
+// Using this name lets the eslint plugin know about lazy defines in
+// this file.
+var DevToolsUtils = exports;
+
 // Re-export the thread-safe utils.
 const ThreadSafeDevToolsUtils = require("./ThreadSafeDevToolsUtils.js");
 for (let key of Object.keys(ThreadSafeDevToolsUtils)) {
   exports[key] = ThreadSafeDevToolsUtils[key];
 }
 
 /**
  * Waits for the next tick in the event loop to execute a callback.
  */
-exports.executeSoon = function executeSoon(aFn) {
+exports.executeSoon = function (fn) {
   if (isWorker) {
-    setImmediate(aFn);
+    setImmediate(fn);
   } else {
     let executor;
     // Only enable async stack reporting when DEBUG_JS_MODULES is set
     // (customized local builds) to avoid a performance penalty.
     if (AppConstants.DEBUG_JS_MODULES || flags.testing) {
       let stack = getStack();
       executor = () => {
-        callFunctionWithAsyncStack(aFn, stack, "DevToolsUtils.executeSoon");
+        callFunctionWithAsyncStack(fn, stack, "DevToolsUtils.executeSoon");
       };
     } else {
-      executor = aFn;
+      executor = fn;
     }
     Services.tm.mainThread.dispatch({
       run: exports.makeInfallible(executor)
     }, Ci.nsIThread.DISPATCH_NORMAL);
   }
 };
 
 /**
  * Waits for the next tick in the event loop.
  *
  * @return Promise
  *         A promise that is resolved after the next tick in the event loop.
  */
-exports.waitForTick = function waitForTick() {
+exports.waitForTick = function () {
   let deferred = defer();
   exports.executeSoon(deferred.resolve);
   return deferred.promise;
 };
 
 /**
  * Waits for the specified amount of time to pass.
  *
- * @param number aDelay
+ * @param number delay
  *        The amount of time to wait, in milliseconds.
  * @return Promise
  *         A promise that is resolved after the specified amount of time passes.
  */
-exports.waitForTime = function waitForTime(aDelay) {
+exports.waitForTime = function (delay) {
   let deferred = defer();
-  setTimeout(deferred.resolve, aDelay);
+  setTimeout(deferred.resolve, delay);
   return deferred.promise;
 };
 
 /**
  * Like Array.prototype.forEach, but doesn't cause jankiness when iterating over
  * very large arrays by yielding to the browser and continuing execution on the
  * next tick.
  *
- * @param Array aArray
+ * @param Array array
  *        The array being iterated over.
- * @param Function aFn
+ * @param Function fn
  *        The function called on each item in the array. If a promise is
  *        returned by this function, iterating over the array will be paused
  *        until the respective promise is resolved.
  * @returns Promise
  *          A promise that is resolved once the whole array has been iterated
- *          over, and all promises returned by the aFn callback are resolved.
+ *          over, and all promises returned by the fn callback are resolved.
  */
-exports.yieldingEach = function yieldingEach(aArray, aFn) {
+exports.yieldingEach = function (array, fn) {
   const deferred = defer();
 
   let i = 0;
-  let len = aArray.length;
+  let len = array.length;
   let outstanding = [deferred.promise];
 
   (function loop() {
     const start = Date.now();
 
     while (i < len) {
       // Don't block the main thread for longer than 16 ms at a time. To
       // maintain 60fps, you have to render every frame in at least 16ms; we
       // aren't including time spent in non-JS here, but this is Good
       // Enough(tm).
       if (Date.now() - start > 16) {
         exports.executeSoon(loop);
         return;
       }
 
       try {
-        outstanding.push(aFn(aArray[i], i++));
+        outstanding.push(fn(array[i], i++));
       } catch (e) {
         deferred.reject(e);
         return;
       }
     }
 
     deferred.resolve();
   }());
@@ -121,126 +127,127 @@ exports.yieldingEach = function yielding
   return promise.all(outstanding);
 };
 
 /**
  * Like XPCOMUtils.defineLazyGetter, but with a |this| sensitive getter that
  * allows the lazy getter to be defined on a prototype and work correctly with
  * instances.
  *
- * @param Object aObject
+ * @param Object object
  *        The prototype object to define the lazy getter on.
- * @param String aKey
+ * @param String key
  *        The key to define the lazy getter on.
- * @param Function aCallback
+ * @param Function callback
  *        The callback that will be called to determine the value. Will be
  *        called with the |this| value of the current instance.
  */
-exports.defineLazyPrototypeGetter =
-function defineLazyPrototypeGetter(aObject, aKey, aCallback) {
-  Object.defineProperty(aObject, aKey, {
+exports.defineLazyPrototypeGetter = function (object, key, callback) {
+  Object.defineProperty(object, key, {
     configurable: true,
     get: function () {
-      const value = aCallback.call(this);
+      const value = callback.call(this);
 
-      Object.defineProperty(this, aKey, {
+      Object.defineProperty(this, key, {
         configurable: true,
         writable: true,
         value: value
       });
 
       return value;
     }
   });
 };
 
 /**
  * Safely get the property value from a Debugger.Object for a given key. Walks
  * the prototype chain until the property is found.
  *
- * @param Debugger.Object aObject
+ * @param Debugger.Object object
  *        The Debugger.Object to get the value from.
- * @param String aKey
+ * @param String key
  *        The key to look for.
  * @return Any
  */
-exports.getProperty = function getProperty(aObj, aKey) {
-  let root = aObj;
+exports.getProperty = function (object, key) {
+  let root = object;
   try {
     do {
-      const desc = aObj.getOwnPropertyDescriptor(aKey);
+      const desc = object.getOwnPropertyDescriptor(key);
       if (desc) {
         if ("value" in desc) {
           return desc.value;
         }
         // Call the getter if it's safe.
         return exports.hasSafeGetter(desc) ? desc.get.call(root).return : undefined;
       }
-      aObj = aObj.proto;
-    } while (aObj);
+      object = object.proto;
+    } while (object);
   } catch (e) {
     // If anything goes wrong report the error and return undefined.
     exports.reportException("getProperty", e);
   }
   return undefined;
 };
 
 /**
  * Determines if a descriptor has a getter which doesn't call into JavaScript.
  *
- * @param Object aDesc
+ * @param Object desc
  *        The descriptor to check for a safe getter.
  * @return Boolean
  *         Whether a safe getter was found.
  */
-exports.hasSafeGetter = function hasSafeGetter(aDesc) {
+exports.hasSafeGetter = function (desc) {
   // Scripted functions that are CCWs will not appear scripted until after
   // unwrapping.
   try {
-    let fn = aDesc.get.unwrap();
+    let fn = desc.get.unwrap();
     return fn && fn.callable && fn.class == "Function" && fn.script === undefined;
   } catch (e) {
     // Avoid exception 'Object in compartment marked as invisible to Debugger'
     return false;
   }
 };
 
 /**
  * Check if it is safe to read properties and execute methods from the given JS
  * object. Safety is defined as being protected from unintended code execution
  * from content scripts (or cross-compartment code).
  *
  * See bugs 945920 and 946752 for discussion.
  *
- * @type Object aObj
+ * @type Object obj
  *       The object to check.
  * @return Boolean
- *         True if it is safe to read properties from aObj, or false otherwise.
+ *         True if it is safe to read properties from obj, or false otherwise.
  */
-exports.isSafeJSObject = function isSafeJSObject(aObj) {
+exports.isSafeJSObject = function (obj) {
   // If we are running on a worker thread, Cu is not available. In this case,
   // we always return false, just to be on the safe side.
   if (isWorker) {
     return false;
   }
 
-  if (Cu.getGlobalForObject(aObj) ==
+  if (Cu.getGlobalForObject(obj) ==
       Cu.getGlobalForObject(exports.isSafeJSObject)) {
-    return true; // aObj is not a cross-compartment wrapper.
+    // obj is not a cross-compartment wrapper.
+    return true;
   }
 
-  let principal = Cu.getObjectPrincipal(aObj);
+  let principal = Cu.getObjectPrincipal(obj);
   if (Services.scriptSecurityManager.isSystemPrincipal(principal)) {
-    return true; // allow chrome objects
+    // allow chrome objects
+    return true;
   }
 
-  return Cu.isXrayWrapper(aObj);
+  return Cu.isXrayWrapper(obj);
 };
 
-exports.dumpn = function dumpn(str) {
+exports.dumpn = function (str) {
   if (flags.wantLogging) {
     dump("DBG-SERVER: " + str + "\n");
   }
 };
 
 /**
  * A verbose logger for low-level tracing.
  */
@@ -248,36 +255,37 @@ exports.dumpv = function (msg) {
   if (flags.wantVerbose) {
     exports.dumpn(msg);
   }
 };
 
 /**
  * Defines a getter on a specified object that will be created upon first use.
  *
- * @param aObject
+ * @param object
  *        The object to define the lazy getter on.
- * @param aName
- *        The name of the getter to define on aObject.
- * @param aLambda
+ * @param name
+ *        The name of the getter to define on object.
+ * @param lambda
  *        A function that returns what the getter should return.  This will
  *        only ever be called once.
  */
-exports.defineLazyGetter = function defineLazyGetter(aObject, aName, aLambda) {
-  Object.defineProperty(aObject, aName, {
+exports.defineLazyGetter = function (object, name, lambda) {
+  Object.defineProperty(object, name, {
     get: function () {
-      delete aObject[aName];
-      return aObject[aName] = aLambda.apply(aObject);
+      delete object[name];
+      object[name] = lambda.apply(object);
+      return object[name];
     },
     configurable: true,
     enumerable: true
   });
 };
 
-exports.defineLazyGetter(this, "AppConstants", () => {
+DevToolsUtils.defineLazyGetter(this, "AppConstants", () => {
   if (isWorker) {
     return {};
   }
   const scope = {};
   Cu.import("resource://gre/modules/AppConstants.jsm", scope);
   return scope.AppConstants;
 });
 
@@ -324,57 +332,54 @@ Object.defineProperty(exports, "assert",
     ? reallyAssert
     : exports.noop,
 });
 
 /**
  * Defines a getter on a specified object for a module.  The module will not
  * be imported until first use.
  *
- * @param aObject
+ * @param object
  *        The object to define the lazy getter on.
- * @param aName
- *        The name of the getter to define on aObject for the module.
- * @param aResource
+ * @param name
+ *        The name of the getter to define on object for the module.
+ * @param resource
  *        The URL used to obtain the module.
- * @param aSymbol
+ * @param symbol
  *        The name of the symbol exported by the module.
- *        This parameter is optional and defaults to aName.
+ *        This parameter is optional and defaults to name.
  */
-exports.defineLazyModuleGetter = function defineLazyModuleGetter(aObject, aName,
-                                                                 aResource,
-                                                                 aSymbol)
-{
-  this.defineLazyGetter(aObject, aName, function XPCU_moduleLambda() {
-    var temp = {};
-    Cu.import(aResource, temp);
-    return temp[aSymbol || aName];
+exports.defineLazyModuleGetter = function (object, name, resource, symbol) {
+  this.defineLazyGetter(object, name, function () {
+    let temp = {};
+    Cu.import(resource, temp);
+    return temp[symbol || name];
   });
 };
 
-exports.defineLazyGetter(this, "NetUtil", () => {
+DevToolsUtils.defineLazyGetter(this, "NetUtil", () => {
   return Cu.import("resource://gre/modules/NetUtil.jsm", {}).NetUtil;
 });
 
-exports.defineLazyGetter(this, "OS", () => {
+DevToolsUtils.defineLazyGetter(this, "OS", () => {
   return Cu.import("resource://gre/modules/osfile.jsm", {}).OS;
 });
 
-exports.defineLazyGetter(this, "TextDecoder", () => {
+DevToolsUtils.defineLazyGetter(this, "TextDecoder", () => {
   return Cu.import("resource://gre/modules/osfile.jsm", {}).TextDecoder;
 });
 
-exports.defineLazyGetter(this, "NetworkHelper", () => {
+DevToolsUtils.defineLazyGetter(this, "NetworkHelper", () => {
   return require("devtools/shared/webconsole/network-helper");
 });
 
 /**
  * Performs a request to load the desired URL and returns a promise.
  *
- * @param aURL String
+ * @param urlIn String
  *        The URL we will request.
  * @param aOptions Object
  *        An object with the following optional properties:
  *        - loadFromCache: if false, will bypass the cache and
  *          always load fresh from the network (default: true)
  *        - policy: the nsIContentPolicy type to apply when fetching the URL
  *                  (only works when loading from system principal)
  *        - window: the window to get the loadGroup from
@@ -391,24 +396,24 @@ exports.defineLazyGetter(this, "NetworkH
  *           - contentType: the content type of the document
  *
  *          If an error occurs, the promise is rejected with that error.
  *
  * XXX: It may be better to use nsITraceableChannel to get to the sources
  * without relying on caching when we can (not for eval, etc.):
  * http://www.softwareishard.com/blog/firebug/nsitraceablechannel-intercept-http-traffic/
  */
-function mainThreadFetch(aURL, aOptions = { loadFromCache: true,
+function mainThreadFetch(urlIn, aOptions = { loadFromCache: true,
                                           policy: Ci.nsIContentPolicy.TYPE_OTHER,
                                           window: null,
                                           charset: null,
                                           principal: null,
                                           cacheKey: null }) {
   // Create a channel.
-  let url = aURL.split(" -> ").pop();
+  let url = urlIn.split(" -> ").pop();
   let channel;
   try {
     channel = newChannelForURL(url, aOptions);
   } catch (ex) {
     return promise.reject(ex);
   }
 
   // Set the channel options.
@@ -521,17 +526,17 @@ function mainThreadFetch(aURL, aOptions 
 /**
  * Opens a channel for given URL. Tries a bit harder than NetUtil.newChannel.
  *
  * @param {String} url - The URL to open a channel for.
  * @param {Object} options - The options object passed to @method fetch.
  * @return {nsIChannel} - The newly created channel. Throws on failure.
  */
 function newChannelForURL(url, { policy, window, principal }) {
-  var securityFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL;
+  let securityFlags = Ci.nsILoadInfo.SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL;
 
   let uri;
   try {
     uri = Services.io.newURI(url, null, null);
   } catch (e) {
     // In the xpcshell tests, the script url is the absolute path of the test
     // file, which will make a malformed URI error be thrown. Add the file
     // scheme to see if it helps.
@@ -565,25 +570,25 @@ function newChannelForURL(url, { policy,
     // supported by Windows, so we also need to handle the exception here if
     // parsing the URL above doesn't throw.
     return newChannelForURL("file://" + url, { policy, window, principal });
   }
 }
 
 // Fetch is defined differently depending on whether we are on the main thread
 // or a worker thread.
-if (!this.isWorker) {
-  exports.fetch = mainThreadFetch;
-} else {
+if (this.isWorker) {
   // Services is not available in worker threads, nor is there any other way
   // to fetch a URL. We need to enlist the help from the main thread here, by
   // issuing an rpc request, to fetch the URL on our behalf.
   exports.fetch = function (url, options) {
     return rpc("fetch", url, options);
   };
+} else {
+  exports.fetch = mainThreadFetch;
 }
 
 /**
  * Open the file at the given path for reading.
  *
  * @param {String} filePath
  *
  * @returns Promise<nsIInputStream>
@@ -628,17 +633,17 @@ function errorOnFlag(exports, name) {
 }
 
 errorOnFlag(exports, "testing");
 errorOnFlag(exports, "wantLogging");
 errorOnFlag(exports, "wantVerbose");
 
 // Calls the property with the given `name` on the given `object`, where
 // `name` is a string, and `object` a Debugger.Object instance.
-///
+//
 // This function uses only the Debugger.Object API to call the property. It
 // avoids the use of unsafeDeference. This is useful for example in workers,
 // where unsafeDereference will return an opaque security wrapper to the
 // referent.
 function callPropertyOnObject(object, name) {
   // Find the property.
   let descriptor;
   let proto = object;
@@ -663,10 +668,9 @@ function callPropertyOnObject(object, na
     throw new Error("Code was terminated.");
   }
   if ("throw" in result) {
     throw result.throw;
   }
   return result.return;
 }
 
-
 exports.callPropertyOnObject = callPropertyOnObject;
--- a/devtools/shared/ThreadSafeDevToolsUtils.js
+++ b/devtools/shared/ThreadSafeDevToolsUtils.js
@@ -91,17 +91,17 @@ exports.reportException = function repor
  * @param aName string
  *      A name for handler, for use in error messages. If omitted, we use
  *      handler.name.
  *
  * (SpiderMonkey does generate good names for anonymous functions, but we
  * don't have a way to get at them from JavaScript at the moment.)
  */
 exports.makeInfallible = function (handler, name = handler.name) {
-  return function (/* arguments */) {
+  return function () {
     try {
       return handler.apply(this, arguments);
     } catch (ex) {
       let who = "Handler function";
       if (name) {
         who += " " + name;
       }
       exports.reportException(who, ex);
@@ -123,27 +123,31 @@ exports.safeErrorString = function (erro
       // isn't a string, don't use it.
       try {
         if (error.stack) {
           let stack = error.stack.toString();
           if (typeof stack == "string") {
             errorString += "\nStack: " + stack;
           }
         }
-      } catch (ee) { }
+      } catch (ee) {
+        // Ignore.
+      }
 
       // Append additional line and column number information to the output,
       // since it might not be part of the stringified error.
       if (typeof error.lineNumber == "number" && typeof error.columnNumber == "number") {
         errorString += "Line: " + error.lineNumber + ", column: " + error.columnNumber;
       }
 
       return errorString;
     }
-  } catch (ee) { }
+  } catch (ee) {
+    // Ignore.
+  }
 
   // We failed to find a good error description, so do the next best thing.
   return Object.prototype.toString.call(error);
 };
 
 /**
  * Interleaves two arrays element by element, returning the combined array, like
  * a zip. In the case of arrays with different sizes, undefined values will be
@@ -289,17 +293,17 @@ exports.settleAll = values => {
     values = Array.isArray(values) ? values : [...values];
     let countdown = values.length;
     let resolutionValues = new Array(countdown);
     let rejectionValue;
     let rejectionOccurred = false;
 
     if (!countdown) {
       resolve(resolutionValues);
-      return deferred.promise;
+      return;
     }
 
     function checkForCompletion() {
       if (--countdown > 0) {
         return;
       }
       if (!rejectionOccurred) {
         resolve(resolutionValues);
--- a/devtools/shared/builtin-modules.js
+++ b/devtools/shared/builtin-modules.js
@@ -15,122 +15,119 @@
 
 const { Cu, CC, Cc, Ci } = require("chrome");
 const { Loader } = Cu.import("resource://gre/modules/commonjs/toolkit/loader.js", {});
 const promise = Cu.import("resource://gre/modules/Promise.jsm", {}).Promise;
 const jsmScope = Cu.import("resource://gre/modules/Services.jsm", {});
 const { Services } = jsmScope;
 // Steal various globals only available in JSM scope (and not Sandbox one)
 const { PromiseDebugging, ChromeUtils, ThreadSafeChromeUtils, HeapSnapshot,
-        atob, btoa, Iterator } = jsmScope;
+        atob, btoa } = jsmScope;
 const { URL } = Cu.Sandbox(CC("@mozilla.org/systemprincipal;1", "nsIPrincipal")(),
                            {wantGlobalProperties: ["URL"]});
 
 /**
  * Defines a getter on a specified object that will be created upon first use.
  *
- * @param aObject
+ * @param object
  *        The object to define the lazy getter on.
- * @param aName
- *        The name of the getter to define on aObject.
- * @param aLambda
+ * @param name
+ *        The name of the getter to define on object.
+ * @param lambda
  *        A function that returns what the getter should return.  This will
  *        only ever be called once.
  */
-function defineLazyGetter(aObject, aName, aLambda)
-{
-  Object.defineProperty(aObject, aName, {
+function defineLazyGetter(object, name, lambda) {
+  Object.defineProperty(object, name, {
     get: function () {
       // Redefine this accessor property as a data property.
-      // Delete it first, to rule out "too much recursion" in case aObject is
+      // Delete it first, to rule out "too much recursion" in case object is
       // a proxy whose defineProperty handler might unwittingly trigger this
       // getter again.
-      delete aObject[aName];
-      let value = aLambda.apply(aObject);
-      Object.defineProperty(aObject, aName, {
+      delete object[name];
+      let value = lambda.apply(object);
+      Object.defineProperty(object, name, {
         value,
         writable: true,
         configurable: true,
         enumerable: true
       });
       return value;
     },
     configurable: true,
     enumerable: true
   });
 }
 
 /**
  * Defines a getter on a specified object for a service.  The service will not
  * be obtained until first use.
  *
- * @param aObject
+ * @param object
  *        The object to define the lazy getter on.
- * @param aName
- *        The name of the getter to define on aObject for the service.
- * @param aContract
+ * @param name
+ *        The name of the getter to define on object for the service.
+ * @param contract
  *        The contract used to obtain the service.
- * @param aInterfaceName
+ * @param interfaceName
  *        The name of the interface to query the service to.
  */
-function defineLazyServiceGetter(aObject, aName, aContract, aInterfaceName)
-{
-  defineLazyGetter(aObject, aName, function XPCU_serviceLambda() {
-    return Cc[aContract].getService(Ci[aInterfaceName]);
+function defineLazyServiceGetter(object, name, contract, interfaceName) {
+  defineLazyGetter(object, name, function () {
+    return Cc[contract].getService(Ci[interfaceName]);
   });
 }
 
 /**
  * Defines a getter on a specified object for a module.  The module will not
  * be imported until first use. The getter allows to execute setup and
  * teardown code (e.g.  to register/unregister to services) and accepts
  * a proxy object which acts on behalf of the module until it is imported.
  *
- * @param aObject
+ * @param object
  *        The object to define the lazy getter on.
- * @param aName
- *        The name of the getter to define on aObject for the module.
- * @param aResource
+ * @param name
+ *        The name of the getter to define on object for the module.
+ * @param resource
  *        The URL used to obtain the module.
- * @param aSymbol
+ * @param symbol
  *        The name of the symbol exported by the module.
- *        This parameter is optional and defaults to aName.
- * @param aPreLambda
+ *        This parameter is optional and defaults to name.
+ * @param preLambda
  *        A function that is executed when the proxy is set up.
  *        This will only ever be called once.
- * @param aPostLambda
+ * @param postLambda
  *        A function that is executed when the module has been imported to
  *        run optional teardown procedures on the proxy object.
  *        This will only ever be called once.
- * @param aProxy
+ * @param proxy
  *        An object which acts on behalf of the module to be imported until
  *        the module has been imported.
  */
-function defineLazyModuleGetter(aObject, aName, aResource, aSymbol,
-                                aPreLambda, aPostLambda, aProxy)
-{
-  let proxy = aProxy || {};
+function defineLazyModuleGetter(object, name, resource, symbol,
+                                preLambda, postLambda, proxy) {
+  proxy = proxy || {};
 
-  if (typeof (aPreLambda) === "function") {
-    aPreLambda.apply(proxy);
+  if (typeof (preLambda) === "function") {
+    preLambda.apply(proxy);
   }
 
-  defineLazyGetter(aObject, aName, function XPCU_moduleLambda() {
-    var temp = {};
+  defineLazyGetter(object, name, function () {
+    let temp = {};
     try {
-      Cu.import(aResource, temp);
+      Cu.import(resource, temp);
 
-      if (typeof (aPostLambda) === "function") {
-        aPostLambda.apply(proxy);
+      if (typeof (postLambda) === "function") {
+        postLambda.apply(proxy);
       }
     } catch (ex) {
-      Cu.reportError("Failed to load module " + aResource + ".");
+      Cu.reportError("Failed to load module " + resource + ".");
       throw ex;
     }
-    return temp[aSymbol || aName];
+    return temp[symbol || name];
   });
 }
 
 /**
  * Define a getter property on the given object that requires the given
  * module. This enables delaying importing modules until the module is
  * actually used.
  *
@@ -219,17 +216,18 @@ exports.globals = {
   atob: atob,
   btoa: btoa,
   URL,
   loader: {
     lazyGetter: defineLazyGetter,
     lazyImporter: defineLazyModuleGetter,
     lazyServiceGetter: defineLazyServiceGetter,
     lazyRequireGetter: lazyRequireGetter,
-    id: null // Defined by Loader.jsm
+    // Defined by Loader.jsm
+    id: null
   },
 
   // Let new XMLHttpRequest do the right thing.
   XMLHttpRequest: function () {
     return Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
            .createInstance(Ci.nsIXMLHttpRequest);
   },
 
--- a/devtools/shared/content-observer.js
+++ b/devtools/shared/content-observer.js
@@ -1,14 +1,14 @@
 /* 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/. */
 "use strict";
 
-const {Cc, Ci, Cu, Cr} = require("chrome");
+const {Ci} = require("chrome");
 const Services = require("Services");
 
 const events = require("sdk/event/core");
 
 /**
  * Handles adding an observer for the creation of content document globals,
  * event sent immediately after a web content document window has been set up,
  * but before any script code has been executed.
--- a/devtools/shared/deprecated-sync-thenables.js
+++ b/devtools/shared/deprecated-sync-thenables.js
@@ -1,16 +1,18 @@
 /* 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/. */
 
 /**
  * THIS MODULE IS DEPRECATED. IMPORT "Promise.jsm" INSTEAD.
  */
 
+/* eslint-disable */
+
 "use strict";
 
 this.Promise = {};
 
 if (typeof (require) === "function") {
   module.exports = Promise;
 } else {
   this.EXPORTED_SYMBOLS = ["Promise"];
--- a/devtools/shared/dom-node-constants.js
+++ b/devtools/shared/dom-node-constants.js
@@ -1,13 +1,15 @@
 /* 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/. */
 "use strict";
 
+/* globals define */
+
 // Make this available to both AMD and CJS environments
 define(function (require, exports, module) {
   module.exports = {
     ELEMENT_NODE: 1,
     ATTRIBUTE_NODE: 2,
     TEXT_NODE: 3,
     CDATA_SECTION_NODE: 4,
     ENTITY_REFERENCE_NODE: 5,
--- a/devtools/shared/flags.js
+++ b/devtools/shared/flags.js
@@ -1,19 +1,24 @@
+"use strict";
 
 /*
  * Create a writable property by tracking it with a private variable.
  * We cannot make a normal property writeable on `exports` because
  * the module system freezes it.
  */
 function makeWritableFlag(exports, name) {
   let flag = false;
   Object.defineProperty(exports, name, {
-    get: function () { return flag; },
-    set: function (state) { flag = state; }
+    get: function () {
+      return flag;
+    },
+    set: function (state) {
+      flag = state;
+    }
   });
 }
 
 makeWritableFlag(exports, "wantLogging");
 makeWritableFlag(exports, "wantVerbose");
 
 // When the testing flag is set, various behaviors may be altered from
 // production mode, typically to enable easier testing or enhanced
--- a/devtools/shared/path.js
+++ b/devtools/shared/path.js
@@ -8,19 +8,18 @@
  * Join all the arguments together and normalize the resulting URI.
  * The initial path must be an full URI with a protocol (i.e. http://).
  */
 exports.joinURI = (initialPath, ...paths) => {
   let url;
 
   try {
     url = new URL(initialPath);
-  }
-  catch (e) {
-    return;
+  } catch (e) {
+    return null;
   }
 
   for (let path of paths) {
     if (path) {
       url = new URL(path, url);
     }
   }
 
--- a/devtools/shared/protocol.js
+++ b/devtools/shared/protocol.js
@@ -60,17 +60,19 @@ types.getType = function (type) {
   }
 
   if (typeof (type) !== "string") {
     return type;
   }
 
   // If already registered, we're done here.
   let reg = registeredTypes.get(type);
-  if (reg) return reg;
+  if (reg) {
+    return reg;
+  }
 
   // New type, see if it's a collection/lifetime type:
   let sep = type.indexOf(":");
   if (sep >= 0) {
     let collection = type.substring(0, sep);
     let subtype = types.getType(type.substring(sep + 1));
 
     if (collection === "array") {
@@ -141,17 +143,19 @@ function identityWrite(v) {
  * @returns a type object that can be used in protocol definitions.
  */
 types.addType = function (name, typeObject = {}, options = {}) {
   if (registeredTypes.has(name)) {
     throw Error("Type '" + name + "' already exists.");
   }
 
   let type = object.merge({
-    toString() { return "[protocol type:" + name + "]";},
+    toString() {
+      return "[protocol type:" + name + "]";
+    },
     name: name,
     primitive: !(typeObject.read || typeObject.write),
     read: identityWrite,
     write: identityWrite
   }, typeObject);
 
   registeredTypes.set(name, type);
 
@@ -165,17 +169,19 @@ types.addType = function (name, typeObje
 types.removeType = function (name) {
   // This type may still be referenced by other types, make sure
   // those references don't work.
   let type = registeredTypes.get(name);
 
   type.name = "DEFUNCT:" + name;
   type.category = "defunct";
   type.primitive = false;
-  type.read = type.write = function () { throw new Error("Using defunct type: " + name); };
+  type.read = type.write = function () {
+    throw new Error("Using defunct type: " + name);
+  };
 
   registeredTypes.delete(name);
 };
 
 /**
  * Add an array type to the type system.
  *
  * getType() will call this function if provided an "array:<type>"
@@ -269,17 +275,17 @@ types.addActorType = function (name) {
       }
 
       // Reading a response on the client side, check for an
       // existing front on the connection, and create the front
       // if it isn't found.
       let actorID = typeof (v) === "string" ? v : v.actor;
       let front = ctx.conn.getActor(actorID);
       if (!front) {
-        front = new type.frontClass(ctx.conn);
+        front = new type.frontClass(ctx.conn); // eslint-disable-line new-cap
         front.actorID = actorID;
         ctx.marshallPool().manage(front);
       }
 
       v = type.formType(detail).read(v, front, detail);
       front.form(v, detail, ctx);
 
       return front;
@@ -348,17 +354,18 @@ types.addNullableType = function (subtyp
  * @param type actorType
  *   The actor type you'll be detailing.
  * @param string detail
  *   The detail to pass.
  */
 types.addActorDetail = function (name, actorType, detail) {
   actorType = types.getType(actorType);
   if (!actorType._actor) {
-    throw Error("Details only apply to actor types, tried to add detail '" + detail + "'' to " + actorType.name + "\n");
+    throw Error(`Details only apply to actor types, tried to add detail '${detail}' ` +
+                `to ${actorType.name}`);
   }
   return types.addType(name, {
     _actor: true,
     category: "detail",
     read: (v, ctx) => actorType.read(v, ctx, detail),
     write: (v, ctx) => actorType.write(v, ctx, detail)
   });
 };
@@ -397,17 +404,18 @@ types.removeLifetime = function (name) {
  * @param string lifetime
  *    A lifetime string previously regisered with addLifetime()
  * @param type subtype
  *    An actor type
  */
 types.addLifetimeType = function (lifetime, subtype) {
   subtype = types.getType(subtype);
   if (!subtype._actor) {
-    throw Error("Lifetimes only apply to actor types, tried to apply lifetime '" + lifetime + "'' to " + subtype.name);
+    throw Error(`Lifetimes only apply to actor types, tried to apply ` +
+                `lifetime '${lifetime}' to ${subtype.name}`);
   }
   let prop = registeredLifetimes.get(lifetime);
   return types.addType(lifetime + ":" + subtype.name, {
     category: "lifetime",
     read: (value, ctx) => subtype.read(value, ctx[prop]),
     write: (value, ctx) => subtype.write(value, ctx[prop])
   });
 };
@@ -575,17 +583,16 @@ function findPlaceholders(template, cons
     path.push(name);
     findPlaceholders(template[name], constructor, path, placeholders);
     path.pop();
   }
 
   return placeholders;
 }
 
-
 function describeTemplate(template) {
   return JSON.parse(JSON.stringify(template, (key, value) => {
     if (value.describe) {
       return value.describe();
     }
     return value;
   }));
 }
@@ -639,17 +646,19 @@ var Request = Class({
       let arg = templateArg.placeholder;
       let path = templateArg.path;
       let name = path[path.length - 1];
       arg.read(getPath(packet, path), ctx, fnArgs, name);
     }
     return fnArgs;
   },
 
-  describe: function () { return describeTemplate(this.template); }
+  describe: function () {
+    return describeTemplate(this.template);
+  }
 });
 
 /**
  * Manages a response template.
  *
  * @param object template
  *    The response template.
  * @construcor
@@ -696,17 +705,19 @@ var Response = Class({
   read: function (packet, ctx) {
     if (!this.retVal) {
       return undefined;
     }
     let v = getPath(packet, this.path);
     return this.retVal.read(v, ctx);
   },
 
-  describe: function () { return describeTemplate(this.template); }
+  describe: function () {
+    return describeTemplate(this.template);
+  }
 });
 
 /**
  * Actor and Front implementations
  */
 
 /**
  * A protocol object that can manage the lifetime of other protocol
@@ -729,32 +740,38 @@ var Pool = Class({
     if (conn) {
       this.conn = conn;
     }
   },
 
   /**
    * Return the parent pool for this client.
    */
-  parent: function () { return this.conn.poolFor(this.actorID); },
+  parent: function () {
+    return this.conn.poolFor(this.actorID);
+  },
 
   /**
    * Override this if you want actors returned by this actor
    * to belong to a different actor by default.
    */
-  marshallPool: function () { return this; },
+  marshallPool: function () {
+    return this;
+  },
 
   /**
    * Pool is the base class for all actors, even leaf nodes.
    * If the child map is actually referenced, go ahead and create
    * the stuff needed by the pool.
    */
   __poolMap: null,
   get _poolMap() {
-    if (this.__poolMap) return this.__poolMap;
+    if (this.__poolMap) {
+      return this.__poolMap;
+    }
     this.__poolMap = new Map();
     this.conn.addActorPool(this);
     return this.__poolMap;
   },
 
   /**
    * Add an actor as a child of this pool.
    */
@@ -878,17 +895,19 @@ var Actor = Class({
         let sendEvent = this._sendEvent.bind(this, name);
         this.on(name, (...args) => {
           sendEvent.apply(null, args);
         });
       }
     }
   },
 
-  toString: function () { return "[Actor " + this.typeName + "/" + this.actorID + "]"; },
+  toString: function () {
+    return "[Actor " + this.typeName + "/" + this.actorID + "]";
+  },
 
   _sendEvent: function (name, ...args) {
     if (!this._actorSpec.events.has(name)) {
       // It's ok to emit events that don't go over the wire.
       return;
     }
     let request = this._actorSpec.events.get(name);
     let packet;
@@ -945,18 +964,22 @@ exports.Actor = Actor;
  * @param spec
  *    The method specification, with the following (optional) properties:
  *      request (object): a request template.
  *      response (object): a response template.
  *      oneway (bool): 'true' if no response should be sent.
  */
 exports.method = function (fn, spec = {}) {
   fn._methodSpec = Object.freeze(spec);
-  if (spec.request) Object.freeze(spec.request);
-  if (spec.response) Object.freeze(spec.response);
+  if (spec.request) {
+    Object.freeze(spec.request);
+  }
+  if (spec.response) {
+    Object.freeze(spec.response);
+  }
   return fn;
 };
 
 /**
  * Generates an actor specification from an actor description.
  */
 var generateActorSpec = function (actorDesc) {
   let actorSpec = {
@@ -981,33 +1004,35 @@ var generateActorSpec = function (actorD
         actorSpec[name] = types.addDictType(actorDesc.typeName + "__" + name, desc.value);
       }
     }
 
     if (desc.value._methodSpec) {
       let methodSpec = desc.value._methodSpec;
       let spec = {};
       spec.name = methodSpec.name || name;
-      spec.request = Request(object.merge({type: spec.name}, methodSpec.request || undefined));
+      spec.request = Request(object.merge({type: spec.name},
+                                          methodSpec.request || undefined));
       spec.response = Response(methodSpec.response || undefined);
       spec.release = methodSpec.release;
       spec.oneway = methodSpec.oneway;
 
       actorSpec.methods.push(spec);
     }
   }
 
   // Find additional method specifications
   if (actorDesc.methods) {
     for (let name in actorDesc.methods) {
       let methodSpec = actorDesc.methods[name];
       let spec = {};
 
       spec.name = methodSpec.name || name;
-      spec.request = Request(object.merge({type: spec.name}, methodSpec.request || undefined));
+      spec.request = Request(object.merge({type: spec.name},
+                                          methodSpec.request || undefined));
       spec.response = Response(methodSpec.response || undefined);
       spec.release = methodSpec.release;
       spec.oneway = methodSpec.oneway;
 
       actorSpec.methods.push(spec);
     }
   }
 
@@ -1051,25 +1076,25 @@ var generateRequestHandlers = function (
           args = spec.request.read(packet, this);
         } catch (ex) {
           console.error("Error reading request: " + packet.type);
           throw ex;
         }
 
         let ret = this[spec.name].apply(this, args);
 
-        let sendReturn = (ret) => {
+        let sendReturn = (retToSend) => {
           if (spec.oneway) {
             // No need to send a response.
             return;
           }
 
           let response;
           try {
-            response = spec.response.write(ret, this);
+            response = spec.response.write(retToSend, this);
           } catch (ex) {
             console.error("Error writing response to: " + spec.name);
             throw ex;
           }
           response.from = this.actorID;
           // If spec.release has been specified, destroy the object.
           if (spec.release) {
             try {
@@ -1197,19 +1222,23 @@ var Front = Class({
     }
     return Pool.prototype.manage.call(this, front);
   },
 
   /**
    * @returns a promise that will resolve to the actorID this front
    * represents.
    */
-  actor: function () { return promise.resolve(this.actorID); },
+  actor: function () {
+    return promise.resolve(this.actorID);
+  },
 
-  toString: function () { return "[Front for " + this.typeName + "/" + this.actorID + "]"; },
+  toString: function () {
+    return "[Front for " + this.typeName + "/" + this.actorID + "]";
+  },
 
   /**
    * Update the actor from its representation.
    * Subclasses should override this.
    */
   form: function (form) {},
 
   /**
@@ -1260,17 +1289,19 @@ var Front = Class({
         throw ex;
       }
       if (event.pre) {
         let results = event.pre.map(pre => pre.apply(this, args));
 
         // Check to see if any of the preEvents returned a promise -- if so,
         // wait for their resolution before emitting. Otherwise, emit synchronously.
         if (results.some(result => result && typeof result.then === "function")) {
-          promise.all(results).then(() => events.emit.apply(null, [this, event.name].concat(args)));
+          promise.all(results).then(() => {
+            return events.emit.apply(null, [this, event.name].concat(args));
+          });
           return;
         }
       }
 
       events.emit.apply(null, [this, event.name].concat(args));
       return;
     }
 
@@ -1363,17 +1394,18 @@ var generateRequestMethods = function (a
   methods.forEach(spec => {
     let name = spec.name;
 
     // If there's already a property by this name in the front, it must
     // be a custom front method.
     if (name in frontProto) {
       let custom = frontProto[spec.name]._customFront;
       if (custom === undefined) {
-        throw Error("Existing method for " + spec.name + " not marked customFront while processing " + actorType.typeName + ".");
+        throw Error(`Existing method for ${spec.name} not marked customFront while ` +
+                    ` processing ${actorSpec.typeName}.`);
       }
       // If the user doesn't need the impl don't generate it.
       if (!custom.impl) {
         return;
       }
       name = custom.impl;
     }
 
@@ -1410,46 +1442,45 @@ var generateRequestMethods = function (a
         return fn.apply(this, args).then(result => {
           this.destroy();
           return result;
         });
       };
     }
   });
 
-
   // Process event specifications
   frontProto._clientSpec = {};
 
-  let events = actorSpec.events;
-  if (events) {
+  let actorEvents = actorSpec.events;
+  if (actorEvents) {
     // This actor has events, scan the prototype for preEvent handlers...
     let preHandlers = new Map();
     for (let name of Object.getOwnPropertyNames(frontProto)) {
       let desc = Object.getOwnPropertyDescriptor(frontProto, name);
       if (!desc.value) {
         continue;
       }
       if (desc.value._preEvent) {
         let preEvent = desc.value._preEvent;
-        if (!events.has(preEvent)) {
+        if (!actorEvents.has(preEvent)) {
           throw Error("preEvent for event that doesn't exist: " + preEvent);
         }
         let handlers = preHandlers.get(preEvent);
         if (!handlers) {
           handlers = [];
           preHandlers.set(preEvent, handlers);
         }
         handlers.push(desc.value);
       }
     }
 
     frontProto._clientSpec.events = new Map();
 
-    for (let [name, request] of events) {
+    for (let [name, request] of actorEvents) {
       frontProto._clientSpec.events.set(request.type, {
         name: name,
         request: request,
         pre: preHandlers.get(name)
       });
     }
   }
 
@@ -1513,17 +1544,16 @@ exports.dumpActorSpec = function (type) 
   }
 
   if (actorSpec.events) {
     for (let [name, request] of actorSpec.events) {
       ret.events[name] = request.describe();
     }
   }
 
-
   JSON.stringify(ret);
 
   return ret;
 };
 
 exports.dumpProtocolSpec = function () {
   let ret = {
     types: {},
--- a/devtools/shared/system.js
+++ b/devtools/shared/system.js
@@ -1,14 +1,14 @@
 /* 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/. */
 "use strict";
 
-const { Cc, Ci, Cu } = require("chrome");
+const { Cc, Ci } = require("chrome");
 const { Task } = require("devtools/shared/task");
 
 loader.lazyRequireGetter(this, "Services");
 loader.lazyRequireGetter(this, "promise");
 loader.lazyRequireGetter(this, "defer", "devtools/shared/defer");
 loader.lazyRequireGetter(this, "OS", "resource://gre/modules/commonjs/node/os.js");
 loader.lazyRequireGetter(this, "DebuggerServer", "devtools/server/main", true);
 loader.lazyRequireGetter(this, "AppConstants",
@@ -59,33 +59,34 @@ function* getSystemInfo() {
   if (apptype === "b2g") {
     os = "B2G";
     // `getSetting` does not work in child processes on b2g.
     // TODO bug 1205797, make this work in child processes.
     try {
       hardware = yield exports.getSetting("deviceinfo.hardware");
       version = yield exports.getSetting("deviceinfo.os");
     } catch (e) {
+      // Ignore.
     }
-  }
-  // Not B2G
-  else {
+  } else {
+    // Not B2G
     os = appInfo.OS;
     version = appInfo.version;
   }
 
   let bundle = Services.strings.createBundle("chrome://branding/locale/brand.properties");
   if (bundle) {
     brandName = bundle.GetStringFromName("brandFullName");
   } else {
     brandName = null;
   }
 
   if (win) {
-    let utils = win.QueryInterface(Ci.nsIInterfaceRequestor).getInterface(Ci.nsIDOMWindowUtils);
+    let utils = win.QueryInterface(Ci.nsIInterfaceRequestor)
+                   .getInterface(Ci.nsIDOMWindowUtils);
     dpi = utils.displayDPI;
     useragent = win.navigator.userAgent;
     width = win.screen.width;
     height = win.screen.height;
     physicalWidth = win.screen.width * win.devicePixelRatio;
     physicalHeight = win.screen.height * win.devicePixelRatio;
   }
 
@@ -126,17 +127,18 @@ function* getSystemInfo() {
 
     // The version of Gecko or XULRunner platform, for example "1.8.1.19" or
     // "1.9.3pre". In "Firefox 3.7 alpha 1" the application version is "3.7a1pre"
     // while the platform version is "1.9.3pre"
     platformversion: geckoVersion,
     geckoversion: geckoVersion,
 
     // Locale used in this build
-    locale: Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry).getSelectedLocale("global"),
+    locale: Cc["@mozilla.org/chrome/chrome-registry;1"]
+              .getService(Ci.nsIXULChromeRegistry).getSelectedLocale("global"),
 
     /**
      * Information regarding the operating system.
      */
 
     // Returns the endianness of the architecture: either "LE" or "BE"
     endianness: OS.endianness(),
 
@@ -181,22 +183,23 @@ function* getSystemInfo() {
   CACHED_INFO = info;
   return info;
 }
 
 function getProfileLocation() {
   // In child processes, we cannot access the profile location.
   try {
     let profd = Services.dirsvc.get("ProfD", Ci.nsILocalFile);
-    let profservice = Cc["@mozilla.org/toolkit/profile-service;1"].getService(Ci.nsIToolkitProfileService);
-    var profiles = profservice.profiles;
+    let profservice = Cc["@mozilla.org/toolkit/profile-service;1"]
+                        .getService(Ci.nsIToolkitProfileService);
+    let profiles = profservice.profiles;
     while (profiles.hasMoreElements()) {
       let profile = profiles.getNext().QueryInterface(Ci.nsIToolkitProfile);
       if (profile.rootDir.path == profd.path) {
-        return profile = profile.name;
+        return profile.name;
       }
     }
 
     return profd.leafName;
   } catch (e) {
     return "";
   }
 }
@@ -209,17 +212,18 @@ function getAppIniString(section, key) {
     inifile = Services.dirsvc.get("CurProcD", Ci.nsIFile);
     inifile.append("application.ini");
   }
 
   if (!inifile.exists()) {
     return undefined;
   }
 
-  let iniParser = Cc["@mozilla.org/xpcom/ini-parser-factory;1"].getService(Ci.nsIINIParserFactory).createINIParser(inifile);
+  let iniParser = Cc["@mozilla.org/xpcom/ini-parser-factory;1"]
+                    .getService(Ci.nsIINIParserFactory).createINIParser(inifile);
   try {
     return iniParser.getString(section, key);
   } catch (e) {
     return undefined;
   }
 }
 
 /**
@@ -311,23 +315,24 @@ function getSetting(name) {
   let deferred = defer();
 
   if ("@mozilla.org/settingsService;1" in Cc) {
     let settingsService;
 
     // settingsService fails in b2g child processes
     // TODO bug 1205797, make this work in child processes.
     try {
-      settingsService = Cc["@mozilla.org/settingsService;1"].getService(Ci.nsISettingsService);
+      settingsService = Cc["@mozilla.org/settingsService;1"]
+                          .getService(Ci.nsISettingsService);
     } catch (e) {
       return promise.reject(e);
     }
 
-    let req = settingsService.createLock().get(name, {
-      handle: (name, value) => deferred.resolve(value),
+    settingsService.createLock().get(name, {
+      handle: (_, value) => deferred.resolve(value),
       handleError: (error) => deferred.reject(error),
     });
   } else {
     deferred.reject(new Error("No settings service"));
   }
   return deferred.promise;
 }
 
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -12438,17 +12438,17 @@ nsDocShell::LoadHistoryEntry(nsISHEntry*
   if (NS_FAILED(rv) || isJS) {
     // We're loading a URL that will execute script from inside asyncOpen.
     // Replace the current document with about:blank now to prevent
     // anything from the current document from leaking into any JavaScript
     // code in the URL.
     // Don't cache the presentation if we're going to just reload the
     // current entry. Caching would lead to trying to save the different
     // content viewers in the same nsISHEntry object.
-    rv = CreateAboutBlankContentViewer(triggeringPrincipal, nullptr,
+    rv = CreateAboutBlankContentViewer(principalToInherit, nullptr,
                                        aEntry != mOSHE);
 
     if (NS_FAILED(rv)) {
       // The creation of the intermittent about:blank content
       // viewer failed for some reason (potentially because the
       // user prevented it). Interrupt the history load.
       return NS_OK;
     }
--- a/docshell/base/nsIDocShellTreeOwner.idl
+++ b/docshell/base/nsIDocShellTreeOwner.idl
@@ -18,24 +18,21 @@ interface nsIDocShellTreeOwner : nsISupp
 {
 	/**
 	 * Called when a content shell is added to the docshell tree.  This is
 	 * _only_ called for "root" content shells (that is, ones whose parent is a
 	 * chrome shell).
 	 *
 	 * @param aContentShell the shell being added.
 	 * @param aPrimary whether the shell is primary.
-	 * @param aTargetable whether the shell can be a target for named window
-	 *					targeting.
 	 * @param aID the "id" of the shell.  What this actually means is
 	 *			undefined. Don't rely on this for anything.
 	 */
 	void contentShellAdded(in nsIDocShellTreeItem aContentShell,
-						   in boolean aPrimary, in boolean aTargetable,
-						   in AString aID);
+	                       in boolean aPrimary, in AString aID);
 
 	/**
 	 * Called when a content shell is removed from the docshell tree.  This is
 	 * _only_ called for "root" content shells (that is, ones whose parent is a
 	 * chrome shell).  Note that if aContentShell was never added,
 	 * contentShellRemoved should just do nothing.
 	 *
 	 * @param aContentShell the shell being removed.
@@ -92,18 +89,19 @@ interface nsIDocShellTreeOwner : nsISupp
 	/*
 	Gets the current persistence states of the window.
 	*/
 	void getPersistence(out boolean aPersistPosition,
                             out boolean aPersistSize,
                             out boolean aPersistSizeMode);
 
 	/*
-	Gets the number of targettable docshells.
+	Gets the number of tabs currently open in our window, assuming
+	this tree owner has such a concept.
 	*/
-	readonly attribute unsigned long targetableShellCount;
+	readonly attribute unsigned long tabCount;
 
 	/*
 	Returns true if there is a primary content shell or a primary
 	tab parent.
 	*/
 	readonly attribute bool hasPrimaryContent;
 };
--- a/dom/animation/Animation.cpp
+++ b/dom/animation/Animation.cpp
@@ -721,17 +721,16 @@ Animation::AnimationTimeToTimeStamp(cons
 TimeStamp
 Animation::ElapsedTimeToTimeStamp(
   const StickyTimeDuration& aElapsedTime) const
 {
   return AnimationTimeToTimeStamp(aElapsedTime +
                                   mEffect->SpecifiedTiming().mDelay);
 }
 
-
 // https://w3c.github.io/web-animations/#silently-set-the-current-time
 void
 Animation::SilentlySetCurrentTime(const TimeDuration& aSeekTime)
 {
   if (!mHoldTime.IsNull() ||
       mStartTime.IsNull() ||
       !mTimeline ||
       mTimeline->GetCurrentTime().IsNull() ||
@@ -776,16 +775,54 @@ Animation::CancelNoUpdate()
 
   UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
 
   if (mTimeline) {
     mTimeline->RemoveAnimation(this);
   }
 }
 
+bool
+Animation::ShouldBeSynchronizedWithMainThread(
+  nsCSSPropertyID aProperty,
+  const nsIFrame* aFrame,
+  AnimationPerformanceWarning::Type& aPerformanceWarning) const
+{
+  // Only synchronize playing animations
+  if (!IsPlaying()) {
+    return false;
+  }
+
+  // Currently only transform animations need to be synchronized
+  if (aProperty != eCSSProperty_transform) {
+    return false;
+  }
+
+  KeyframeEffectReadOnly* keyframeEffect = mEffect
+                                           ? mEffect->AsKeyframeEffect()
+                                           : nullptr;
+  if (!keyframeEffect) {
+    return false;
+  }
+
+  // Are we starting at the same time as other geometric animations?
+  // We check this before calling ShouldBlockAsyncTransformAnimations, partly
+  // because it's cheaper, but also because it's often the most useful thing
+  // to know when you're debugging performance.
+  if (mSyncWithGeometricAnimations &&
+      keyframeEffect->HasAnimationOfProperty(eCSSProperty_transform)) {
+    aPerformanceWarning = AnimationPerformanceWarning::Type::
+                          TransformWithSyncGeometricAnimations;
+    return true;
+  }
+
+  return keyframeEffect->
+           ShouldBlockAsyncTransformAnimations(aFrame, aPerformanceWarning);
+}
+
 void
 Animation::UpdateRelevance()
 {
   bool wasRelevant = mIsRelevant;
   mIsRelevant = HasCurrentEffect() || IsInEffect();
 
   // Notify animation observers.
   if (wasRelevant && !mIsRelevant) {
@@ -934,16 +971,26 @@ Animation::NotifyEffectTimingUpdated()
 {
   MOZ_ASSERT(mEffect,
              "We should only update timing effect when we have a target "
              "effect");
   UpdateTiming(Animation::SeekFlag::NoSeek,
                Animation::SyncNotifyFlag::Async);
 }
 
+void
+Animation::NotifyGeometricAnimationsStartingThisFrame()
+{
+  if (!IsNewlyStarted() || !mEffect) {
+    return;
+  }
+
+  mSyncWithGeometricAnimations = true;
+}
+
 // https://w3c.github.io/web-animations/#play-an-animation
 void
 Animation::PlayNoUpdate(ErrorResult& aRv, LimitBehavior aLimitBehavior)
 {
   AutoMutationBatchForAnimation mb(*this);
 
   bool abortedPause = mPendingState == PendingState::PausePending;
 
@@ -999,16 +1046,21 @@ Animation::PlayNoUpdate(ErrorResult& aRv
 
   if (!reuseReadyPromise) {
     // Clear ready promise. We'll create a new one lazily.
     mReady = nullptr;
   }
 
   mPendingState = PendingState::PlayPending;
 
+  // Clear flag that causes us to sync transform animations with the main
+  // thread for now. We'll set this when we go to set up compositor
+  // animations if it applies.
+  mSyncWithGeometricAnimations = false;
+
   nsIDocument* doc = GetRenderedDocument();
   if (doc) {
     PendingAnimationTracker* tracker =
       doc->GetOrCreatePendingAnimationTracker();
     tracker->AddPlayPending(*this);
   } else {
     TriggerOnNextTick(Nullable<TimeDuration>());
   }
--- a/dom/animation/Animation.h
+++ b/dom/animation/Animation.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_Animation_h
 #define mozilla_dom_Animation_h
 
 #include "nsWrapperCache.h"
 #include "nsCycleCollectionParticipant.h"
+#include "mozilla/AnimationPerformanceWarning.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/DOMEventTargetHelper.h"
 #include "mozilla/EffectCompositor.h" // For EffectCompositor::CascadeLevel
 #include "mozilla/LinkedList.h"
 #include "mozilla/TimeStamp.h" // for TimeStamp, TimeDuration
 #include "mozilla/dom/AnimationBinding.h" // for AnimationPlayState
 #include "mozilla/dom/AnimationEffectReadOnly.h"
 #include "mozilla/dom/AnimationTimeline.h"
@@ -30,17 +31,17 @@
 // GetTickCount().
 #ifdef GetCurrentTime
 #undef GetCurrentTime
 #endif
 
 struct JSContext;
 class nsCSSPropertyIDSet;
 class nsIDocument;
-class nsPresContext;
+class nsIFrame;
 
 namespace mozilla {
 
 class AnimValuesStyleRule;
 
 namespace dom {
 
 class CSSAnimation;
@@ -57,16 +58,17 @@ public:
   explicit Animation(nsIGlobalObject* aGlobal)
     : DOMEventTargetHelper(aGlobal)
     , mPlaybackRate(1.0)
     , mPendingState(PendingState::NotPending)
     , mAnimationIndex(sNextAnimationIndex++)
     , mFinishedAtLastComposeStyle(false)
     , mIsRelevant(false)
     , mFinishedIsResolved(false)
+    , mSyncWithGeometricAnimations(false)
   {
   }
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(Animation,
                                            DOMEventTargetHelper)
 
   nsIGlobalObject* GetParentObject() const { return GetOwnerGlobal(); }
@@ -276,16 +278,21 @@ public:
 
   bool IsPlaying() const
   {
     return mPlaybackRate != 0.0 &&
            (PlayState() == AnimationPlayState::Running ||
             mPendingState == PendingState::PlayPending);
   }
 
+  bool ShouldBeSynchronizedWithMainThread(
+    nsCSSPropertyID aProperty,
+    const nsIFrame* aFrame,
+    AnimationPerformanceWarning::Type& aPerformanceWarning) const;
+
   bool IsRelevant() const { return mIsRelevant; }
   void UpdateRelevance();
 
   /**
    * Returns true if this Animation has a lower composite order than aOther.
    */
   bool HasLowerCompositeOrderThan(const Animation& aOther) const;
 
@@ -309,16 +316,17 @@ public:
    * if any.
    * Any properties contained in |aPropertiesToSkip| will not be added or
    * updated in |aStyleRule|.
    */
   void ComposeStyle(RefPtr<AnimValuesStyleRule>& aStyleRule,
                     const nsCSSPropertyIDSet& aPropertiesToSkip);
 
   void NotifyEffectTimingUpdated();
+  void NotifyGeometricAnimationsStartingThisFrame();
 
 protected:
   void SilentlySetCurrentTime(const TimeDuration& aNewCurrentTime);
   void SilentlySetPlaybackRate(double aPlaybackRate);
   void CancelNoUpdate();
   void PlayNoUpdate(ErrorResult& aRv, LimitBehavior aLimitBehavior);
   void PauseNoUpdate(ErrorResult& aRv);
   void ResumeAt(const TimeDuration& aReadyTime);
@@ -369,16 +377,28 @@ protected:
   void CancelPendingTasks();
 
   /**
    * Performs the same steps as CancelPendingTasks and also rejects and
    * recreates the ready promise if the animation was pending.
    */
   void ResetPendingTasks();
 
+  /**
+   * Returns true if this animation is not only play-pending, but has
+   * yet to be given a pending ready time. This roughly corresponds to
+   * animations that are waiting to be painted (since we set the pending
+   * ready time at the end of painting). Identifying such animations is
+   * useful because in some cases animations that are painted together
+   * may need to be synchronized.
+   */
+  bool IsNewlyStarted() const {
+    return mPendingState == PendingState::PlayPending &&
+           mPendingReadyTime.IsNull();
+  }
   bool IsPossiblyOrphanedPendingAnimation() const;
   StickyTimeDuration EffectEnd() const;
 
   nsIDocument* GetRenderedDocument() const;
 
   RefPtr<AnimationTimeline> mTimeline;
   RefPtr<AnimationEffectReadOnly> mEffect;
   // The beginning of the delay period.
@@ -428,15 +448,20 @@ protected:
 
   nsRevocableEventPtr<nsRunnableMethod<Animation>> mFinishNotificationTask;
   // True if mFinished is resolved or would be resolved if mFinished has
   // yet to be created. This is not set when mFinished is rejected since
   // in that case mFinished is immediately reset to represent a new current
   // finished promise.
   bool mFinishedIsResolved;
 
+  // True if this animation was triggered at the same time as one or more
+  // geometric animations and hence we should run any transform animations on
+  // the main thread.
+  bool mSyncWithGeometricAnimations;
+
   nsString mId;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_Animation_h
--- a/dom/animation/AnimationEffectReadOnly.h
+++ b/dom/animation/AnimationEffectReadOnly.h
@@ -80,16 +80,24 @@ public:
   // Shortcut that gets the computed timing using the current local time as
   // calculated from the timeline time.
   ComputedTiming GetComputedTiming(const TimingParams* aTiming = nullptr) const;
   void GetComputedTimingAsDict(ComputedTimingProperties& aRetVal) const;
 
   virtual void SetAnimation(Animation* aAnimation) = 0;
   Animation* GetAnimation() const { return mAnimation; };
 
+  /**
+   * Returns true if this effect animates one of the properties we consider
+   * geometric properties, e.g. properties such as 'width' or 'margin-left'
+   * that we try to synchronize with transform animations, on a valid target
+   * element.
+   */
+  virtual bool AffectsGeometry() const = 0;
+
 protected:
   virtual ~AnimationEffectReadOnly();
 
   Nullable<TimeDuration> GetLocalTime() const;
 
 protected:
   RefPtr<nsIDocument> mDocument;
   RefPtr<AnimationEffectTimingReadOnly> mTiming;
--- a/dom/animation/AnimationPerformanceWarning.cpp
+++ b/dom/animation/AnimationPerformanceWarning.cpp
@@ -54,16 +54,19 @@ AnimationPerformanceWarning::ToLocalized
       key = "CompositorAnimationWarningTransformPreserve3D";
       break;
     case Type::TransformSVG:
       key = "CompositorAnimationWarningTransformSVG";
       break;
     case Type::TransformWithGeometricProperties:
       key = "CompositorAnimationWarningTransformWithGeometricProperties";
       break;
+    case Type::TransformWithSyncGeometricAnimations:
+      key = "CompositorAnimationWarningTransformWithSyncGeometricAnimations";
+      break;
     case Type::TransformFrameInactive:
       key = "CompositorAnimationWarningTransformFrameInactive";
       break;
     case Type::OpacityFrameInactive:
       key = "CompositorAnimationWarningOpacityFrameInactive";
       break;
     case Type::HasRenderingObserver:
       key = "CompositorAnimationWarningHasRenderingObserver";
--- a/dom/animation/AnimationPerformanceWarning.h
+++ b/dom/animation/AnimationPerformanceWarning.h
@@ -4,30 +4,34 @@
  * 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_AnimationPerformanceWarning_h
 #define mozilla_dom_AnimationPerformanceWarning_h
 
 #include <initializer_list>
 
+#include "mozilla/Maybe.h"
+#include "nsTArray.h"
+
 class nsXPIDLString;
 
 namespace mozilla {
 
 // Represents the reason why we can't run the CSS property on the compositor.
 struct AnimationPerformanceWarning
 {
   enum class Type : uint8_t {
     ContentTooSmall,
     ContentTooLarge,
     TransformBackfaceVisibilityHidden,
     TransformPreserve3D,
     TransformSVG,
     TransformWithGeometricProperties,
+    TransformWithSyncGeometricAnimations,
     TransformFrameInactive,
     OpacityFrameInactive,
     HasRenderingObserver,
   };
 
   explicit AnimationPerformanceWarning(Type aType)
     : mType(aType) { }
 
--- a/dom/animation/AnimationUtils.cpp
+++ b/dom/animation/AnimationUtils.cpp
@@ -1,17 +1,16 @@
 /* -*- 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 "AnimationUtils.h"
 
-#include "nsContentUtils.h" // For nsContentUtils::IsCallerChrome
 #include "nsDebug.h"
 #include "nsIAtom.h"
 #include "nsIContent.h"
 #include "nsIDocument.h"
 #include "nsGlobalWindow.h"
 #include "nsString.h"
 #include "xpcpublic.h" // For xpc::NativeGlobal
 #include "mozilla/Preferences.h"
@@ -74,14 +73,14 @@ AnimationUtils::IsCoreAPIEnabled()
     Preferences::AddBoolVarCache(&sCoreAPIEnabled,
                                  "dom.animations-api.core.enabled");
   }
 
   return sCoreAPIEnabled;
 }
 
 /* static */ bool
-AnimationUtils::IsCoreAPIEnabledForCaller()
+AnimationUtils::IsCoreAPIEnabledForCaller(dom::CallerType aCallerType)
 {
-  return IsCoreAPIEnabled() || nsContentUtils::IsCallerChrome();
+  return IsCoreAPIEnabled() || aCallerType == dom::CallerType::System;
 }
 
 } // namespace mozilla
--- a/dom/animation/AnimationUtils.h
+++ b/dom/animation/AnimationUtils.h
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_AnimationUtils_h
 #define mozilla_dom_AnimationUtils_h
 
 #include "mozilla/TimeStamp.h"
+#include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/Nullable.h"
 #include "nsStringFwd.h"
 
 class nsIContent;
 class nsIDocument;
 struct JSContext;
 
 namespace mozilla {
@@ -66,14 +67,14 @@ public:
    * true.
    */
   static bool IsCoreAPIEnabled();
 
   /**
    * Returns true if the preference to enable the core Web Animations API is
    * true or the caller is chrome.
    */
-  static bool IsCoreAPIEnabledForCaller();
+  static bool IsCoreAPIEnabledForCaller(dom::CallerType aCallerType);
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/animation/EffectCompositor.cpp
+++ b/dom/animation/EffectCompositor.cpp
@@ -80,37 +80,35 @@ IsMatchForCompositor(const KeyframeEffec
 {
   const Animation* animation = aEffect.GetAnimation();
   MOZ_ASSERT(animation);
 
   if (!animation->IsRelevant()) {
     return MatchForCompositor::No;
   }
 
-  bool isPlaying = animation->IsPlaying();
-
-  // If we are finding animations for transform, check if there are other
-  // animations that should block the transform animation. e.g. geometric
-  // properties' animation. This check should be done regardless of whether
-  // the effect has the target property |aProperty| or not.
   AnimationPerformanceWarning::Type warningType;
-  if (aProperty == eCSSProperty_transform &&
-      isPlaying &&
-      aEffect.ShouldBlockAsyncTransformAnimations(aFrame, warningType)) {
+  if (animation->ShouldBeSynchronizedWithMainThread(aProperty, aFrame,
+                                                    warningType)) {
     EffectCompositor::SetPerformanceWarning(
       aFrame, aProperty,
       AnimationPerformanceWarning(warningType));
+    // For a given |aFrame|, we don't want some animations of |aProperty| to
+    // run on the compositor and others to run on the main thread, so if any
+    // need to be synchronized with the main thread, run them all there.
     return MatchForCompositor::NoAndBlockThisProperty;
   }
 
   if (!aEffect.HasEffectiveAnimationOfProperty(aProperty)) {
     return MatchForCompositor::No;
   }
 
-  return isPlaying ? MatchForCompositor::Yes : MatchForCompositor::IfNeeded;
+  return animation->IsPlaying()
+         ? MatchForCompositor::Yes
+         : MatchForCompositor::IfNeeded;
 }
 
 // Helper function to factor out the common logic from
 // GetAnimationsForCompositor and HasAnimationsForCompositor.
 //
 // Takes an optional array to fill with eligible animations.
 //
 // Returns true if there are eligible animations, false otherwise.
@@ -122,16 +120,28 @@ FindAnimationsForCompositor(const nsIFra
   MOZ_ASSERT(!aMatches || aMatches->IsEmpty(),
              "Matches array, if provided, should be empty");
 
   EffectSet* effects = EffectSet::GetEffectSet(aFrame);
   if (!effects || effects->IsEmpty()) {
     return false;
   }
 
+  // First check for newly-started transform animations that should be
+  // synchronized with geometric animations. We need to do this before any
+  // other early returns (the one above is ok) since we can only check this
+  // state when the animation is newly-started.
+  if (aProperty == eCSSProperty_transform) {
+    PendingAnimationTracker* tracker =
+      aFrame->PresContext()->Document()->GetPendingAnimationTracker();
+    if (tracker) {
+      tracker->MarkAnimationsThatMightNeedSynchronization();
+    }
+  }
+
   // If the property will be added to the animations level of the cascade but
   // there is an !important rule for that property in the cascade then the
   // animation will not be applied since the !important rule overrides it.
   if (effects->PropertiesWithImportantRules().HasProperty(aProperty) &&
       effects->PropertiesForAnimationsLevel().HasProperty(aProperty)) {
     return false;
   }
 
@@ -185,16 +195,19 @@ FindAnimationsForCompositor(const nsIFra
   }
 
   bool foundRunningAnimations = false;
   for (KeyframeEffectReadOnly* effect : *effects) {
     MatchForCompositor matchResult =
       IsMatchForCompositor(*effect, aProperty, aFrame);
 
     if (matchResult == MatchForCompositor::NoAndBlockThisProperty) {
+      // For a given |aFrame|, we don't want some animations of |aProperty| to
+      // run on the compositor and others to run on the main thread, so if any
+      // need to be synchronized with the main thread, run them all there.
       if (aMatches) {
         aMatches->Clear();
       }
       return false;
     }
 
     if (matchResult == MatchForCompositor::No) {
       continue;
--- a/dom/animation/KeyframeEffect.cpp
+++ b/dom/animation/KeyframeEffect.cpp
@@ -128,21 +128,22 @@ KeyframeEffect::SetTarget(const Nullable
   } else if (mEffectOptions.mSpacingMode == SpacingMode::paced) {
     // New target is null, so fall back to distribute spacing.
     KeyframeUtils::ApplyDistributeSpacing(mKeyframes);
   }
 }
 
 void
 KeyframeEffect::SetIterationComposite(
-  const IterationCompositeOperation& aIterationComposite)
+  const IterationCompositeOperation& aIterationComposite,
+  CallerType aCallerType)
 {
   // Ignore iterationComposite if the Web Animations API is not enabled,
   // then the default value 'Replace' will be used.
-  if (!AnimationUtils::IsCoreAPIEnabledForCaller()) {
+  if (!AnimationUtils::IsCoreAPIEnabledForCaller(aCallerType)) {
     return;
   }
 
   if (mEffectOptions.mIterationComposite == aIterationComposite) {
     return;
   }
 
   if (mAnimation && mAnimation->IsRelevant()) {
@@ -151,25 +152,27 @@ KeyframeEffect::SetIterationComposite(
 
   mEffectOptions.mIterationComposite = aIterationComposite;
   RequestRestyle(EffectCompositor::RestyleType::Layer);
 }
 
 void
 KeyframeEffect::SetSpacing(JSContext* aCx,
                            const nsAString& aSpacing,
+                           CallerType aCallerType,
                            ErrorResult& aRv)
 {
   SpacingMode spacingMode = SpacingMode::distribute;
   nsCSSPropertyID pacedProperty = eCSSProperty_UNKNOWN;
   nsAutoString invalidPacedProperty;
   KeyframeEffectParams::ParseSpacing(aSpacing,
                                      spacingMode,
                                      pacedProperty,
                                      invalidPacedProperty,
+                                     aCallerType,
                                      aRv);
   if (aRv.Failed()) {
     return;
   }
 
   if (!invalidPacedProperty.IsEmpty()) {
     const char16_t* params[] = { invalidPacedProperty.get() };
     nsIDocument* doc = AnimationUtils::GetCurrentRealmDocument(aCx);
--- a/dom/animation/KeyframeEffect.h
+++ b/dom/animation/KeyframeEffect.h
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_dom_KeyframeEffect_h
 #define mozilla_dom_KeyframeEffect_h
 
 #include "nsWrapperCache.h"
+#include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/KeyframeEffectReadOnly.h"
 #include "mozilla/AnimationTarget.h" // For (Non)OwningAnimationTarget
 #include "mozilla/Maybe.h"
 
 struct JSContext;
 class JSObject;
 class nsIDocument;
 
@@ -66,17 +67,27 @@ public:
 
   // This method calls GetTargetStyleContext which is not safe to use when
   // we are in the middle of updating style. If we need to use this when
   // updating style, we should pass the nsStyleContext into this method and use
   // that to update the properties rather than calling
   // GetStyleContextForElement.
   void SetTarget(const Nullable<ElementOrCSSPseudoElement>& aTarget);
 
-  void SetSpacing(JSContext* aCx, const nsAString& aSpacing, ErrorResult& aRv);
+  void GetSpacing(nsString& aRetVal, CallerType aCallerType)
+  {
+    KeyframeEffectReadOnly::GetSpacing(aRetVal);
+  }
+  void SetSpacing(JSContext* aCx, const nsAString& aSpacing,
+                  CallerType aCallerType, ErrorResult& aRv);
+  IterationCompositeOperation IterationComposite(CallerType aCallerType)
+  {
+    return KeyframeEffectReadOnly::IterationComposite();
+  }
   void SetIterationComposite(
-    const IterationCompositeOperation& aIterationComposite);
+    const IterationCompositeOperation& aIterationComposite,
+    CallerType aCallerType);
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_KeyframeEffect_h
--- a/dom/animation/KeyframeEffectParams.cpp
+++ b/dom/animation/KeyframeEffectParams.cpp
@@ -104,23 +104,24 @@ ConsumeIdentToken(RangedPtr<const char16
   }
 }
 
 /* static */ void
 KeyframeEffectParams::ParseSpacing(const nsAString& aSpacing,
                                    SpacingMode& aSpacingMode,
                                    nsCSSPropertyID& aPacedProperty,
                                    nsAString& aInvalidPacedProperty,
+                                   dom::CallerType aCallerType,
                                    ErrorResult& aRv)
 {
   aInvalidPacedProperty.Truncate();
 
   // Ignore spacing if the core API is not enabled since it is not yet ready to
   // ship.
-  if (!AnimationUtils::IsCoreAPIEnabledForCaller()) {
+  if (!AnimationUtils::IsCoreAPIEnabledForCaller(aCallerType)) {
     aSpacingMode = SpacingMode::distribute;
     return;
   }
 
   // Parse spacing.
   // distribute | paced({ident})
   // https://w3c.github.io/web-animations/#dom-keyframeeffectreadonly-spacing
   // 1. distribute spacing.
--- a/dom/animation/KeyframeEffectParams.h
+++ b/dom/animation/KeyframeEffectParams.h
@@ -9,16 +9,17 @@
 
 #include "nsCSSProps.h"
 #include "nsString.h"
 // X11 has a #define for None
 #ifdef None
 #undef None
 #endif
 #include "mozilla/dom/KeyframeEffectBinding.h" // IterationCompositeOperation
+#include "mozilla/dom/BindingDeclarations.h"   // CallerType
 
 namespace mozilla {
 
 class ErrorResult;
 
 enum class SpacingMode
 {
   distribute,
@@ -49,16 +50,17 @@ struct KeyframeEffectParams
    *                                    is not a recognized animatable property,
    *                                    will be set to <ident>.
    * @param [out] aRv The error result.
    */
   static void ParseSpacing(const nsAString& aSpacing,
                            SpacingMode& aSpacingMode,
                            nsCSSPropertyID& aPacedProperty,
                            nsAString& aInvalidPacedProperty,
+                           dom::CallerType aCallerType,
                            ErrorResult& aRv);
 
   dom::IterationCompositeOperation mIterationComposite =
     dom::IterationCompositeOperation::Replace;
   dom::CompositeOperation mComposite = dom::CompositeOperation::Replace;
   SpacingMode mSpacingMode = SpacingMode::distribute;
   nsCSSPropertyID mPacedProperty = eCSSProperty_UNKNOWN;
 };
--- a/dom/animation/KeyframeEffectReadOnly.cpp
+++ b/dom/animation/KeyframeEffectReadOnly.cpp
@@ -563,30 +563,32 @@ KeyframeEffectOptionsFromUnion(
   MOZ_ASSERT(aOptions.IsKeyframeAnimationOptions());
   return aOptions.GetAsKeyframeAnimationOptions();
 }
 
 template <class OptionsType>
 static KeyframeEffectParams
 KeyframeEffectParamsFromUnion(const OptionsType& aOptions,
                               nsAString& aInvalidPacedProperty,
+                              CallerType aCallerType,
                               ErrorResult& aRv)
 {
   KeyframeEffectParams result;
   if (!aOptions.IsUnrestrictedDouble()) {
     const KeyframeEffectOptions& options =
       KeyframeEffectOptionsFromUnion(aOptions);
     KeyframeEffectParams::ParseSpacing(options.mSpacing,
                                        result.mSpacingMode,
                                        result.mPacedProperty,
                                        aInvalidPacedProperty,
+                                       aCallerType,
                                        aRv);
     // Ignore iterationComposite if the Web Animations API is not enabled,
     // then the default value 'Replace' will be used.
-    if (AnimationUtils::IsCoreAPIEnabledForCaller()) {
+    if (AnimationUtils::IsCoreAPIEnabledForCaller(aCallerType)) {
       result.mIterationComposite = options.mIterationComposite;
       // FIXME: Bug 1311620: We don't support additive animation yet.
       if (options.mComposite != dom::CompositeOperation::Add) {
         result.mComposite = options.mComposite;
       }
     }
   }
   return result;
@@ -634,17 +636,18 @@ KeyframeEffectReadOnly::ConstructKeyfram
   TimingParams timingParams =
     TimingParams::FromOptionsUnion(aOptions, doc, aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
 
   nsAutoString invalidPacedProperty;
   KeyframeEffectParams effectOptions =
-    KeyframeEffectParamsFromUnion(aOptions, invalidPacedProperty, aRv);
+    KeyframeEffectParamsFromUnion(aOptions, invalidPacedProperty,
+                                  aGlobal.CallerType(), aRv);
   if (aRv.Failed()) {
     return nullptr;
   }
 
   if (!invalidPacedProperty.IsEmpty()) {
     const char16_t* params[] = { invalidPacedProperty.get() };
     nsContentUtils::ReportToConsole(nsIScriptError::warningFlag,
                                     NS_LITERAL_CSTRING("Animation"),
@@ -1250,20 +1253,31 @@ KeyframeEffectReadOnly::GetPresContext()
   }
   return shell->GetPresContext();
 }
 
 /* static */ bool
 KeyframeEffectReadOnly::IsGeometricProperty(
   const nsCSSPropertyID aProperty)
 {
+  MOZ_ASSERT(!nsCSSProps::IsShorthand(aProperty),
+             "Property should be a longhand property");
+
   switch (aProperty) {
     case eCSSProperty_bottom:
     case eCSSProperty_height:
     case eCSSProperty_left:
+    case eCSSProperty_margin_bottom:
+    case eCSSProperty_margin_left:
+    case eCSSProperty_margin_right:
+    case eCSSProperty_margin_top:
+    case eCSSProperty_padding_bottom:
+    case eCSSProperty_padding_left:
+    case eCSSProperty_padding_right:
+    case eCSSProperty_padding_top:
     case eCSSProperty_right:
     case eCSSProperty_top:
     case eCSSProperty_width:
       return true;
     default:
       return false;
   }
 }
@@ -1300,22 +1314,16 @@ KeyframeEffectReadOnly::CanAnimateTransf
   return true;
 }
 
 bool
 KeyframeEffectReadOnly::ShouldBlockAsyncTransformAnimations(
   const nsIFrame* aFrame,
   AnimationPerformanceWarning::Type& aPerformanceWarning) const
 {
-  // We currently only expect this method to be called for effects whose
-  // animations are eligible for the compositor since, Animations that are
-  // paused, zero-duration, finished etc. should not block other animations from
-  // running on the compositor.
-  MOZ_ASSERT(mAnimation && mAnimation->IsPlaying());
-
   EffectSet* effectSet =
     EffectSet::GetEffectSet(mTarget->mElement, mTarget->mPseudoType);
   for (const AnimationProperty& property : mProperties) {
     // If there is a property for animations level that is overridden by
     // !important rules, it should not block other animations from running
     // on the compositor.
     // NOTE: We don't currently check for !important rules for properties that
     // don't run on the compositor. As result such properties (e.g. margin-left)
@@ -1342,16 +1350,28 @@ KeyframeEffectReadOnly::ShouldBlockAsync
         return true;
       }
     }
   }
 
   return false;
 }
 
+bool
+KeyframeEffectReadOnly::HasGeometricProperties() const
+{
+  for (const AnimationProperty& property : mProperties) {
+    if (IsGeometricProperty(property.mProperty)) {
+      return true;
+    }
+  }
+
+  return false;
+}
+
 void
 KeyframeEffectReadOnly::SetPerformanceWarning(
   nsCSSPropertyID aProperty,
   const AnimationPerformanceWarning& aWarning)
 {
   for (AnimationProperty& property : mProperties) {
     if (property.mProperty == aProperty &&
         (!property.mPerformanceWarning ||
--- a/dom/animation/KeyframeEffectReadOnly.h
+++ b/dom/animation/KeyframeEffectReadOnly.h
@@ -19,16 +19,17 @@
 #include "mozilla/Attributes.h"
 #include "mozilla/ComputedTimingFunction.h"
 #include "mozilla/EffectCompositor.h"
 #include "mozilla/KeyframeEffectParams.h"
 #include "mozilla/ServoBindingTypes.h" // RawServoDeclarationBlock and
                                        // associated RefPtrTraits
 #include "mozilla/StyleAnimationValue.h"
 #include "mozilla/dom/AnimationEffectReadOnly.h"
+#include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/Element.h"
 
 struct JSContext;
 class JSObject;
 class nsIContent;
 class nsIDocument;
 class nsIFrame;
 class nsIPresShell;
@@ -311,16 +312,21 @@ public:
   // and 'top' on an element, we force all 'transform' animations running at
   // the same time on the same element to run on the main thread.
   //
   // When returning true, |aPerformanceWarning| stores the reason why
   // we shouldn't run the transform animations.
   bool ShouldBlockAsyncTransformAnimations(
     const nsIFrame* aFrame,
     AnimationPerformanceWarning::Type& aPerformanceWarning) const;
+  bool HasGeometricProperties() const;
+  bool AffectsGeometry() const override
+  {
+    return GetTarget() && HasGeometricProperties();
+  }
 
   nsIDocument* GetRenderedDocument() const;
   nsPresContext* GetPresContext() const;
   nsIPresShell* GetPresShell() const;
 
   // Associates a warning with the animated property on the specified frame
   // indicating why, for example, the property could not be animated on the
   // compositor. |aParams| and |aParamsLength| are optional parameters which
--- a/dom/animation/KeyframeUtils.cpp
+++ b/dom/animation/KeyframeUtils.cpp
@@ -507,18 +507,18 @@ KeyframeUtils::ApplySpacing(nsTArray<Key
     firstElement.mComputedOffset = firstElement.mOffset.valueOr(0.0);
     // We will fill in the last keyframe's offset below
   } else {
     Keyframe& lastElement = aKeyframes.LastElement();
     lastElement.mComputedOffset = lastElement.mOffset.valueOr(1.0);
   }
 
   // Fill in remaining missing offsets.
-  const Keyframe* const last = aKeyframes.cend() - 1;
-  const RangedPtr<Keyframe> begin(aKeyframes.begin(), aKeyframes.Length());
+  const Keyframe* const last = &aKeyframes.LastElement();
+  const RangedPtr<Keyframe> begin(aKeyframes.Elements(), aKeyframes.Length());
   RangedPtr<Keyframe> keyframeA = begin;
   while (keyframeA != last) {
     // Find keyframe A and keyframe B *between* which we will apply spacing.
     RangedPtr<Keyframe> keyframeB = keyframeA + 1;
     while (keyframeB->mOffset.isNothing() && keyframeB != last) {
       ++keyframeB;
     }
     keyframeB->mComputedOffset = keyframeB->mOffset.valueOr(1.0);
--- a/dom/animation/PendingAnimationTracker.cpp
+++ b/dom/animation/PendingAnimationTracker.cpp
@@ -80,30 +80,85 @@ PendingAnimationTracker::TriggerPendingA
       animation->TriggerOnNextTick(readyTime);
 
       iter.Remove();
     }
   };
 
   triggerAnimationsAtReadyTime(mPlayPendingSet);
   triggerAnimationsAtReadyTime(mPausePendingSet);
+
+  mHasPlayPendingGeometricAnimations = mPlayPendingSet.Count()
+                                       ? CheckState::Indeterminate
+                                       : CheckState::Absent;
 }
 
 void
 PendingAnimationTracker::TriggerPendingAnimationsNow()
 {
   auto triggerAndClearAnimations = [](AnimationSet& aAnimationSet) {
     for (auto iter = aAnimationSet.Iter(); !iter.Done(); iter.Next()) {
       iter.Get()->GetKey()->TriggerNow();
     }
     aAnimationSet.Clear();
   };
 
   triggerAndClearAnimations(mPlayPendingSet);
   triggerAndClearAnimations(mPausePendingSet);
+
+  mHasPlayPendingGeometricAnimations = CheckState::Absent;
+}
+
+void
+PendingAnimationTracker::MarkAnimationsThatMightNeedSynchronization()
+{
+  // We only ever set mHasPlayPendingGeometricAnimations to 'present' in
+  // HasPlayPendingGeometricAnimations(). So, if it is 'present' already,
+  // (i.e. before calling HasPlayPendingGeometricAnimations()) we can assume
+  // that this method has already been called for the current set of
+  // play-pending animations and it is not necessary to run again.
+  //
+  // We can't make the same assumption about 'absent', but if this method
+  // was already called and the result was 'absent', then this method is
+  // a no-op anyway so it's ok to run again.
+  //
+  // Note that *without* this optimization, starting animations would become
+  // O(n^2) in that case where each animation is on a different element and
+  // contains a compositor-animatable property since we would end up iterating
+  // over all animations in the play-pending set for each target element.
+  if (mHasPlayPendingGeometricAnimations == CheckState::Present) {
+    return;
+  }
+
+  if (!HasPlayPendingGeometricAnimations()) {
+    return;
+  }
+
+  for (auto iter = mPlayPendingSet.Iter(); !iter.Done(); iter.Next()) {
+    iter.Get()->GetKey()->NotifyGeometricAnimationsStartingThisFrame();
+  }
+}
+
+bool
+PendingAnimationTracker::HasPlayPendingGeometricAnimations()
+{
+  if (mHasPlayPendingGeometricAnimations != CheckState::Indeterminate) {
+    return mHasPlayPendingGeometricAnimations == CheckState::Present;
+  }
+
+  mHasPlayPendingGeometricAnimations = CheckState::Absent;
+  for (auto iter = mPlayPendingSet.ConstIter(); !iter.Done(); iter.Next()) {
+    auto animation = iter.Get()->GetKey();
+    if (animation->GetEffect() && animation->GetEffect()->AffectsGeometry()) {
+      mHasPlayPendingGeometricAnimations = CheckState::Present;
+      break;
+    }
+  }
+
+  return mHasPlayPendingGeometricAnimations == CheckState::Present;
 }
 
 void
 PendingAnimationTracker::EnsurePaintIsScheduled()
 {
   if (!mDocument) {
     return;
   }
--- a/dom/animation/PendingAnimationTracker.h
+++ b/dom/animation/PendingAnimationTracker.h
@@ -26,20 +26,22 @@ public:
   NS_INLINE_DECL_CYCLE_COLLECTING_NATIVE_REFCOUNTING(PendingAnimationTracker)
   NS_DECL_CYCLE_COLLECTION_NATIVE_CLASS(PendingAnimationTracker)
 
   void AddPlayPending(dom::Animation& aAnimation)
   {
     MOZ_ASSERT(!IsWaitingToPause(aAnimation),
                "Animation is already waiting to pause");
     AddPending(aAnimation, mPlayPendingSet);
+    mHasPlayPendingGeometricAnimations = CheckState::Indeterminate;
   }
   void RemovePlayPending(dom::Animation& aAnimation)
   {
     RemovePending(aAnimation, mPlayPendingSet);
+    mHasPlayPendingGeometricAnimations = CheckState::Indeterminate;
   }
   bool IsWaitingToPlay(const dom::Animation& aAnimation) const
   {
     return IsWaiting(aAnimation, mPlayPendingSet);
   }
 
   void AddPausePending(dom::Animation& aAnimation)
   {
@@ -57,28 +59,43 @@ public:
   }
 
   void TriggerPendingAnimationsOnNextTick(const TimeStamp& aReadyTime);
   void TriggerPendingAnimationsNow();
   bool HasPendingAnimations() const {
     return mPlayPendingSet.Count() > 0 || mPausePendingSet.Count() > 0;
   }
 
+  /**
+   * Looks amongst the set of play-pending animations, and, if there are
+   * animations that affect geometric properties, notifies all play-pending
+   * animations so that they can be synchronized, if needed.
+   */
+  void MarkAnimationsThatMightNeedSynchronization();
+
 private:
   ~PendingAnimationTracker() { }
 
+  bool HasPlayPendingGeometricAnimations();
   void EnsurePaintIsScheduled();
 
   typedef nsTHashtable<nsRefPtrHashKey<dom::Animation>> AnimationSet;
 
   void AddPending(dom::Animation& aAnimation, AnimationSet& aSet);
   void RemovePending(dom::Animation& aAnimation, AnimationSet& aSet);
   bool IsWaiting(const dom::Animation& aAnimation,
                  const AnimationSet& aSet) const;
 
   AnimationSet mPlayPendingSet;
   AnimationSet mPausePendingSet;
   nsCOMPtr<nsIDocument> mDocument;
+
+  enum class CheckState {
+    Indeterminate,
+    Absent,
+    Present
+  };
+  CheckState mHasPlayPendingGeometricAnimations = CheckState::Indeterminate;
 };
 
 } // namespace mozilla
 
 #endif // mozilla_dom_PendingAnimationTracker_h
--- a/dom/animation/test/chrome/test_animation_performance_warning.html
+++ b/dom/animation/test/chrome/test_animation_performance_warning.html
@@ -73,17 +73,17 @@ function assert_animation_property_state
                     'warning message should match');
     }
   }
 }
 
 // Check that the animation is running on compositor and
 // warning property is not set for the CSS property regardless
 // expected values.
-function assert_property_state_on_compositor(actual, expected) {
+function assert_all_properties_running_on_compositor(actual, expected) {
   assert_equals(actual.length, expected.length);
 
   var sortedActual = actual.sort(compare_property_state);
   var sortedExpected = expected.sort(compare_property_state);
 
   for (var i = 0; i < sortedActual.length; i++) {
     assert_equals(sortedActual[i].property,
                   sortedExpected[i].property,
@@ -91,538 +91,171 @@ function assert_property_state_on_compos
     assert_true(sortedActual[i].runningOnCompositor,
                 'runningOnCompositor property should be true on ' +
                 sortedActual[i].property);
     assert_not_exists(sortedActual[i], 'warning',
                       'warning property should not be set');
   }
 }
 
-var gAnimationsTests = [
-  {
-    desc: 'animations on compositor',
-    frames: {
-      opacity: [0, 1]
-    },
-    expected: [
-      {
-        property: 'opacity',
-        runningOnCompositor: true
-      }
-    ]
-  },
-  {
-    desc: 'animations on main thread',
-    frames: {
-      backgroundColor: ['white', 'red']
+function testBasicOperation() {
+  [
+    {
+      desc: 'animations on compositor',
+      frames: {
+        opacity: [0, 1]
+      },
+      expected: [
+        {
+          property: 'opacity',
+          runningOnCompositor: true
+        }
+      ]
     },
-    expected: [
-      {
-        property: 'background-color',
-        runningOnCompositor: false
-      }
-    ]
-  },
-  {
-    desc: 'animations on both threads',
-    frames: {
-      backgroundColor: ['white', 'red'],
-      transform: ['translate(0px)', 'translate(100px)']
-    },
-    expected: [
-      {
-        property: 'background-color',
-        runningOnCompositor: false
+    {
+      desc: 'animations on main thread',
+      frames: {
+        backgroundColor: ['white', 'red']
       },
-      {
-        property: 'transform',
-        runningOnCompositor: true
-      }
-    ]
-  },
-  {
-    desc: 'two animation properties on compositor thread',
-    frames: {
-      opacity: [0, 1],
-      transform: ['translate(0px)', 'translate(100px)']
+      expected: [
+        {
+          property: 'background-color',
+          runningOnCompositor: false
+        }
+      ]
     },
-    expected: [
-      {
-        property: 'opacity',
-        runningOnCompositor: true
+    {
+      desc: 'animations on both threads',
+      frames: {
+        backgroundColor: ['white', 'red'],
+        transform: ['translate(0px)', 'translate(100px)']
       },
-      {
-        property: 'transform',
-        runningOnCompositor: true
-      }
-    ]
-  },
-  {
-    desc: 'opacity on compositor with animation of geometric properties',
-    frames: {
-      width: ['100px', '200px'],
-      opacity: [0, 1]
-    },
-    expected: [
-      {
-        property: 'width',
-        runningOnCompositor: false
-      },
-      {
-        property: 'opacity',
-        runningOnCompositor: true
-      }
-    ]
-  },
-];
-
-// Test cases that check results of adding/removing a 'width' property on the
-// same animation object.
-var gAnimationWithGeometricKeyframeTests = [
-  {
-    desc: 'transform',
-    frames: {
-      transform: ['translate(0px)', 'translate(100px)']
-    },
-    expected: {
-      withoutGeometric: [
+      expected: [
+        {
+          property: 'background-color',
+          runningOnCompositor: false
+        },
         {
           property: 'transform',
           runningOnCompositor: true
         }
-      ],
-      withGeometric: [
-        {
-          property: 'width',
-          runningOnCompositor: false
-        },
-        {
-          property: 'transform',
-          runningOnCompositor: false,
-          warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
-        }
       ]
-    }
-  },
-  {
-    desc: 'opacity and transform',
-    frames: {
-      opacity: [0, 1],
-      transform: ['translate(0px)', 'translate(100px)']
     },
-    expected: {
-      withoutGeometric: [
+    {
+      desc: 'two animation properties on compositor thread',
+      frames: {
+        opacity: [0, 1],
+        transform: ['translate(0px)', 'translate(100px)']
+      },
+      expected: [
         {
           property: 'opacity',
           runningOnCompositor: true
         },
         {
           property: 'transform',
           runningOnCompositor: true
         }
-      ],
-      withGeometric: [
+      ]
+    },
+    {
+      desc: 'opacity on compositor with animation of geometric properties',
+      frames: {
+        width: ['100px', '200px'],
+        opacity: [0, 1]
+      },
+      expected: [
         {
           property: 'width',
           runningOnCompositor: false
         },
         {
           property: 'opacity',
           runningOnCompositor: true
-        },
-        {
-          property: 'transform',
-          runningOnCompositor: false,
-          warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
         }
       ]
-    }
-  },
-];
-
-// Performance warning tests that set and clear a style property.
-var gPerformanceWarningTestsStyle = [
-  {
-    desc: 'preserve-3d transform',
-    frames: {
-      transform: ['translate(0px)', 'translate(100px)']
     },
-    style: 'transform-style: preserve-3d',
-    expected: [
-      {
-        property: 'transform',
-        runningOnCompositor: false,
-        warning: 'CompositorAnimationWarningTransformPreserve3D'
-      }
-    ]
-  },
-  {
-    desc: 'transform with backface-visibility:hidden',
-    frames: {
-      transform: ['translate(0px)', 'translate(100px)']
-    },
-    style: 'backface-visibility: hidden;',
-    expected: [
-      {
-        property: 'transform',
-        runningOnCompositor: false,
-        warning: 'CompositorAnimationWarningTransformBackfaceVisibilityHidden'
-      }
-    ]
-  },
-  {
-    desc: 'opacity and transform with preserve-3d',
-    frames: {
-      opacity: [0, 1],
-      transform: ['translate(0px)', 'translate(100px)']
-    },
-    style: 'transform-style: preserve-3d',
-    expected: [
-      {
-        property: 'opacity',
-        runningOnCompositor: true
-      },
-      {
-        property: 'transform',
-        runningOnCompositor: false,
-        warning: 'CompositorAnimationWarningTransformPreserve3D'
-      }
-    ]
-  },
-  {
-    desc: 'opacity and transform with backface-visibility:hidden',
-    frames: {
-      opacity: [0, 1],
-      transform: ['translate(0px)', 'translate(100px)']
-    },
-    style: 'backface-visibility: hidden;',
-    expected: [
-      {
-        property: 'opacity',
-        runningOnCompositor: true
-      },
-      {
-        property: 'transform',
-        runningOnCompositor: false,
-        warning: 'CompositorAnimationWarningTransformBackfaceVisibilityHidden'
-      }
-    ]
-  },
-];
-
-// Performance warning tests that set and clear the id property
-var gPerformanceWarningTestsId= [
-  {
-    desc: 'moz-element referencing a transform',
-    frames: {
-      transform: ['translate(0px)', 'translate(100px)']
-    },
-    id: 'transformed',
-    createelement: 'width:100px; height:100px; background: -moz-element(#transformed)',
-    expected: [
-      {
-        property: 'transform',
-        runningOnCompositor: false,
-        warning: 'CompositorAnimationWarningHasRenderingObserver'
-      }
-    ]
-  },
-];
-
-var gMultipleAsyncAnimationsTests = [
-  {
-    desc: 'opacity and transform with preserve-3d',
-    style: 'transform-style: preserve-3d',
-    animations: [
-      {
-        frames: {
-          transform: ['translate(0px)', 'translate(100px)']
-        },
-        expected: [
-          {
-            property: 'transform',
-            runningOnCompositor: false,
-            warning: 'CompositorAnimationWarningTransformPreserve3D'
-          }
-        ]
-      },
-      {
-        frames: {
-          opacity: [0, 1]
-        },
-        expected: [
-          {
-            property: 'opacity',
-            runningOnCompositor: true,
-          }
-        ]
-      }
-    ],
-  },
-  {
-    desc: 'opacity and transform with backface-visibility:hidden',
-    style: 'backface-visibility: hidden;',
-    animations: [
-      {
-        frames: {
-          transform: ['translate(0px)', 'translate(100px)']
-        },
-        expected: [
-          {
-            property: 'transform',
-            runningOnCompositor: false,
-            warning: 'CompositorAnimationWarningTransformBackfaceVisibilityHidden'
-          }
-        ]
-      },
-      {
-        frames: {
-          opacity: [0, 1]
-        },
-        expected: [
-          {
-            property: 'opacity',
-            runningOnCompositor: true,
-          }
-        ]
-      }
-    ],
-  },
-];
-
-// Test cases that check results of adding/removing a 'width' keyframe on the
-// same animation object, where multiple animation objects belong to the same
-// element.
-// The 'width' property is added to animations[1].
-var gMultipleAsyncAnimationsWithGeometricKeyframeTests = [
-  {
-    desc: 'transform and opacity with geometric keyframes',
-    animations: [
-      {
-        frames: {
-          transform: ['translate(0px)', 'translate(100px)']
-        },
-        expected: {
-          withoutGeometric: [
-            {
-              property: 'transform',
-              runningOnCompositor: true
-            }
-          ],
-          withGeometric: [
-            {
-              property: 'transform',
-              runningOnCompositor: false,
-              warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
-            }
-          ]
-        }
-      },
-      {
-        frames: {
-          opacity: [0, 1]
-        },
-        expected: {
-          withoutGeometric: [
-            {
-              property: 'opacity',
-              runningOnCompositor: true,
-            }
-          ],
-          withGeometric: [
-            {
-              property: 'width',
-              runningOnCompositor: false,
-            },
-            {
-              property: 'opacity',
-              runningOnCompositor: true,
-            }
-          ]
-        }
-      }
-    ],
-  },
-  {
-    desc: 'opacity and transform with geometric keyframes',
-    animations: [
-      {
-        frames: {
-          opacity: [0, 1]
-        },
-        expected: {
-          withoutGeometric: [
-            {
-              property: 'opacity',
-              runningOnCompositor: true,
-            }
-          ],
-          withGeometric: [
-            {
-              property: 'opacity',
-              runningOnCompositor: true,
-            }
-          ]
-        }
-      },
-      {
-        frames: {
-          transform: ['translate(0px)', 'translate(100px)']
-        },
-        expected: {
-          withoutGeometric: [
-            {
-              property: 'transform',
-              runningOnCompositor: true
-            }
-          ],
-          withGeometric: [
-            {
-              property: 'width',
-              runningOnCompositor: false,
-            },
-            {
-              property: 'transform',
-              runningOnCompositor: false,
-              warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
-            }
-          ]
-        }
-      }
-    ]
-  },
-];
-
-// Test cases that check results of adding/removing 'width' animation on the
-// same element which has async animations.
-var gMultipleAsyncAnimationsWithGeometricAnimationTests = [
-  {
-    desc: 'transform',
-    animations: [
-      {
-        frames: {
-          transform: ['translate(0px)', 'translate(100px)']
-        },
-        expected: [
-          {
-            property: 'transform',
-            runningOnCompositor: false,
-            warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
-          }
-        ]
-      },
-    ]
-  },
-  {
-    desc: 'opacity',
-    animations: [
-      {
-        frames: {
-          opacity: [0, 1]
-        },
-        expected: [
-          {
-            property: 'opacity',
-            runningOnCompositor: true
-          }
-        ]
-      },
-    ]
-  },
-  {
-    desc: 'opacity and transform',
-    animations: [
-      {
-        frames: {
-          transform: ['translate(0px)', 'translate(100px)']
-        },
-        expected: [
-          {
-            property: 'transform',
-            runningOnCompositor: false,
-            warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
-          }
-        ]
-      },
-      {
-        frames: {
-          opacity: [0, 1]
-        },
-        expected: [
-          {
-            property: 'opacity',
-            runningOnCompositor: true,
-          }
-        ]
-      }
-    ],
-  },
-];
-
-var gAnimationsOnTooSmallElementTests = [
-  {
-    desc: 'opacity on too small element',
-    frames: {
-      opacity: [0, 1]
-    },
-    style: { style: 'width: 8px; height: 8px; background-color: red;' +
-                    // We need to set transform here to try creating an
-                    // individual frame for this opacity element.
-                    // Without this, this small element is created on the same
-                    // nsIFrame of mochitest iframe, i.e. the document which are
-                    // running this test, as a result the layer corresponding
-                    // to the frame is sent to compositor.
-                    'transform: translateX(100px);' },
-    expected: [
-      {
-        property: 'opacity',
-        runningOnCompositor: false,
-        warning: /Animation cannot be run on the compositor because frame size \(8, 8\) is smaller than \(16, 16\)/
-      }
-    ]
-  },
-  {
-    desc: 'transform on too small element',
-    frames: {
-      transform: ['translate(0px)', 'translate(100px)']
-    },
-    style: { style: 'width: 8px; height: 8px; background-color: red;' },
-    expected: [
-      {
-        property: 'transform',
-        runningOnCompositor: false,
-        warning: /Animation cannot be run on the compositor because frame size \(8, 8\) is smaller than \(16, 16\)/
-      }
-    ]
-  },
-];
-
-function start() {
-  var bundleService = SpecialPowers.Cc['@mozilla.org/intl/stringbundle;1']
-    .getService(SpecialPowers.Ci.nsIStringBundleService);
-  gStringBundle = bundleService
-    .createBundle("chrome://global/locale/layout_errors.properties");
-
-  gAnimationsTests.forEach(function(subtest) {
+  ].forEach(subtest => {
     promise_test(function(t) {
-      var animation = addDivAndAnimate(t,
-                                       { class: 'compositable' },
+      var animation = addDivAndAnimate(t, { class: 'compositable' },
                                        subtest.frames, 100 * MS_PER_SEC);
       return animation.ready.then(function() {
         assert_animation_property_state_equals(
           animation.effect.getProperties(),
           subtest.expected);
       });
     }, subtest.desc);
   });
+}
 
-  gAnimationWithGeometricKeyframeTests.forEach(function(subtest) {
+// Test adding/removing a 'width' property on the same animation object.
+function testKeyframesWithGeometricProperties() {
+  [
+    {
+      desc: 'transform',
+      frames: {
+        transform: ['translate(0px)', 'translate(100px)']
+      },
+      expected: {
+        withoutGeometric: [
+          {
+            property: 'transform',
+            runningOnCompositor: true
+          }
+        ],
+        withGeometric: [
+          {
+            property: 'width',
+            runningOnCompositor: false
+          },
+          {
+            property: 'transform',
+            runningOnCompositor: false,
+            warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
+          }
+        ]
+      }
+    },
+    {
+      desc: 'opacity and transform',
+      frames: {
+        opacity: [0, 1],
+        transform: ['translate(0px)', 'translate(100px)']
+      },
+      expected: {
+        withoutGeometric: [
+          {
+            property: 'opacity',
+            runningOnCompositor: true
+          },
+          {
+            property: 'transform',
+            runningOnCompositor: true
+          }
+        ],
+        withGeometric: [
+          {
+            property: 'width',
+            runningOnCompositor: false
+          },
+          {
+            property: 'opacity',
+            runningOnCompositor: true
+          },
+          {
+            property: 'transform',
+            runningOnCompositor: false,
+            warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
+          }
+        ]
+      }
+    },
+  ].forEach(subtest => {
     promise_test(function(t) {
-      var animation = addDivAndAnimate(t,
-                                       { class: 'compositable' },
+      var animation = addDivAndAnimate(t, { class: 'compositable' },
                                        subtest.frames, 100 * MS_PER_SEC);
       return animation.ready.then(function() {
         // First, a transform animation is running on compositor.
         assert_animation_property_state_equals(
           animation.effect.getProperties(),
           subtest.expected.withoutGeometric);
       }).then(function() {
         // Add a 'width' property.
@@ -651,218 +284,891 @@ function start() {
       }).then(function() {
         // Finally, the transform animation is running on compositor.
         assert_animation_property_state_equals(
           animation.effect.getProperties(),
           subtest.expected.withoutGeometric);
       });
     }, 'An animation has: ' + subtest.desc);
   });
+}
 
-  gPerformanceWarningTestsStyle.forEach(function(subtest) {
+// Test that the expected set of geometric properties all block transform
+// animations.
+function testSetOfGeometricProperties() {
+  const geometricProperties = [
+    'width', 'height',
+    'top', 'right', 'bottom', 'left',
+    'margin-top', 'margin-right', 'margin-bottom', 'margin-left',
+    'padding-top', 'padding-right', 'padding-bottom', 'padding-left'
+  ];
+
+  geometricProperties.forEach(property => {
     promise_test(function(t) {
-      var animation = addDivAndAnimate(t,
-                                       { class: 'compositable' },
+      const keyframes = {
+        [propertyToIDL(property)]: [ '100px', '200px' ],
+        transform: [ 'translate(0px)', 'translate(100px)' ]
+      };
+      var animation = addDivAndAnimate(t, { class: 'compositable' },
+                                       keyframes, 100 * MS_PER_SEC);
+
+      return animation.ready.then(function() {
+        assert_animation_property_state_equals(
+          animation.effect.getProperties(),
+          [
+            {
+              property,
+              runningOnCompositor: false
+            },
+            {
+              property: 'transform',
+              runningOnCompositor: false,
+              warning: 'CompositorAnimationWarningTransformWithSyncGeometricAnimations'
+            }
+          ]);
+      }, 'Transform animation should be run on the main thread');
+    }, `${property} is treated as a geometric property`);
+  });
+}
+
+// Performance warning tests that set and clear a style property.
+function testStyleChanges() {
+  [
+    {
+      desc: 'preserve-3d transform',
+      frames: {
+        transform: ['translate(0px)', 'translate(100px)']
+      },
+      style: 'transform-style: preserve-3d',
+      expected: [
+        {
+          property: 'transform',
+          runningOnCompositor: false,
+          warning: 'CompositorAnimationWarningTransformPreserve3D'
+        }
+      ]
+    },
+    {
+      desc: 'transform with backface-visibility:hidden',
+      frames: {
+        transform: ['translate(0px)', 'translate(100px)']
+      },
+      style: 'backface-visibility: hidden;',
+      expected: [
+        {
+          property: 'transform',
+          runningOnCompositor: false,
+          warning: 'CompositorAnimationWarningTransformBackfaceVisibilityHidden'
+        }
+      ]
+    },
+    {
+      desc: 'opacity and transform with preserve-3d',
+      frames: {
+        opacity: [0, 1],
+        transform: ['translate(0px)', 'translate(100px)']
+      },
+      style: 'transform-style: preserve-3d',
+      expected: [
+        {
+          property: 'opacity',
+          runningOnCompositor: true
+        },
+        {
+          property: 'transform',
+          runningOnCompositor: false,
+          warning: 'CompositorAnimationWarningTransformPreserve3D'
+        }
+      ]
+    },
+    {
+      desc: 'opacity and transform with backface-visibility:hidden',
+      frames: {
+        opacity: [0, 1],
+        transform: ['translate(0px)', 'translate(100px)']
+      },
+      style: 'backface-visibility: hidden;',
+      expected: [
+        {
+          property: 'opacity',
+          runningOnCompositor: true
+        },
+        {
+          property: 'transform',
+          runningOnCompositor: false,
+          warning: 'CompositorAnimationWarningTransformBackfaceVisibilityHidden'
+        }
+      ]
+    },
+  ].forEach(subtest => {
+    promise_test(function(t) {
+      var animation = addDivAndAnimate(t, { class: 'compositable' },
                                        subtest.frames, 100 * MS_PER_SEC);
       return animation.ready.then(function() {
-        assert_property_state_on_compositor(
+        assert_all_properties_running_on_compositor(
           animation.effect.getProperties(),
           subtest.expected);
         animation.effect.target.style = subtest.style;
         return waitForFrame();
       }).then(function() {
         assert_animation_property_state_equals(
           animation.effect.getProperties(),
           subtest.expected);
         animation.effect.target.style = '';
         return waitForFrame();
       }).then(function() {
-        assert_property_state_on_compositor(
+        assert_all_properties_running_on_compositor(
           animation.effect.getProperties(),
           subtest.expected);
       });
     }, subtest.desc);
   });
+}
 
-  gPerformanceWarningTestsId.forEach(function(subtest) {
+// Performance warning tests that set and clear the id property
+function testIdChanges() {
+  [
+    {
+      desc: 'moz-element referencing a transform',
+      frames: {
+        transform: ['translate(0px)', 'translate(100px)']
+      },
+      id: 'transformed',
+      createelement: 'width:100px; height:100px; background: -moz-element(#transformed)',
+      expected: [
+        {
+          property: 'transform',
+          runningOnCompositor: false,
+          warning: 'CompositorAnimationWarningHasRenderingObserver'
+        }
+      ]
+    },
+  ].forEach(subtest => {
     promise_test(function(t) {
       if (subtest.createelement) {
         addDiv(t, { style: subtest.createelement });
       }
 
-      var animation = addDivAndAnimate(t,
-                                       { class: 'compositable' },
+      var animation = addDivAndAnimate(t, { class: 'compositable' },
                                        subtest.frames, 100 * MS_PER_SEC);
       return animation.ready.then(function() {
-        assert_property_state_on_compositor(
+        assert_all_properties_running_on_compositor(
           animation.effect.getProperties(),
           subtest.expected);
         animation.effect.target.id = subtest.id;
         return waitForFrame();
       }).then(function() {
         assert_animation_property_state_equals(
           animation.effect.getProperties(),
           subtest.expected);
         animation.effect.target.id = '';
         return waitForFrame();
       }).then(function() {
-        assert_property_state_on_compositor(
+        assert_all_properties_running_on_compositor(
           animation.effect.getProperties(),
           subtest.expected);
       });
     }, subtest.desc);
   });
+}
 
-  gMultipleAsyncAnimationsTests.forEach(function(subtest) {
+function testMultipleAnimations() {
+  [
+    {
+      desc: 'opacity and transform with preserve-3d',
+      style: 'transform-style: preserve-3d',
+      animations: [
+        {
+          frames: {
+            transform: ['translate(0px)', 'translate(100px)']
+          },
+          expected: [
+            {
+              property: 'transform',
+              runningOnCompositor: false,
+              warning: 'CompositorAnimationWarningTransformPreserve3D'
+            }
+          ]
+        },
+        {
+          frames: {
+            opacity: [0, 1]
+          },
+          expected: [
+            {
+              property: 'opacity',
+              runningOnCompositor: true,
+            }
+          ]
+        }
+      ],
+    },
+    {
+      desc: 'opacity and transform with backface-visibility:hidden',
+      style: 'backface-visibility: hidden;',
+      animations: [
+        {
+          frames: {
+            transform: ['translate(0px)', 'translate(100px)']
+          },
+          expected: [
+            {
+              property: 'transform',
+              runningOnCompositor: false,
+              warning: 'CompositorAnimationWarningTransformBackfaceVisibilityHidden'
+            }
+          ]
+        },
+        {
+          frames: {
+            opacity: [0, 1]
+          },
+          expected: [
+            {
+              property: 'opacity',
+              runningOnCompositor: true,
+            }
+          ]
+        }
+      ],
+    },
+  ].forEach(subtest => {
     promise_test(function(t) {
       var div = addDiv(t, { class: 'compositable' });
       var animations = subtest.animations.map(function(anim) {
         var animation = div.animate(anim.frames, 100 * MS_PER_SEC);
 
         // Bind expected values to animation object.
         animation.expected = anim.expected;
         return animation;
       });
       return waitForAllAnimations(animations).then(function() {
-        animations.forEach(function(anim) {
-          assert_property_state_on_compositor(
+        animations.forEach(anim => {
+          assert_all_properties_running_on_compositor(
             anim.effect.getProperties(),
             anim.expected);
         });
         div.style = subtest.style;
         return waitForFrame();
       }).then(function() {
-        animations.forEach(function(anim) {
+        animations.forEach(anim => {
           assert_animation_property_state_equals(
             anim.effect.getProperties(),
             anim.expected);
         });
         div.style = '';
         return waitForFrame();
       }).then(function() {
-        animations.forEach(function(anim) {
-          assert_property_state_on_compositor(
+        animations.forEach(anim => {
+          assert_all_properties_running_on_compositor(
             anim.effect.getProperties(),
             anim.expected);
         });
       });
     }, 'Multiple animations: ' + subtest.desc);
   });
+}
 
-  gMultipleAsyncAnimationsWithGeometricKeyframeTests.forEach(function(subtest) {
+// Test adding/removing a 'width' keyframe on the same animation object, where
+// multiple animation objects belong to the same element.
+// The 'width' property is added to animations[1].
+function testMultipleAnimationsWithGeometricKeyframes() {
+  [
+    {
+      desc: 'transform and opacity with geometric keyframes',
+      animations: [
+        {
+          frames: {
+            transform: ['translate(0px)', 'translate(100px)']
+          },
+          expected: {
+            withoutGeometric: [
+              {
+                property: 'transform',
+                runningOnCompositor: true
+              }
+            ],
+            withGeometric: [
+              {
+                property: 'transform',
+                runningOnCompositor: false,
+                warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
+              }
+            ]
+          }
+        },
+        {
+          frames: {
+            opacity: [0, 1]
+          },
+          expected: {
+            withoutGeometric: [
+              {
+                property: 'opacity',
+                runningOnCompositor: true,
+              }
+            ],
+            withGeometric: [
+              {
+                property: 'width',
+                runningOnCompositor: false,
+              },
+              {
+                property: 'opacity',
+                runningOnCompositor: true,
+              }
+            ]
+          }
+        }
+      ],
+    },
+    {
+      desc: 'opacity and transform with geometric keyframes',
+      animations: [
+        {
+          frames: {
+            opacity: [0, 1]
+          },
+          expected: {
+            withoutGeometric: [
+              {
+                property: 'opacity',
+                runningOnCompositor: true,
+              }
+            ],
+            withGeometric: [
+              {
+                property: 'opacity',
+                runningOnCompositor: true,
+              }
+            ]
+          }
+        },
+        {
+          frames: {
+            transform: ['translate(0px)', 'translate(100px)']
+          },
+          expected: {
+            withoutGeometric: [
+              {
+                property: 'transform',
+                runningOnCompositor: true
+              }
+            ],
+            withGeometric: [
+              {
+                property: 'width',
+                runningOnCompositor: false,
+              },
+              {
+                property: 'transform',
+                runningOnCompositor: false,
+                warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
+              }
+            ]
+          }
+        }
+      ]
+    },
+  ].forEach(subtest => {
     promise_test(function(t) {
       var div = addDiv(t, { class: 'compositable' });
       var animations = subtest.animations.map(function(anim) {
         var animation = div.animate(anim.frames, 100 * MS_PER_SEC);
 
         // Bind expected values to animation object.
         animation.expected = anim.expected;
         return animation;
       });
       return waitForAllAnimations(animations).then(function() {
         // First, all animations are running on compositor.
-        animations.forEach(function(anim) {
+        animations.forEach(anim => {
           assert_animation_property_state_equals(
             anim.effect.getProperties(),
             anim.expected.withoutGeometric);
         });
       }).then(function() {
         // Add a 'width' property to animations[1].
         var keyframes = animations[1].effect.getKeyframes();
 
         keyframes[0].width = '100px';
         keyframes[1].width = '200px';
 
         animations[1].effect.setKeyframes(keyframes);
         return waitForFrame();
       }).then(function() {
         // Now the transform animation is not running on compositor because of
         // the 'width' property.
-        animations.forEach(function(anim) {
+        animations.forEach(anim => {
           assert_animation_property_state_equals(
             anim.effect.getProperties(),
             anim.expected.withGeometric);
         });
       }).then(function() {
         // Remove the 'width' property from animations[1].
         var keyframes = animations[1].effect.getKeyframes();
 
         delete keyframes[0].width;
         delete keyframes[1].width;
 
         animations[1].effect.setKeyframes(keyframes);
         return waitForFrame();
       }).then(function() {
         // Finally, all animations are running on compositor.
-        animations.forEach(function(anim) {
+        animations.forEach(anim => {
           assert_animation_property_state_equals(
             anim.effect.getProperties(),
             anim.expected.withoutGeometric);
         });
       });
     }, 'Multiple animations with geometric property: ' + subtest.desc);
   });
+}
 
-  gMultipleAsyncAnimationsWithGeometricAnimationTests.forEach(function(subtest) {
+// Tests adding/removing 'width' animation on the same element which has async
+// animations.
+function testMultipleAnimationsWithGeometricAnimations() {
+  [
+    {
+      desc: 'transform',
+      animations: [
+        {
+          frames: {
+            transform: ['translate(0px)', 'translate(100px)']
+          },
+          expected: [
+            {
+              property: 'transform',
+              runningOnCompositor: false,
+              warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
+            }
+          ]
+        },
+      ]
+    },
+    {
+      desc: 'opacity',
+      animations: [
+        {
+          frames: {
+            opacity: [0, 1]
+          },
+          expected: [
+            {
+              property: 'opacity',
+              runningOnCompositor: true
+            }
+          ]
+        },
+      ]
+    },
+    {
+      desc: 'opacity and transform',
+      animations: [
+        {
+          frames: {
+            transform: ['translate(0px)', 'translate(100px)']
+          },
+          expected: [
+            {
+              property: 'transform',
+              runningOnCompositor: false,
+              warning: 'CompositorAnimationWarningTransformWithGeometricProperties'
+            }
+          ]
+        },
+        {
+          frames: {
+            opacity: [0, 1]
+          },
+          expected: [
+            {
+              property: 'opacity',
+              runningOnCompositor: true,
+            }
+          ]
+        }
+      ],
+    },
+  ].forEach(subtest => {
     promise_test(function(t) {
       var div = addDiv(t, { class: 'compositable' });
       var animations = subtest.animations.map(function(anim) {
         var animation = div.animate(anim.frames, 100 * MS_PER_SEC);
 
         // Bind expected values to animation object.
         animation.expected = anim.expected;
         return animation;
       });
 
       var widthAnimation;
 
       return waitForAllAnimations(animations).then(function() {
-        animations.forEach(function(anim) {
-          assert_property_state_on_compositor(
+        animations.forEach(anim => {
+          assert_all_properties_running_on_compositor(
             anim.effect.getProperties(),
             anim.expected);
         });
       }).then(function() {
         // Append 'width' animation on the same element.
         widthAnimation = div.animate({ width: ['100px', '200px'] },
                                      100 * MS_PER_SEC);
         return waitForFrame();
       }).then(function() {
         // Now transform animations are not running on compositor because of
         // the 'width' animation.
-        animations.forEach(function(anim) {
+        animations.forEach(anim => {
           assert_animation_property_state_equals(
             anim.effect.getProperties(),
             anim.expected);
         });
         // Remove the 'width' animation.
         widthAnimation.cancel();
         return waitForFrame();
       }).then(function() {
         // Now all animations are running on compositor.
-        animations.forEach(function(anim) {
-          assert_property_state_on_compositor(
+        animations.forEach(anim => {
+          assert_all_properties_running_on_compositor(
             anim.effect.getProperties(),
             anim.expected);
         });
       });
     }, 'Multiple async animations and geometric animation: ' + subtest.desc);
   });
+}
 
-  gAnimationsOnTooSmallElementTests.forEach(function(subtest) {
+function testSmallElements() {
+  [
+    {
+      desc: 'opacity on too small element',
+      frames: {
+        opacity: [0, 1]
+      },
+      style: { style: 'width: 8px; height: 8px; background-color: red;' +
+                      // We need to set transform here to try creating an
+                      // individual frame for this opacity element.
+                      // Without this, this small element is created on the same
+                      // nsIFrame of mochitest iframe, i.e. the document which are
+                      // running this test, as a result the layer corresponding
+                      // to the frame is sent to compositor.
+                      'transform: translateX(100px);' },
+      expected: [
+        {
+          property: 'opacity',
+          runningOnCompositor: false,
+          warning: /Animation cannot be run on the compositor because frame size \(8, 8\) is smaller than \(16, 16\)/
+        }
+      ]
+    },
+    {
+      desc: 'transform on too small element',
+      frames: {
+        transform: ['translate(0px)', 'translate(100px)']
+      },
+      style: { style: 'width: 8px; height: 8px; background-color: red;' },
+      expected: [
+        {
+          property: 'transform',
+          runningOnCompositor: false,
+          warning: /Animation cannot be run on the compositor because frame size \(8, 8\) is smaller than \(16, 16\)/
+        }
+      ]
+    },
+  ].forEach(subtest => {
     promise_test(function(t) {
     var div = addDiv(t, subtest.style);
     var animation = div.animate(subtest.frames, 100 * MS_PER_SEC);
       return animation.ready.then(function() {
         assert_animation_property_state_equals(
           animation.effect.getProperties(),
           subtest.expected);
       });
     }, subtest.desc);
   });
+}
+
+function testSynchronizedAnimations() {
+  promise_test(function(t) {
+    const elemA = addDiv(t, { class: 'compositable' });
+    const elemB = addDiv(t, { class: 'compositable' });
+
+    const animA = elemA.animate({ transform: [ 'translate(0px)',
+                                               'translate(100px)' ] },
+                                100 * MS_PER_SEC);
+    const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
+                                100 * MS_PER_SEC);
+
+    return Promise.all([animA.ready, animB.ready])
+      .then(() => {
+        assert_animation_property_state_equals(
+          animA.effect.getProperties(),
+          [ { property: 'transform',
+              runningOnCompositor: false,
+              warning: 'CompositorAnimationWarningTransformWithSyncGeometricAnimations'
+          } ]);
+      });
+  }, 'Animations created within the same tick are synchronized'
+     + ' (compositor animation created first)');
+
+  promise_test(function(t) {
+    const elemA = addDiv(t, { class: 'compositable' });
+    const elemB = addDiv(t, { class: 'compositable' });
+
+    const animA = elemA.animate({ marginLeft: [ '0px', '100px' ] },
+                                100 * MS_PER_SEC);
+    const animB = elemB.animate({ transform: [ 'translate(0px)',
+                                               'translate(100px)' ] },
+                                100 * MS_PER_SEC);
+
+    return Promise.all([animA.ready, animB.ready])
+      .then(() => {
+        assert_animation_property_state_equals(
+          animB.effect.getProperties(),
+          [ { property: 'transform',
+              runningOnCompositor: false,
+              warning: 'CompositorAnimationWarningTransformWithSyncGeometricAnimations'
+          } ]);
+      });
+  }, 'Animations created within the same tick are synchronized'
+     + ' (compositor animation created second)');
+
+  promise_test(function(t) {
+    const attrs = { class: 'compositable',
+                    style: 'transition: all 100s' };
+    const elemA = addDiv(t, attrs);
+    const elemB = addDiv(t, attrs);
+    elemA.style.transform = 'translate(0px)';
+    elemB.style.marginLeft = '0px';
+    getComputedStyle(elemA).transform;
+    getComputedStyle(elemB).marginLeft;
+
+    // Generally the sequence of steps is as follows:
+    //
+    //   Tick -> requestAnimationFrame -> Style -> Paint -> Events (-> Tick...)
+    //
+    // In this test we want to set up two transitions during the "Events"
+    // stage but only flush style for one such that the second one is actually
+    // generated during the "Style" stage of the *next* tick.
+    //
+    // Web content often generates transitions in this way (that is, it doesn't
+    // pay regard to when style is flushed and nor should it). However, we
+    // still want transitions generated in this way to be synchronized.
+    let timeForFirstFrame;
+    return waitForIdleCallback()
+      .then(() => {
+        timeForFirstFrame = document.timeline.currentTime;
+        elemA.style.transform = 'translate(100px)';
+        // Flush style to trigger first transition
+        getComputedStyle(elemA).transform;
+        elemB.style.marginLeft = '100px';
+        // DON'T flush style here (this includes calling getAnimations!)
+        return waitForFrame();
+      }).then(() => {
+        assert_not_equals(timeForFirstFrame, document.timeline.currentTime,
+                          'Should be on the other side of a tick');
+        // Wait another tick so we can let the transition be started
+        // by regular style resolution.
+        return waitForFrame();
+      }).then(() => {
+        const transitionA = elemA.getAnimations()[0];
+        assert_animation_property_state_equals(
+          transitionA.effect.getProperties(),
+          [ { property: 'transform',
+              runningOnCompositor: false,
+              warning: 'CompositorAnimationWarningTransformWithSyncGeometricAnimations'
+          } ]);
+      });
+  }, 'Transitions created before and after a tick are synchronized');
+
+  promise_test(function(t) {
+    const elemA = addDiv(t, { class: 'compositable' });
+    const elemB = addDiv(t, { class: 'compositable' });
+
+    const animA = elemA.animate({ transform: [ 'translate(0px)',
+                                               'translate(100px)' ],
+                                  opacity: [ 0, 1 ] },
+                                100 * MS_PER_SEC);
+    const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
+                                100 * MS_PER_SEC);
+
+    return Promise.all([animA.ready, animB.ready])
+      .then(() => {
+        assert_animation_property_state_equals(
+          animA.effect.getProperties(),
+          [ { property: 'transform',
+              runningOnCompositor: false,
+              warning: 'CompositorAnimationWarningTransformWithSyncGeometricAnimations'
+            },
+            { property: 'opacity',
+              runningOnCompositor: true
+            } ]);
+      });
+  }, 'Opacity animations on the same element continue running on the'
+     + ' compositor when transform animations are synchronized with geometric'
+     + ' animations');
+
+  promise_test(function(t) {
+    const elemA = addDiv(t, { class: 'compositable' });
+    const elemB = addDiv(t, { class: 'compositable' });
+
+    const animA = elemA.animate({ marginLeft: [ '0px', '100px' ] },
+                                100 * MS_PER_SEC);
+    let animB;
+
+    return waitForFrame()
+      .then(() => {
+        animB = elemB.animate({ transform: [ 'translate(0px)',
+                                             'translate(100px)' ] },
+                                100 * MS_PER_SEC);
+        return animB.ready;
+      }).then(() => {
+        assert_animation_property_state_equals(
+          animB.effect.getProperties(),
+          [ { property: 'transform',
+              runningOnCompositor: true } ]);
+      });
+  }, 'Transform animations are NOT synchronized with geometric animations'
+     + ' started in the previous frame');
+
+  promise_test(function(t) {
+    const elemA = addDiv(t, { class: 'compositable' });
+    const elemB = addDiv(t, { class: 'compositable' });
+
+    const animA = elemA.animate({ transform: [ 'translate(0px)',
+                                               'translate(100px)' ] },
+                                100 * MS_PER_SEC);
+    let animB;
+
+    return waitForFrame()
+      .then(() => {
+        animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
+                              100 * MS_PER_SEC);
+        return animB.ready;
+      }).then(() => {
+        assert_animation_property_state_equals(
+          animA.effect.getProperties(),
+          [ { property: 'transform',
+              runningOnCompositor: true } ]);
+      });
+  }, 'Transform animations are NOT synchronized with geometric animations'
+     + ' started in the next frame');
+
+  promise_test(function(t) {
+    const elemA = addDiv(t, { class: 'compositable' });
+    const elemB = addDiv(t, { class: 'compositable' });
+
+    const animA = elemA.animate({ transform: [ 'translate(0px)',
+                                               'translate(100px)' ] },
+                                100 * MS_PER_SEC);
+    const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
+                                100 * MS_PER_SEC);
+    animB.pause();
+
+    return Promise.all([animA.ready, animB.ready])
+      .then(() => {
+        assert_animation_property_state_equals(
+          animA.effect.getProperties(),
+          [ { property: 'transform', runningOnCompositor: true } ]);
+      });
+  }, 'Paused animations are not synchronized');
+
+  promise_test(function(t) {
+    const elemA = addDiv(t, { class: 'compositable' });
+    const elemB = addDiv(t, { class: 'compositable' });
+
+    const animA = elemA.animate({ transform: [ 'translate(0px)',
+                                               'translate(100px)' ] },
+                                100 * MS_PER_SEC);
+    const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
+                                100 * MS_PER_SEC);
+
+    // Seek one of the animations so that their start times will differ
+    animA.currentTime = 5000;
+
+    return Promise.all([animA.ready, animB.ready])
+      .then(() => {
+        assert_not_equals(animA.startTime, animB.startTime,
+                          'Animations should have different start times');
+        assert_animation_property_state_equals(
+          animA.effect.getProperties(),
+          [ { property: 'transform',
+              runningOnCompositor: false,
+              warning: 'CompositorAnimationWarningTransformWithSyncGeometricAnimations'
+          } ]);
+      });
+  }, 'Animations are synchronized based on when they are started'
+     + ' and NOT their start time');
+
+  promise_test(function(t) {
+    const elemA = addDiv(t, { class: 'compositable' });
+    const elemB = addDiv(t, { class: 'compositable' });
+
+    const animA = elemA.animate({ transform: [ 'translate(0px)',
+                                               'translate(100px)' ] },
+                                100 * MS_PER_SEC);
+    const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
+                                100 * MS_PER_SEC);
+
+    return Promise.all([animA.ready, animB.ready])
+      .then(() => {
+        assert_animation_property_state_equals(
+          animA.effect.getProperties(),
+          [ { property: 'transform',
+              runningOnCompositor: false } ]);
+        // Restart animation
+        animA.pause();
+        animA.play();
+        return animA.ready;
+      }).then(() => {
+        assert_animation_property_state_equals(
+          animA.effect.getProperties(),
+          [ { property: 'transform',
+              runningOnCompositor: true } ]);
+      });
+  }, 'An initially synchronized animation may be unsynchronized if restarted');
+
+  promise_test(function(t) {
+    const elemA = addDiv(t, { class: 'compositable' });
+    const elemB = addDiv(t, { class: 'compositable' });
+
+    const animA = elemA.animate({ transform: [ 'translate(0px)',
+                                               'translate(100px)' ] },
+                                100 * MS_PER_SEC);
+    const animB = elemB.animate({ marginLeft: [ '0px', '100px' ] },
+                                100 * MS_PER_SEC);
+
+    // Clear target effect
+    animB.effect.target = null;
+
+    return Promise.all([animA.ready, animB.ready])
+      .then(() => {
+        assert_animation_property_state_equals(
+          animA.effect.getProperties(),
+          [ { property: 'transform',
+              runningOnCompositor: true } ]);
+      });
+  }, 'A geometric animation with no target element is not synchronized');
+}
+
+function start() {
+  var bundleService = SpecialPowers.Cc['@mozilla.org/intl/stringbundle;1']
+    .getService(SpecialPowers.Ci.nsIStringBundleService);
+  gStringBundle = bundleService
+    .createBundle("chrome://global/locale/layout_errors.properties");
+
+  testBasicOperation();
+  testKeyframesWithGeometricProperties();
+  testSetOfGeometricProperties();
+  testStyleChanges();
+  testIdChanges();
+  testMultipleAnimations();
+  testMultipleAnimationsWithGeometricKeyframes();
+  testMultipleAnimationsWithGeometricAnimations();
+  testSmallElements();
+  testSynchronizedAnimations();
 
   promise_test(function(t) {
     var animation = addDivAndAnimate(t,
                                      { class: 'compositable' },
                                      { transform: [ 'translate(0px)',
                                                     'translate(100px)'] },
                                      100 * MS_PER_SEC);
     return animation.ready.then(function() {
--- a/dom/animation/test/mozilla/file_discrete-animations.html
+++ b/dom/animation/test/mozilla/file_discrete-animations.html
@@ -138,28 +138,16 @@ for (let property in gMozillaSpecificPro
     testAnimationSamples(animation, idlName,
                          [{ time: 0,    expected: from.toLowerCase() },
                           { time: 940,  expected: from.toLowerCase() },
                           { time: 960,  expected: to.toLowerCase() }]);
   }, property + " should animate between '"
    + from + "' and '" + to + "' with keyframe easing");
 }
 
-function propertyToIDL(property) {
-  var prefixMatch = property.match(/^-(\w+)-/);
-  if (prefixMatch) {
-    var prefix = prefixMatch[1] === "moz" ? "Moz" : prefixMatch[1];
-    property = prefix + property.substring(prefixMatch[0].length - 1);
-  }
-  // https://drafts.csswg.org/cssom/#css-property-to-idl-attribute
-  return property.replace(/-([a-z])/gi, function(str, group) {
-    return group.toUpperCase();
-  });
-}
-
 function testAnimationSamples(animation, idlName, testSamples) {
   const target = animation.effect.target;
   testSamples.forEach(testSample => {
     animation.currentTime = testSample.time;
     assert_equals(getComputedStyle(target)[idlName], testSample.expected,
                   "The value should be " + testSample.expected +
                   " at " + testSample.time + "ms");
   });
--- a/dom/animation/test/testcommon.js
+++ b/dom/animation/test/testcommon.js
@@ -133,25 +133,50 @@ function addStyle(t, rules) {
   if (t && typeof t.add_cleanup === 'function') {
     t.add_cleanup(function() {
       extraStyle.remove();
     });
   }
 }
 
 /**
+ * Takes a CSS property (e.g. margin-left) and returns the equivalent IDL
+ * name (e.g. marginLeft).
+ */
+function propertyToIDL(property) {
+  var prefixMatch = property.match(/^-(\w+)-/);
+  if (prefixMatch) {
+    var prefix = prefixMatch[1] === 'moz' ? 'Moz' : prefixMatch[1];
+    property = prefix + property.substring(prefixMatch[0].length - 1);
+  }
+  // https://drafts.csswg.org/cssom/#css-property-to-idl-attribute
+  return property.replace(/-([a-z])/gi, function(str, group) {
+    return group.toUpperCase();
+  });
+}
+
+/**
  * Promise wrapper for requestAnimationFrame.
  */
 function waitForFrame() {
   return new Promise(function(resolve, reject) {
     window.requestAnimationFrame(resolve);
   });
 }
 
 /**
+ * Promise wrapper for requestIdleCallback.
+ */
+function waitForIdleCallback() {
+  return new Promise(function(resolve, reject) {
+    window.requestIdleCallback(resolve);
+  });
+}
+
+/**
  * Returns a Promise that is resolved after the given number of consecutive
  * animation frames have occured (using requestAnimationFrame callbacks).
  *
  * @param frameCount  The number of animation frames.
  * @param onFrame  An optional function to be processed in each animation frame.
  */
 function waitForAnimationFrames(frameCount, onFrame) {
   return new Promise(function(resolve, reject) {
@@ -209,17 +234,17 @@ if (opener) {
     opener.add_completion_callback(function() {
       self.close();
     });
     opener.done();
   }
 }
 
 /**
- * Return a new MutaionObserver which started observing |target| element
+ * Return a new MutationObserver which started observing |target| element
  * with { animations: true, subtree: |subtree| } option.
  * NOTE: This observer should be used only with takeRecords(). If any of
  * MutationRecords are observed in the callback of the MutationObserver,
  * it will raise an assertion.
  */
 function setupSynchronousObserver(t, target, subtree) {
    var observer = new MutationObserver(records => {
      assert_unreached("Any MutationRecords should not be observed in this " +
--- a/dom/base/Location.cpp
+++ b/dom/base/Location.cpp
@@ -576,17 +576,17 @@ Location::GetPathname(nsAString& aPathna
 {
   aPathname.Truncate();
 
   nsCOMPtr<nsIURI> uri;
   nsresult result = NS_OK;
 
   result = GetURI(getter_AddRefs(uri));
 
-  nsCOMPtr<nsIURL> url(do_QueryInterface(uri));
+  nsCOMPtr<nsIURIWithQuery> url(do_QueryInterface(uri));
   if (url) {
     nsAutoCString file;
 
     result = url->GetFilePath(file);
 
     if (NS_SUCCEEDED(result)) {
       AppendUTF8toUTF16(file, aPathname);
     }
@@ -599,22 +599,22 @@ NS_IMETHODIMP
 Location::SetPathname(const nsAString& aPathname)
 {
   nsCOMPtr<nsIURI> uri;
   nsresult rv = GetWritableURI(getter_AddRefs(uri));
   if (NS_WARN_IF(NS_FAILED(rv) || !uri)) {
     return rv;
   }
 
-  rv = uri->SetPath(NS_ConvertUTF16toUTF8(aPathname));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return rv;
+  nsCOMPtr<nsIURIWithQuery> url(do_QueryInterface(uri));
+  if (url && NS_SUCCEEDED(url->SetFilePath(NS_ConvertUTF16toUTF8(aPathname)))) {
+    return SetURI(uri);
   }
 
-  return SetURI(uri);
+  return NS_OK;
 }
 
 NS_IMETHODIMP
 Location::GetPort(nsAString& aPort)
 {
   aPort.SetLength(0);
 
   nsCOMPtr<nsIURI> uri;
--- a/dom/base/nsFrameLoader.cpp
+++ b/dom/base/nsFrameLoader.cpp
@@ -954,21 +954,19 @@ nsFrameLoader::AddTreeItemToTreeOwner(ns
 
   bool retval = false;
   if (aParentType == nsIDocShellTreeItem::typeChrome && isContent) {
     retval = true;
 
     bool is_primary = value.LowerCaseEqualsLiteral("content-primary");
 
     if (aOwner) {
-      bool is_targetable = is_primary ||
-        value.LowerCaseEqualsLiteral("content-targetable");
       mOwnerContent->AddMutationObserver(this);
       mObservingOwnerContent = true;
-      aOwner->ContentShellAdded(aItem, is_primary, is_targetable, value);
+      aOwner->ContentShellAdded(aItem, is_primary, value);
     }
   }
 
   return retval;
 }
 
 static bool
 AllDescendantsOfType(nsIDocShellTreeItem* aParentItem, int32_t aType)
@@ -3280,21 +3278,18 @@ nsFrameLoader::AttributeChanged(nsIDocum
       pm->HidePopupsInDocShell(mDocShell);
   }
 #endif
 
   parentTreeOwner->ContentShellRemoved(mDocShell);
   if (value.LowerCaseEqualsLiteral("content") ||
       StringBeginsWith(value, NS_LITERAL_STRING("content-"),
                        nsCaseInsensitiveStringComparator())) {
-    bool is_targetable = is_primary ||
-      value.LowerCaseEqualsLiteral("content-targetable");
-
-    parentTreeOwner->ContentShellAdded(mDocShell, is_primary,
-                                       is_targetable, value);
+
+    parentTreeOwner->ContentShellAdded(mDocShell, is_primary, value);
   }
 }
 
 /**
  * Send the RequestNotifyAfterRemotePaint message to the current Tab.
  */
 NS_IMETHODIMP
 nsFrameLoader::RequestNotifyAfterRemotePaint()
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -314,18 +314,17 @@ int32_t gTimeoutCnt                     
 static int32_t gMinTimeoutValue;
 static int32_t gMinBackgroundTimeoutValue;
 inline int32_t
 nsGlobalWindow::DOMMinTimeoutValue() const {
   // First apply any back pressure delay that might be in effect.
   int32_t value = std::max(mBackPressureDelayMS, 0);
   // Don't use the background timeout value when there are audio contexts
   // present, so that baackground audio can keep running smoothly. (bug 1181073)
-  bool isBackground = mAudioContexts.IsEmpty() &&
-    (!mOuterWindow || mOuterWindow->IsBackground());
+  bool isBackground = mAudioContexts.IsEmpty() && IsBackgroundInternal();
   return
     std::max(isBackground ? gMinBackgroundTimeoutValue : gMinTimeoutValue, value);
 }
 
 // The number of nested timeouts before we start clamping. HTML5 says 1, WebKit
 // uses 5.
 #define DOM_CLAMP_TIMEOUT_NESTING_LEVEL 5
 
@@ -649,16 +648,21 @@ void nsGlobalWindow::UnthrottleIdleCallb
 
   while (!mThrottledIdleRequestCallbacks.isEmpty()) {
     RefPtr<IdleRequest> request(mThrottledIdleRequestCallbacks.popFirst());
     mIdleRequestCallbacks.insertBack(request);
     NS_IdleDispatchToCurrentThread(request.forget());
   }
 }
 
+bool
+nsGlobalWindow::IsBackgroundInternal() const
+{
+  return !mOuterWindow || mOuterWindow->IsBackground();
+}
 
 namespace mozilla {
 namespace dom {
 extern uint64_t
 NextWindowID();
 } // namespace dom
 } // namespace mozilla
 
@@ -7015,18 +7019,18 @@ nsGlobalWindow::CanMoveResizeWindows(boo
       if (docShell) {
         nsCOMPtr<nsITabChild> child = docShell->GetTabChild();
         if (child) {
           child->SendGetTabCount(&itemCount);
         }
       }
     } else {
       nsCOMPtr<nsIDocShellTreeOwner> treeOwner = GetTreeOwner();
-      if (treeOwner) {
-        treeOwner->GetTargetableShellCount(&itemCount);
+      if (!treeOwner || NS_FAILED(treeOwner->GetTabCount(&itemCount))) {
+        itemCount = 0;
       }
     }
     if (itemCount > 1) {
       return false;
     }
   }
 
   if (mDocShell) {
@@ -12736,17 +12740,17 @@ nsGlobalWindow::SetTimeoutOrInterval(nsI
   timeout->mInterval = interval;
   timeout->mScriptHandler = aHandler;
   timeout->mReason = aReason;
 
   // Now clamp the actual interval we will use for the timer based on
   uint32_t nestingLevel = sNestingLevel + 1;
   uint32_t realInterval = interval;
   if (aIsInterval || nestingLevel >= DOM_CLAMP_TIMEOUT_NESTING_LEVEL ||
-      mBackPressureDelayMS > 0) {
+      mBackPressureDelayMS > 0 || IsBackgroundInternal()) {
     // Don't allow timeouts less than DOMMinTimeoutValue() from
     // now...
     realInterval = std::max(realInterval, uint32_t(DOMMinTimeoutValue()));
   }
 
   TimeDuration delta = TimeDuration::FromMilliseconds(realInterval);
 
   if (IsFrozen()) {
--- a/dom/base/nsGlobalWindow.h
+++ b/dom/base/nsGlobalWindow.h
@@ -1719,16 +1719,18 @@ private:
   // When timers are being throttled and we reduce the thottle delay we must
   // reschedule.  The amount of the old throttle delay must be provided in
   // order to bound how many timers must be examined.
   nsresult ResetTimersForThrottleReduction(int32_t aPreviousThrottleDelayMS);
 
   mozilla::dom::TabGroup* TabGroupInner();
   mozilla::dom::TabGroup* TabGroupOuter();
 
+  bool IsBackgroundInternal() const;
+
 public:
   // Dispatch a runnable related to the global.
   virtual nsresult Dispatch(const char* aName,
                             mozilla::dom::TaskCategory aCategory,
                             already_AddRefed<nsIRunnable>&& aRunnable) override;
 
   virtual already_AddRefed<nsIEventTarget>
   EventTargetFor(mozilla::dom::TaskCategory aCategory) const override;
--- a/dom/base/test/chrome/file_bug990812-1.xul
+++ b/dom/base/test/chrome/file_bug990812-1.xul
@@ -46,17 +46,17 @@ https://bugzilla.mozilla.org/show_bug.cg
           opener.setTimeout("next()");
           window.close();
         }
       });
 
       var browser = document.createElement("browser");
       browser.setAttribute("messagemanagergroup", "test");
       browser.setAttribute("src", "about:mozilla");
-      browser.setAttribute("type", "content-targetable");
+      browser.setAttribute("type", "content");
       document.documentElement.appendChild(browser);
 
       globalMM.removeDelayedFrameScript(FRAME_SCRIPT_GLOBAL);
       messageManager.removeDelayedFrameScript(FRAME_SCRIPT_WINDOW);
       getGroupMessageManager("test").removeDelayedFrameScript(FRAME_SCRIPT_GROUP);
     }
 
   ]]></script>
--- a/dom/base/test/chrome/file_bug990812-2.xul
+++ b/dom/base/test/chrome/file_bug990812-2.xul
@@ -49,11 +49,11 @@ https://bugzilla.mozilla.org/show_bug.cg
       Promise.all([global, window, group]).then(function () {
         opener.setTimeout("next()");
         self.close();
       });
     }
 
   ]]></script>
 
-  <browser messagemanagergroup="test" type="content-targetable" src="about:mozilla" />
+  <browser messagemanagergroup="test" type="content" src="about:mozilla" />
 
 </window>
--- a/dom/base/test/chrome/file_bug990812-3.xul
+++ b/dom/base/test/chrome/file_bug990812-3.xul
@@ -60,12 +60,12 @@ https://bugzilla.mozilla.org/show_bug.cg
       Promise.all([promise1, promise2]).then(function () {
         opener.setTimeout("next()");
         window.close();
       });
     }
 
   ]]></script>
 
-  <browser messagemanagergroup="test1" type="content-targetable" src="about:mozilla" />
-  <browser messagemanagergroup="test2" type="content-targetable" src="about:mozilla" />
+  <browser messagemanagergroup="test1" type="content" src="about:mozilla" />
+  <browser messagemanagergroup="test2" type="content" src="about:mozilla" />
 
 </window>
--- a/dom/base/test/chrome/file_bug990812-4.xul
+++ b/dom/base/test/chrome/file_bug990812-4.xul
@@ -57,12 +57,12 @@ https://bugzilla.mozilla.org/show_bug.cg
       Promise.all([promise1, promise2]).then(function () {
         opener.setTimeout("next()");
         window.close();
       });
     }
 
   ]]></script>
 
-  <browser messagemanagergroup="test1" type="content-targetable" src="about:mozilla" />
-  <browser messagemanagergroup="test2" type="content-targetable" src="about:mozilla" />
+  <browser messagemanagergroup="test1" type="content" src="about:mozilla" />
+  <browser messagemanagergroup="test2" type="content" src="about:mozilla" />
 
 </window>
--- a/dom/base/test/chrome/file_bug990812-5.xul
+++ b/dom/base/test/chrome/file_bug990812-5.xul
@@ -63,15 +63,15 @@ https://bugzilla.mozilla.org/show_bug.cg
       Promise.all([promise1, promise2]).then(function () {
         opener.setTimeout("next()");
         window.close();
       });
     }
 
   ]]></script>
 
-  <browser messagemanagergroup="test1" type="content-targetable" src="about:mozilla" />
-  <browser messagemanagergroup="test1" type="content-targetable" src="about:mozilla" />
+  <browser messagemanagergroup="test1" type="content" src="about:mozilla" />
+  <browser messagemanagergroup="test1" type="content" src="about:mozilla" />
 
-  <browser messagemanagergroup="test2" type="content-targetable" src="about:mozilla" />
-  <browser messagemanagergroup="test2" type="content-targetable" src="about:mozilla" />
+  <browser messagemanagergroup="test2" type="content" src="about:mozilla" />
+  <browser messagemanagergroup="test2" type="content" src="about:mozilla" />
 
 </window>
--- a/dom/base/test/chrome/file_bug990812.xul
+++ b/dom/base/test/chrome/file_bug990812.xul
@@ -40,17 +40,17 @@ https://bugzilla.mozilla.org/show_bug.cg
           opener.setTimeout("next()");
           window.close();
         }
       });
 
       var browser = document.createElement("browser");
       browser.setAttribute("messagemanagergroup", "test");
       browser.setAttribute("src", "about:mozilla");
-      browser.setAttribute("type", "content-targetable");
+      browser.setAttribute("type", "content");
       document.documentElement.appendChild(browser);
 
       globalMM.removeDelayedFrameScript(FRAME_SCRIPT_GLOBAL);
       messageManager.removeDelayedFrameScript(FRAME_SCRIPT_WINDOW);
       getGroupMessageManager("test").removeDelayedFrameScript(FRAME_SCRIPT_GROUP);
     }
 
   ]]></script>
--- a/dom/bindings/BindingDeclarations.h
+++ b/dom/bindings/BindingDeclarations.h
@@ -79,16 +79,18 @@ struct AllOwningUnionBase {
 };
 
 
 struct EnumEntry {
   const char* value;
   size_t length;
 };
 
+enum class CallerType : uint32_t;
+
 class MOZ_STACK_CLASS GlobalObject
 {
 public:
   GlobalObject(JSContext* aCx, JSObject* aObject);
 
   JSObject* Get() const
   {
     return mGlobalJSObject;
@@ -108,16 +110,20 @@ public:
   {
     return !Get();
   }
 
   // It returns the subjectPrincipal if called on the main-thread, otherwise
   // a nullptr is returned.
   nsIPrincipal* GetSubjectPrincipal() const;
 
+  // Get the caller type.  Note that this needs to be called before anyone has
+  // had a chance to mess with the JSContext.
+  dom::CallerType CallerType() const;
+
 protected:
   JS::Rooted<JSObject*> mGlobalJSObject;
   JSContext* mCx;
   mutable nsISupports* MOZ_UNSAFE_REF("Valid because GlobalObject is a stack "
                                       "class, and mGlobalObject points to the "
                                       "global, so it won't be destroyed as long "
                                       "as GlobalObject lives on the stack") mGlobalObject;
 };
--- a/dom/bindings/BindingUtils.cpp
+++ b/dom/bindings/BindingUtils.cpp
@@ -2310,16 +2310,23 @@ GlobalObject::GetSubjectPrincipal() cons
   }
 
   JSCompartment* compartment = js::GetContextCompartment(mCx);
   MOZ_ASSERT(compartment);
   JSPrincipals* principals = JS_GetCompartmentPrincipals(compartment);
   return nsJSPrincipals::get(principals);
 }
 
+CallerType
+GlobalObject::CallerType() const
+{
+  return nsContentUtils::ThreadsafeIsSystemCaller(mCx) ?
+    dom::CallerType::System : dom::CallerType::NonSystem;
+}
+
 static bool
 CallOrdinaryHasInstance(JSContext* cx, JS::CallArgs& args)
 {
     JS::Rooted<JSObject*> thisObj(cx, &args.thisv().toObject());
     bool isInstance;
     if (!JS::OrdinaryHasInstance(cx, thisObj, args.get(0), &isInstance)) {
       return false;
     }
--- a/dom/bindings/BindingUtils.h
+++ b/dom/bindings/BindingUtils.h
@@ -892,16 +892,17 @@ AssertReflectorHasGivenProto(JSContext* 
 
 template <class T, GetOrCreateReflectorWrapBehavior wrapBehavior>
 MOZ_ALWAYS_INLINE bool
 DoGetOrCreateDOMReflector(JSContext* cx, T* value,
                           JS::Handle<JSObject*> givenProto,
                           JS::MutableHandle<JS::Value> rval)
 {
   MOZ_ASSERT(value);
+  MOZ_ASSERT_IF(givenProto, js::IsObjectInContextCompartment(givenProto, cx));
   // We can get rid of this when we remove support for hasXPConnectImpls.
   bool couldBeDOMBinding = CouldBeDOMBinding(value);
   JSObject* obj = value->GetWrapper();
   if (obj) {
 #ifdef DEBUG
     AssertReflectorHasGivenProto(cx, obj, givenProto);
     // Have to reget obj because AssertReflectorHasGivenProto can
     // trigger gc so the pointer may now be invalid.
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -3626,16 +3626,17 @@ class CGWrapWithCacheMethod(CGAbstractMe
             aCache->ReleaseWrapper(aObject);
             aCache->ClearWrapper();
             return false;
             """)
 
         return fill(
             """
             $*{assertInheritance}
+            MOZ_ASSERT_IF(aGivenProto, js::IsObjectInContextCompartment(aGivenProto, aCx));
             MOZ_ASSERT(!aCache->GetWrapper(),
                        "You should probably not be using Wrap() directly; use "
                        "GetOrCreateDOMReflector instead");
 
             MOZ_ASSERT(ToSupportsIsOnPrimaryInheritanceChain(aObject, aCache),
                        "nsISupports must be on our primary inheritance chain");
 
             JS::Rooted<JSObject*> global(aCx, FindAssociatedGlobal(aCx, aObject->GetParentObject()));
@@ -3726,16 +3727,17 @@ class CGWrapNonWrapperCacheMethod(CGAbst
         self.properties = properties
 
     def definition_body(self):
         failureCode = "return false;\n"
 
         return fill(
             """
             $*{assertions}
+            MOZ_ASSERT_IF(aGivenProto, js::IsObjectInContextCompartment(aGivenProto, aCx));
 
             JS::Rooted<JSObject*> global(aCx, JS::CurrentGlobalOrNull(aCx));
             $*{declareProto}
 
             $*{createObject}
 
             $*{unforgeable}
 
--- a/dom/bindings/TypedArray.h
+++ b/dom/bindings/TypedArray.h
@@ -100,23 +100,22 @@ public:
   }
 
   inline bool inited() const {
     return !!mTypedObj;
   }
 
   // About shared memory:
   //
-  // Any DOM TypedArray as well as any DOM ArrayBufferView that does
-  // not represent a JS DataView can map the memory of either a JS
-  // ArrayBuffer or a JS SharedArrayBuffer.  (DataView cannot view
-  // shared memory.)  If the TypedArray maps a SharedArrayBuffer the
-  // Length() and Data() accessors on the DOM view will return zero
-  // and nullptr; to get the actual length and data, call the
-  // LengthAllowShared() and DataAllowShared() accessors instead.
+  // Any DOM TypedArray as well as any DOM ArrayBufferView can map the
+  // memory of either a JS ArrayBuffer or a JS SharedArrayBuffer.  If
+  // the TypedArray maps a SharedArrayBuffer the Length() and Data()
+  // accessors on the DOM view will return zero and nullptr; to get
+  // the actual length and data, call the LengthAllowShared() and
+  // DataAllowShared() accessors instead.
   //
   // Two methods are available for determining if a DOM view maps
   // shared memory.  The IsShared() method is cheap and can be called
   // if the view has been computed; the JS_GetTypedArraySharedness()
   // method is slightly more expensive and can be called on the Obj()
   // value if the view may not have been computed and if the value is
   // known to represent a JS TypedArray.
   //
--- a/dom/canvas/WebGL2Context.h
+++ b/dom/canvas/WebGL2Context.h
@@ -309,42 +309,42 @@ public:
 
     void ClearBufferfi(GLenum buffer, GLint drawBuffer, GLfloat depth, GLint stencil);
 
     // -------------------------------------------------------------------------
     // Sampler Objects - WebGL2ContextSamplers.cpp
 
     already_AddRefed<WebGLSampler> CreateSampler();
     void DeleteSampler(WebGLSampler* sampler);
-    bool IsSampler(WebGLSampler* sampler);
+    bool IsSampler(const WebGLSampler* sampler);
     void BindSampler(GLuint unit, WebGLSampler* sampler);
     void SamplerParameteri(WebGLSampler& sampler, GLenum pname, GLint param);
     void SamplerParameterf(WebGLSampler& sampler, GLenum pname, GLfloat param);
     void GetSamplerParameter(JSContext*, const WebGLSampler& sampler, GLenum pname,
                              JS::MutableHandleValue retval);
 
 
     // -------------------------------------------------------------------------
     // Sync objects - WebGL2ContextSync.cpp
 
     already_AddRefed<WebGLSync> FenceSync(GLenum condition, GLbitfield flags);
-    bool IsSync(WebGLSync* sync);
+    bool IsSync(const WebGLSync* sync);
     void DeleteSync(WebGLSync* sync);
     GLenum ClientWaitSync(const WebGLSync& sync, GLbitfield flags, GLuint64 timeout);
     void WaitSync(const WebGLSync& sync, GLbitfield flags, GLint64 timeout);
     void GetSyncParameter(JSContext*, const WebGLSync& sync, GLenum pname,
                           JS::MutableHandleValue retval);
 
 
     // -------------------------------------------------------------------------
     // Transform Feedback - WebGL2ContextTransformFeedback.cpp
 
     already_AddRefed<WebGLTransformFeedback> CreateTransformFeedback();
     void DeleteTransformFeedback(WebGLTransformFeedback* tf);
-    bool IsTransformFeedback(WebGLTransformFeedback* tf);
+    bool IsTransformFeedback(const WebGLTransformFeedback* tf);
     void BindTransformFeedback(GLenum target, WebGLTransformFeedback* tf);
     void BeginTransformFeedback(GLenum primitiveMode);
     void EndTransformFeedback();
     void PauseTransformFeedback();
     void ResumeTransformFeedback();
     void TransformFeedbackVaryings(WebGLProgram& program,
                                    const dom::Sequence<nsString>& varyings,
                                    GLenum bufferMode);
--- a/dom/canvas/WebGL2ContextFramebuffers.cpp
+++ b/dom/canvas/WebGL2ContextFramebuffers.cpp
@@ -100,60 +100,73 @@ WebGL2Context::GetFramebufferAttachmentP
                                                  GLenum attachment,
                                                  GLenum pname,
                                                  ErrorResult& out_error)
 {
     return WebGLContext::GetFramebufferAttachmentParameter(cx, target, attachment, pname,
                                                            out_error);
 }
 
-// Map attachments intended for the default buffer, to attachments for a non-
-// default buffer.
+////
+
 static bool
-TranslateDefaultAttachments(const dom::Sequence<GLenum>& in, dom::Sequence<GLenum>* out)
+ValidateBackbufferAttachmentEnum(WebGLContext* webgl, const char* funcName,
+                                 GLenum attachment)
 {
-    for (size_t i = 0; i < in.Length(); i++) {
-        switch (in[i]) {
-            case LOCAL_GL_COLOR:
-                if (!out->AppendElement(LOCAL_GL_COLOR_ATTACHMENT0, fallible)) {
-                    return false;
-                }
-                break;
+    switch (attachment) {
+    case LOCAL_GL_COLOR:
+    case LOCAL_GL_DEPTH:
+    case LOCAL_GL_STENCIL:
+        return true;
 
-            case LOCAL_GL_DEPTH:
-                if (!out->AppendElement(LOCAL_GL_DEPTH_ATTACHMENT, fallible)) {
-                    return false;
-                }
-                break;
+    default:
+        webgl->ErrorInvalidEnum("%s: attachment: invalid enum value 0x%x.",
+                                funcName, attachment);
+        return false;
+    }
+}
 
-            case LOCAL_GL_STENCIL:
-                if (!out->AppendElement(LOCAL_GL_STENCIL_ATTACHMENT, fallible)) {
-                    return false;
-                }
-                break;
-        }
+static bool
+ValidateFramebufferAttachmentEnum(WebGLContext* webgl, const char* funcName,
+                                  GLenum attachment)
+{
+    if (attachment >= LOCAL_GL_COLOR_ATTACHMENT0 &&
+        attachment <= webgl->LastColorAttachmentEnum())
+    {
+        return true;
     }
 
-    return true;
+    switch (attachment) {
+    case LOCAL_GL_DEPTH_ATTACHMENT:
+    case LOCAL_GL_STENCIL_ATTACHMENT:
+    case LOCAL_GL_DEPTH_STENCIL_ATTACHMENT:
+        return true;
+
+    default:
+        webgl->ErrorInvalidEnum("%s: attachment: invalid enum value 0x%x.",
+                                funcName, attachment);
+        return false;
+    }
 }
 
-void
-WebGL2Context::InvalidateFramebuffer(GLenum target,
-                                     const dom::Sequence<GLenum>& attachments,
-                                     ErrorResult& rv)
+bool
+WebGLContext::ValidateInvalidateFramebuffer(const char* funcName, GLenum target,
+                                            const dom::Sequence<GLenum>& attachments,
+                                            ErrorResult* const out_rv,
+                                            std::vector<GLenum>* const scopedVector,
+                                            GLsizei* const out_glNumAttachments,
+                                            const GLenum** const out_glAttachments)
 {
-    const char funcName[] = "invalidateSubFramebuffer";
+    if (IsContextLost())
+        return false;
 
-    if (IsContextLost())
-        return;
-
-    MakeContextCurrent();
+    gl->MakeCurrent();
 
     if (!ValidateFramebufferTarget(target, funcName))
-        return;
+        return false;
 
     const WebGLFramebuffer* fb;
     bool isDefaultFB;
     switch (target) {
     case LOCAL_GL_FRAMEBUFFER:
     case LOCAL_GL_DRAW_FRAMEBUFFER:
         fb = mBoundDrawFramebuffer;
         isDefaultFB = gl->Screen()->IsDrawFramebufferDefault();
@@ -163,111 +176,124 @@ WebGL2Context::InvalidateFramebuffer(GLe
         fb = mBoundReadFramebuffer;
         isDefaultFB = gl->Screen()->IsReadFramebufferDefault();
         break;
 
     default:
         MOZ_CRASH("GFX: Bad target.");
     }
 
-    const bool badColorAttachmentIsInvalidOp = true;
-    for (size_t i = 0; i < attachments.Length(); i++) {
-        if (!ValidateFramebufferAttachment(fb, attachments[i], funcName,
-                                           badColorAttachmentIsInvalidOp))
-        {
-            return;
+    *out_glNumAttachments = attachments.Length();
+    *out_glAttachments = attachments.Elements();
+
+    if (fb) {
+        for (const auto& attachment : attachments) {
+            if (!ValidateFramebufferAttachmentEnum(this, funcName, attachment))
+                return false;
+        }
+    } else {
+        for (const auto& attachment : attachments) {
+            if (!ValidateBackbufferAttachmentEnum(this, funcName, attachment))
+                return false;
+        }
+
+        if (!isDefaultFB) {
+            MOZ_ASSERT(scopedVector->empty());
+            scopedVector->reserve(attachments.Length());
+            for (const auto& attachment : attachments) {
+                switch (attachment) {
+                case LOCAL_GL_COLOR:
+                    scopedVector->push_back(LOCAL_GL_COLOR_ATTACHMENT0);
+                    break;
+
+                case LOCAL_GL_DEPTH:
+                    scopedVector->push_back(LOCAL_GL_DEPTH_ATTACHMENT);
+                    break;
+
+                case LOCAL_GL_STENCIL:
+                    scopedVector->push_back(LOCAL_GL_STENCIL_ATTACHMENT);
+                    break;
+
+                default:
+                    MOZ_CRASH();
+                }
+            }
+            *out_glNumAttachments = scopedVector->size();
+            *out_glAttachments = scopedVector->data();
         }
     }
 
-    // InvalidateFramebuffer is a hint to the driver. Should be OK to
-    // skip calls if not supported, for example by OSX 10.9 GL
-    // drivers.
-    if (!gl->IsSupported(gl::GLFeature::invalidate_framebuffer))
-        return;
+    return true;
+}
+
+void
+WebGL2Context::InvalidateFramebuffer(GLenum target,
+                                     const dom::Sequence<GLenum>& attachments,
+                                     ErrorResult& rv)
+{
+    const char funcName[] = "invalidateSubFramebuffer";
 
-    if (!fb && !isDefaultFB) {
-        dom::Sequence<GLenum> tmpAttachments;
-        if (!TranslateDefaultAttachments(attachments, &tmpAttachments)) {
-            rv.Throw(NS_ERROR_OUT_OF_MEMORY);
-            return;
-        }
+    std::vector<GLenum> scopedVector;
+    GLsizei glNumAttachments;
+    const GLenum* glAttachments;
+    if (!ValidateInvalidateFramebuffer(funcName, target, attachments, &rv, &scopedVector,
+                                       &glNumAttachments, &glAttachments))
+    {
+        return;
+    }
+
+    ////
 
-        gl->fInvalidateFramebuffer(target, tmpAttachments.Length(),
-                                   tmpAttachments.Elements());
-    } else {
-        gl->fInvalidateFramebuffer(target, attachments.Length(), attachments.Elements());
+    // Some drivers (like OSX 10.9 GL) just don't support invalidate_framebuffer.
+    const bool useFBInvalidation = (mAllowFBInvalidation &&
+                                    gl->IsSupported(gl::GLFeature::invalidate_framebuffer));
+    if (useFBInvalidation) {
+        gl->fInvalidateFramebuffer(target, glNumAttachments, glAttachments);
+        return;
     }
+
+    // Use clear instead?
+    // No-op for now.
 }
 
 void
 WebGL2Context::InvalidateSubFramebuffer(GLenum target, const dom::Sequence<GLenum>& attachments,
                                         GLint x, GLint y, GLsizei width, GLsizei height,
                                         ErrorResult& rv)
 {
     const char funcName[] = "invalidateSubFramebuffer";
 
-    if (IsContextLost())
-        return;
-
-    MakeContextCurrent();
-
-    if (!ValidateFramebufferTarget(target, funcName))
-        return;
-
-    if (width < 0 || height < 0) {
-        ErrorInvalidValue("%s: width and height must be >= 0.", funcName);
+    std::vector<GLenum> scopedVector;
+    GLsizei glNumAttachments;
+    const GLenum* glAttachments;
+    if (!ValidateInvalidateFramebuffer(funcName, target, attachments, &rv, &scopedVector,
+                                       &glNumAttachments, &glAttachments))
+    {
         return;
     }
 
-    const WebGLFramebuffer* fb;
-    bool isDefaultFB;
-    switch (target) {
-    case LOCAL_GL_FRAMEBUFFER:
-    case LOCAL_GL_DRAW_FRAMEBUFFER:
-        fb = mBoundDrawFramebuffer;
-        isDefaultFB = gl->Screen()->IsDrawFramebufferDefault();
-        break;
-
-    case LOCAL_GL_READ_FRAMEBUFFER:
-        fb = mBoundReadFramebuffer;
-        isDefaultFB = gl->Screen()->IsReadFramebufferDefault();
-        break;
-
-    default:
-        MOZ_CRASH("GFX: Bad target.");
+    if (!ValidateNonNegative(funcName, "width", width) ||
+        !ValidateNonNegative(funcName, "height", height))
+    {
+        return;
     }
 
-    const bool badColorAttachmentIsInvalidOp = true;
-    for (size_t i = 0; i < attachments.Length(); i++) {
-        if (!ValidateFramebufferAttachment(fb, attachments[i], funcName,
-                                           badColorAttachmentIsInvalidOp))
-        {
-            return;
-        }
+    ////
+
+    // Some drivers (like OSX 10.9 GL) just don't support invalidate_framebuffer.
+    const bool useFBInvalidation = (mAllowFBInvalidation &&
+                                    gl->IsSupported(gl::GLFeature::invalidate_framebuffer));
+    if (useFBInvalidation) {
+        gl->fInvalidateSubFramebuffer(target, glNumAttachments, glAttachments, x, y,
+                                      width, height);
+        return;
     }
 
-    // InvalidateFramebuffer is a hint to the driver. Should be OK to
-    // skip calls if not supported, for example by OSX 10.9 GL
-    // drivers.
-    if (!gl->IsSupported(gl::GLFeature::invalidate_framebuffer))
-        return;
-
-    if (!fb && !isDefaultFB) {
-        dom::Sequence<GLenum> tmpAttachments;
-        if (!TranslateDefaultAttachments(attachments, &tmpAttachments)) {
-            rv.Throw(NS_ERROR_OUT_OF_MEMORY);
-            return;
-        }
-
-        gl->fInvalidateSubFramebuffer(target, tmpAttachments.Length(),
-                                      tmpAttachments.Elements(), x, y, width, height);
-    } else {
-        gl->fInvalidateSubFramebuffer(target, attachments.Length(),
-                                      attachments.Elements(), x, y, width, height);
-    }
+    // Use clear instead?
+    // No-op for now.
 }
 
 void
 WebGL2Context::ReadBuffer(GLenum mode)
 {
     const char funcName[] = "readBuffer";
     if (IsContextLost())
         return;
--- a/dom/canvas/WebGL2ContextPrograms.cpp
+++ b/dom/canvas/WebGL2ContextPrograms.cpp
@@ -14,15 +14,15 @@ namespace mozilla {
 // Programs and shaders
 
 GLint
 WebGL2Context::GetFragDataLocation(const WebGLProgram& prog, const nsAString& name)
 {
     if (IsContextLost())
         return -1;
 
-    if (!ValidateObjectRef("getFragDataLocation: program", prog))
+    if (!ValidateObject("getFragDataLocation: program", prog))
         return -1;
 
     return prog.GetFragDataLocation(name);
 }
 
 } // namespace mozilla
--- a/dom/canvas/WebGL2ContextQueries.cpp
+++ b/dom/canvas/WebGL2ContextQueries.cpp
@@ -73,63 +73,48 @@ WebGLContext::CreateQuery(const char* fu
 
 void
 WebGLContext::DeleteQuery(WebGLQuery* query, const char* funcName)
 {
     if (!funcName) {
         funcName = "deleteQuery";
     }
 
-    if (IsContextLost())
-        return;
-
-    if (!query)
-        return;
-
-    if (!ValidateObjectAllowDeleted(funcName, query))
+    if (!ValidateDeleteObject(funcName, query))
         return;
 
     query->DeleteQuery();
 }
 
 bool
 WebGLContext::IsQuery(const WebGLQuery* query, const char* funcName)
 {
     if (!funcName) {
         funcName = "isQuery";
     }
 
-    if (IsContextLost())
-        return false;
-
-    if (!query)
-        return false;
-
-    if (!ValidateObjectAllowDeleted("isQuery", query))
+    if (!ValidateIsObject(funcName, query))
         return false;
 
     return query->IsQuery();
 }
 
 void
 WebGLContext::BeginQuery(GLenum target, WebGLQuery& query, const char* funcName)
 {
     if (!funcName) {
         funcName = "beginQuery";
     }
 
     if (IsContextLost())
         return;
 
-    if (!ValidateObjectAllowDeleted(funcName, &query))
+    if (!ValidateObject(funcName, query))
         return;
 
-    if (query.IsDeleted())
-        return ErrorInvalidOperation("%s: Cannot begin a deleted query.", funcName);
-
     const auto& slot = ValidateQuerySlotByTarget(funcName, target);
     if (!slot)
         return;
 
     if (*slot)
         return ErrorInvalidOperation("%s: Query target already active.", funcName);
 
     ////
@@ -233,18 +218,15 @@ WebGLContext::GetQueryParameter(JSContex
     if (!funcName) {
         funcName = "getQueryParameter";
     }
 
     retval.setNull();
     if (IsContextLost())
         return;
 
-    if (!ValidateObjectAllowDeleted(funcName, &query))
+    if (!ValidateObject(funcName, query))
         return;
 
-    if (query.IsDeleted())
-        return ErrorInvalidOperation("%s: Query must not be deleted.", funcName);
-
     query.GetQueryParameter(pname, retval);
 }
 
 } // namespace mozilla
--- a/dom/canvas/WebGL2ContextSamplers.cpp
+++ b/dom/canvas/WebGL2ContextSamplers.cpp
@@ -21,116 +21,98 @@ WebGL2Context::CreateSampler()
 
     RefPtr<WebGLSampler> globj = new WebGLSampler(this, sampler);
     return globj.forget();
 }
 
 void
 WebGL2Context::DeleteSampler(WebGLSampler* sampler)
 {
-    if (IsContextLost())
-        return;
-
-    if (!ValidateObjectAllowDeletedOrNull("deleteSampler", sampler))
-        return;
-
-    if (!sampler || sampler->IsDeleted())
+    if (!ValidateDeleteObject("deleteSampler", sampler))
         return;
 
     for (int n = 0; n < mGLMaxTextureUnits; n++) {
         if (mBoundSamplers[n] == sampler) {
             mBoundSamplers[n] = nullptr;
 
             InvalidateResolveCacheForTextureWithTexUnit(n);
         }
     }
 
     sampler->RequestDelete();
 }
 
 bool
-WebGL2Context::IsSampler(WebGLSampler* sampler)
+WebGL2Context::IsSampler(const WebGLSampler* sampler)
 {
-    if (IsContextLost())
-        return false;
-
-    if (!sampler)
-        return false;
-
-    if (!ValidateObjectAllowDeleted("isSampler", sampler))
-        return false;
-
-    if (sampler->IsDeleted())
+    if (!ValidateIsObject("isSampler", sampler))
         return false;
 
     MakeContextCurrent();
     return gl->fIsSampler(sampler->mGLName);
 }
 
 void
 WebGL2Context::BindSampler(GLuint unit, WebGLSampler* sampler)
 {
     if (IsContextLost())
         return;
 
-    if (!ValidateObjectAllowDeletedOrNull("bindSampler", sampler))
+    if (sampler && !ValidateObject("bindSampler", *sampler))
         return;
 
     if (GLint(unit) >= mGLMaxTextureUnits)
         return ErrorInvalidValue("bindSampler: unit must be < %d", mGLMaxTextureUnits);
 
-    if (sampler && sampler->IsDeleted())
-        return ErrorInvalidOperation("bindSampler: binding deleted sampler");
-
     ////
 
     gl->MakeCurrent();
     gl->fBindSampler(unit, sampler ? sampler->mGLName : 0);
 
     InvalidateResolveCacheForTextureWithTexUnit(unit);
     mBoundSamplers[unit] = sampler;
 }
 
 void
 WebGL2Context::SamplerParameteri(WebGLSampler& sampler, GLenum pname, GLint paramInt)
 {
     const char funcName[] = "samplerParameteri";
     if (IsContextLost())
         return;
 
-    if (!ValidateObjectRef(funcName, sampler))
+    if (!ValidateObject(funcName, sampler))
         return;
 
     sampler.SamplerParameter(funcName, pname, paramInt);
 }
 
 void
 WebGL2Context::SamplerParameterf(WebGLSampler& sampler, GLenum pname, GLfloat paramFloat)
 {
     const char funcName[] = "samplerParameterf";
     if (IsContextLost())
         return;
 
-    if (!ValidateObjectRef(funcName, sampler))
+    if (!ValidateObject(funcName, sampler))
         return;
 
     sampler.SamplerParameter(funcName, pname, WebGLIntOrFloat(paramFloat).AsInt());
 }
 
 void
 WebGL2Context::GetSamplerParameter(JSContext*, const WebGLSampler& sampler, GLenum pname,
                                    JS::MutableHandleValue retval)
 {
     const char funcName[] = "getSamplerParameter";
     retval.setNull();
 
     if (IsContextLost())
         return;
 
-    if (!ValidateObjectRef(funcName, sampler))
+    if (!ValidateObject(funcName, sampler))
         return;
 
     ////
 
     gl->MakeCurrent();
 
     switch (pname) {
     case LOCAL_GL_TEXTURE_MIN_FILTER:
--- a/dom/canvas/WebGL2ContextSync.cpp
+++ b/dom/canvas/WebGL2ContextSync.cpp
@@ -11,66 +11,60 @@
 namespace mozilla {
 
 // -------------------------------------------------------------------------
 // Sync objects
 
 already_AddRefed<WebGLSync>
 WebGL2Context::FenceSync(GLenum condition, GLbitfield flags)
 {
-   if (IsContextLost())
-       return nullptr;
+    if (IsContextLost())
+        return nullptr;
 
-   if (condition != LOCAL_GL_SYNC_GPU_COMMANDS_COMPLETE) {
-       ErrorInvalidEnum("fenceSync: condition must be SYNC_GPU_COMMANDS_COMPLETE");
-       return nullptr;
-   }
+    if (condition != LOCAL_GL_SYNC_GPU_COMMANDS_COMPLETE) {
+        ErrorInvalidEnum("fenceSync: condition must be SYNC_GPU_COMMANDS_COMPLETE");
+        return nullptr;
+    }
 
-   if (flags != 0) {
-       ErrorInvalidValue("fenceSync: flags must be 0");
-       return nullptr;
-   }
+    if (flags != 0) {
+        ErrorInvalidValue("fenceSync: flags must be 0");
+        return nullptr;
+    }
 
-   MakeContextCurrent();
-   RefPtr<WebGLSync> globj = new WebGLSync(this, condition, flags);
-   return globj.forget();
+    MakeContextCurrent();
+    RefPtr<WebGLSync> globj = new WebGLSync(this, condition, flags);
+    return globj.forget();
 }
 
 bool
-WebGL2Context::IsSync(WebGLSync* sync)
+WebGL2Context::IsSync(const WebGLSync* sync)
 {
-   if (IsContextLost())
-       return false;
+    if (!ValidateIsObject("isSync", sync))
+        return false;
 
-   return ValidateObjectAllowDeleted("isSync", sync) && !sync->IsDeleted();
+    return true;
 }
 
 void
 WebGL2Context::DeleteSync(WebGLSync* sync)
 {
-    if (IsContextLost())
-        return;
-
-    if (!ValidateObjectAllowDeletedOrNull("deleteSync", sync))
-        return;
-
-    if (!sync || sync->IsDeleted())
+    if (!ValidateDeleteObject("deleteSync", sync))
         return;
 
     sync->RequestDelete();
 }
 
 GLenum
 WebGL2Context::ClientWaitSync(const WebGLSync& sync, GLbitfield flags, GLuint64 timeout)
 {
     const char funcName[] = "clientWaitSync";
     if (IsContextLost())
         return LOCAL_GL_WAIT_FAILED;
 
-    if (!ValidateObjectRef(funcName, sync))
+    if (!ValidateObject(funcName, sync))
         return LOCAL_GL_WAIT_FAILED;
 
     if (flags != 0 && flags != LOCAL_GL_SYNC_FLUSH_COMMANDS_BIT) {
         ErrorInvalidValue("%s: `flags` must be SYNC_FLUSH_COMMANDS_BIT or 0.", funcName);
         return LOCAL_GL_WAIT_FAILED;
     }
 
     MakeContextCurrent();
@@ -79,17 +73,17 @@ WebGL2Context::ClientWaitSync(const WebG
 
 void
 WebGL2Context::WaitSync(const WebGLSync& sync, GLbitfield flags, GLint64 timeout)
 {
     const char funcName[] = "waitSync";
     if (IsContextLost())
         return;
 
-    if (!ValidateObjectRef(funcName, sync))
+    if (!ValidateObject(funcName, sync))
         return;
 
     if (flags != 0) {
         ErrorInvalidValue("%s: `flags` must be 0.", funcName);
         return;
     }
 
     if (timeout != -1) {
@@ -105,17 +99,17 @@ void
 WebGL2Context::GetSyncParameter(JSContext*, const WebGLSync& sync, GLenum pname,
                                 JS::MutableHandleValue retval)
 {
     const char funcName[] = "getSyncParameter";
     retval.setNull();
     if (IsContextLost())
         return;
 
-    if (!ValidateObjectRef(funcName, sync))
+    if (!ValidateObject(funcName, sync))
         return;
 
     ////
 
     gl->MakeCurrent();
 
     GLint result = 0;
     switch (pname) {
--- a/dom/canvas/WebGL2ContextTransformFeedback.cpp
+++ b/dom/canvas/WebGL2ContextTransformFeedback.cpp
@@ -27,66 +27,54 @@ WebGL2Context::CreateTransformFeedback()
     RefPtr<WebGLTransformFeedback> ret = new WebGLTransformFeedback(this, tf);
     return ret.forget();
 }
 
 void
 WebGL2Context::DeleteTransformFeedback(WebGLTransformFeedback* tf)
 {
     const char funcName[] = "deleteTransformFeedback";
-    if (IsContextLost())
-        return;
-
-    if (!ValidateObject(funcName, tf))
+    if (!ValidateDeleteObject(funcName, tf))
         return;
 
     if (tf->mIsActive) {
         ErrorInvalidOperation("%s: Cannot delete active transform feedbacks.", funcName);
         return;
     }
 
     if (mBoundTransformFeedback == tf) {
         BindTransformFeedback(LOCAL_GL_TRANSFORM_FEEDBACK, nullptr);
     }
 
     tf->RequestDelete();
 }
 
 bool
-WebGL2Context::IsTransformFeedback(WebGLTransformFeedback* tf)
+WebGL2Context::IsTransformFeedback(const WebGLTransformFeedback* tf)
 {
-    if (IsContextLost())
-        return false;
-
-    if (!ValidateObjectAllowDeletedOrNull("isTransformFeedback", tf))
-        return false;
-
-    if (!tf || tf->IsDeleted())
+    if (!ValidateIsObject("isTransformFeedback", tf))
         return false;
 
     MakeContextCurrent();
     return gl->fIsTransformFeedback(tf->mGLName);
 }
 
 void
 WebGL2Context::BindTransformFeedback(GLenum target, WebGLTransformFeedback* tf)
 {
     const char funcName[] = "bindTransformFeedback";
     if (IsContextLost())
         return;
 
     if (target != LOCAL_GL_TRANSFORM_FEEDBACK)
         return ErrorInvalidEnum("%s: `target` must be TRANSFORM_FEEDBACK.", funcName);
 
-    if (!ValidateObjectAllowDeletedOrNull(funcName, tf))
+    if (tf && !ValidateObject(funcName, *tf))
         return;
 
-    if (tf && tf->IsDeleted())
-        return ErrorInvalidOperation("%s: TFO already deleted.", funcName);
-
     if (mBoundTransformFeedback->mIsActive &&
         !mBoundTransformFeedback->mIsPaused)
     {
         ErrorInvalidOperation("%s: Currently bound transform feedback is active and not"
                               " paused.",
                               funcName);
         return;
     }
@@ -138,27 +126,27 @@ WebGL2Context::ResumeTransformFeedback()
 void
 WebGL2Context::TransformFeedbackVaryings(WebGLProgram& program,
                                          const dom::Sequence<nsString>& varyings,
                                          GLenum bufferMode)
 {
     if (IsContextLost())
         return;
 
-    if (!ValidateObjectRef("transformFeedbackVaryings: program", program))
+    if (!ValidateObject("transformFeedbackVaryings: program", program))
         return;
 
     program.TransformFeedbackVaryings(varyings, bufferMode);
 }
 
 already_AddRefed<WebGLActiveInfo>
 WebGL2Context::GetTransformFeedbackVarying(const WebGLProgram& program, GLuint index)
 {
     if (IsContextLost())
         return nullptr;
 
-    if (!ValidateObjectRef("getTransformFeedbackVarying: program", program))
+    if (!ValidateObject("getTransformFeedbackVarying: program", program))
         return nullptr;
 
     return program.GetTransformFeedbackVarying(index);
 }
 
 } // namespace mozilla
--- a/dom/canvas/WebGL2ContextUniforms.cpp
+++ b/dom/canvas/WebGL2ContextUniforms.cpp
@@ -133,17 +133,17 @@ void
 WebGL2Context::GetUniformIndices(const WebGLProgram& program,
                                  const dom::Sequence<nsString>& uniformNames,
                                  dom::Nullable< nsTArray<GLuint> >& retval)
 {
     retval.SetNull();
     if (IsContextLost())
         return;
 
-    if (!ValidateObjectRef("getUniformIndices: program", program))
+    if (!ValidateObject("getUniformIndices: program", program))
         return;
 
     if (!uniformNames.Length())
         return;
 
     program.GetUniformIndices(uniformNames, retval);
 }
 
@@ -174,17 +174,17 @@ WebGL2Context::GetActiveUniforms(JSConte
     const char funcName[] = "getActiveUniforms";
     retval.setNull();
     if (IsContextLost())
         return;
 
     if (!ValidateUniformEnum(this, pname, funcName))
         return;
 
-    if (!ValidateObjectRef("getActiveUniforms: program", program))
+    if (!ValidateObject("getActiveUniforms: program", program))
         return;
 
     const auto& count = uniformIndices.Length();
 
     JS::Rooted<JSObject*> array(cx, JS_NewArrayObject(cx, count));
     UniquePtr<GLint[]> samples(new GLint[count]);
     if (!array || !samples) {
         ErrorOutOfMemory("%s: Failed to allocate buffers.", funcName);
@@ -226,33 +226,33 @@ WebGL2Context::GetActiveUniforms(JSConte
 
 GLuint
 WebGL2Context::GetUniformBlockIndex(const WebGLProgram& program,
                                     const nsAString& uniformBlockName)
 {
     if (IsContextLost())
         return 0;
 
-    if (!ValidateObjectRef("getUniformBlockIndex: program", program))
+    if (!ValidateObject("getUniformBlockIndex: program", program))
         return 0;
 
     return program.GetUniformBlockIndex(uniformBlockName);
 }
 
 void
 WebGL2Context::GetActiveUniformBlockParameter(JSContext* cx, const WebGLProgram& program,
                                               GLuint uniformBlockIndex, GLenum pname,
                                               JS::MutableHandleValue out_retval,
                                               ErrorResult& out_error)
 {
     out_retval.setNull();
     if (IsContextLost())
         return;
 
-    if (!ValidateObjectRef("getActiveUniformBlockParameter: program", program))
+    if (!ValidateObject("getActiveUniformBlockParameter: program", program))
         return;
 
     MakeContextCurrent();
 
     switch(pname) {
     case LOCAL_GL_UNIFORM_BLOCK_REFERENCED_BY_VERTEX_SHADER:
     case LOCAL_GL_UNIFORM_BLOCK_REFERENCED_BY_FRAGMENT_SHADER:
     case LOCAL_GL_UNIFORM_BLOCK_BINDING:
@@ -273,28 +273,28 @@ WebGL2Context::GetActiveUniformBlockPara
 void
 WebGL2Context::GetActiveUniformBlockName(const WebGLProgram& program,
                                          GLuint uniformBlockIndex, nsAString& retval)
 {
     retval.SetIsVoid(true);
     if (IsContextLost())
         return;
 
-    if (!ValidateObjectRef("getActiveUniformBlockName: program", program))
+    if (!ValidateObject("getActiveUniformBlockName: program", program))
         return;
 
     program.GetActiveUniformBlockName(uniformBlockIndex, retval);
 }
 
 void
 WebGL2Context::UniformBlockBinding(WebGLProgram& program, GLuint uniformBlockIndex,
                                    GLuint uniformBlockBinding)
 {
     if (IsContextLost())
         return;
 
-    if (!ValidateObjectRef("uniformBlockBinding: program", program))
+    if (!ValidateObject("uniformBlockBinding: program", program))
         return;
 
     program.UniformBlockBinding(uniformBlockIndex, uniformBlockBinding);
 }
 
 } // namespace mozilla
--- a/dom/canvas/WebGLBuffer.cpp
+++ b/dom/canvas/WebGLBuffer.cpp
@@ -8,17 +8,17 @@
 #include "GLContext.h"
 #include "mozilla/dom/WebGLRenderingContextBinding.h"
 #include "WebGLContext.h"
 #include "WebGLElementArrayCache.h"
 
 namespace mozilla {
 
 WebGLBuffer::WebGLBuffer(WebGLContext* webgl, GLuint buf)
-    : WebGLContextBoundObject(webgl)
+    : WebGLRefCountedObject(webgl)
     , mGLName(buf)
     , mContent(Kind::Undefined)
     , mUsage(LOCAL_GL_STATIC_DRAW)
     , mByteLength(0)
 {
     mContext->mBuffers.insertBack(this);
 }
 
--- a/dom/canvas/WebGLBuffer.h
+++ b/dom/canvas/WebGLBuffer.h
@@ -17,17 +17,16 @@
 namespace mozilla {
 
 class WebGLElementArrayCache;
 
 class WebGLBuffer final
     : public nsWrapperCache
     , public WebGLRefCountedObject<WebGLBuffer>
     , public LinkedListElement<WebGLBuffer>
-    , public WebGLContextBoundObject
 {
     friend class WebGLContext;
     friend class WebGL2Context;
     friend class WebGLTexture;
     friend class WebGLTransformFeedback;
 
 public:
     enum class Kind {
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -121,16 +121,17 @@ WebGLContext::WebGLContext()
     , mLayerIsMirror(false)
     , mBypassShaderValidation(false)
     , mBuffersForUB_Dirty(true)
     , mContextLossHandler(this)
     , mNeedsFakeNoAlpha(false)
     , mNeedsFakeNoDepth(false)
     , mNeedsFakeNoStencil(false)
     , mNeedsEmulatedLoneDepthStencil(false)
+    , mAllowFBInvalidation(gfxPrefs::WebGLFBInvalidation())
 {
     mGeneration = 0;
     mInvalidated = false;
     mCapturedFrameInvalidated = false;
     mShouldPresent = true;
     mResetLayer = true;
     mOptionsFrozen = false;
     mMinCapability = false;
@@ -216,17 +217,17 @@ WebGLContext::~WebGLContext()
     DestroyResourcesAndContext();
     if (NS_IsMainThread()) {
         // XXX mtseng: bug 709490, not thread safe
         WebGLMemoryTracker::RemoveWebGLContext(this);
     }
 }
 
 template<typename T>
-static void
+void
 ClearLinkedList(LinkedList<T>& list)
 {
     while (!list.isEmpty()) {
         list.getLast()->DeleteOnce();
     }
 }
 
 void
--- a/dom/canvas/WebGLContext.h
+++ b/dom/canvas/WebGLContext.h
@@ -531,21 +531,16 @@ public:
     void Flush();
     void Finish();
     void FramebufferRenderbuffer(GLenum target, GLenum attachment,
                                  GLenum rbTarget, WebGLRenderbuffer* rb);
     void FramebufferTexture2D(GLenum target, GLenum attachment,
                               GLenum texImageTarget, WebGLTexture* tex,
                               GLint level);
 
-    // Framebuffer validation
-    bool ValidateFramebufferAttachment(const WebGLFramebuffer* fb, GLenum attachment,
-                                       const char* funcName,
-                                       bool badColorAttachmentIsInvalidOp = false);
-
     void FrontFace(GLenum mode);
     already_AddRefed<WebGLActiveInfo> GetActiveAttrib(const WebGLProgram& prog,
                                                       GLuint index);
     already_AddRefed<WebGLActiveInfo> GetActiveUniform(const WebGLProgram& prog,
                                                        GLuint index);
 
     void
     GetAttachedShaders(const WebGLProgram& prog,
@@ -616,21 +611,21 @@ public:
     {
         retval.set(GetUniform(cx, prog, loc));
     }
 
     already_AddRefed<WebGLUniformLocation>
     GetUniformLocation(const WebGLProgram& prog, const nsAString& name);
 
     void Hint(GLenum target, GLenum mode);
-    bool IsFramebuffer(WebGLFramebuffer* fb);
-    bool IsProgram(WebGLProgram* prog);
-    bool IsRenderbuffer(WebGLRenderbuffer* rb);
-    bool IsShader(WebGLShader* shader);
-    bool IsVertexArray(WebGLVertexArray* vao);
+    bool IsFramebuffer(const WebGLFramebuffer* fb);
+    bool IsProgram(const WebGLProgram* prog);
+    bool IsRenderbuffer(const WebGLRenderbuffer* rb);
+    bool IsShader(const WebGLShader* shader);
+    bool IsVertexArray(const WebGLVertexArray* vao);
     void LineWidth(GLfloat width);
     void LinkProgram(WebGLProgram& prog);
     void PixelStorei(GLenum pname, GLint param);
     void PolygonOffset(GLfloat factor, GLfloat units);
 
     already_AddRefed<layers::SharedSurfaceTextureClient> GetVRFrame();
     bool StartVRPresentation();
 protected:
@@ -1628,43 +1623,105 @@ protected:
     bool ConvertImage(size_t width, size_t height, size_t srcStride,
                       size_t dstStride, const uint8_t* src, uint8_t* dst,
                       WebGLTexelFormat srcFormat, bool srcPremultiplied,
                       WebGLTexelFormat dstFormat, bool dstPremultiplied,
                       size_t dstTexelSize);
 
     //////
 public:
-    // Returns false if `object` is null or not valid.
-    template<class ObjectType>
-    bool ValidateObject(const char* info, const ObjectType* object);
+    bool ValidateObjectAllowDeleted(const char* funcName,
+                                    const WebGLContextBoundObject& object)
+    {
+        if (!object.IsCompatibleWithContext(this)) {
+            ErrorInvalidOperation("%s: Object from different WebGL context (or older"
+                                  " generation of this one) passed as argument.",
+                                  funcName);
+            return false;
+        }
+
+        return true;
+    }
+
+    bool ValidateObject(const char* funcName, const WebGLDeletableObject& object,
+                        bool isShaderOrProgram = false)
+    {
+        if (!ValidateObjectAllowDeleted(funcName, object))
+            return false;
 
-    // Returns false if `object` is not valid.
-    template<class ObjectType>
-    bool ValidateObjectRef(const char* info, const ObjectType& object);
-
-    // Returns false if `object` is not valid.  Considers null to be valid.
-    template<class ObjectType>
-    bool ValidateObjectAllowNull(const char* info, const ObjectType* object);
+        if (isShaderOrProgram) {
+            /* GLES 3.0.5 p45:
+             * "Commands that accept shader or program object names will generate the
+             *  error INVALID_VALUE if the provided name is not the name of either a
+             *  shader or program object[.]"
+             * Further, shaders and programs appear to be different from other objects,
+             * in that their lifetimes are better defined. However, they also appear to
+             * allow use of objects marked for deletion, and only reject
+             * actually-destroyed objects.
+             */
+            if (object.IsDeleted()) {
+                ErrorInvalidValue("%s: Shader or program object argument cannot have been"
+                                  " deleted.",
+                                  funcName);
+                return false;
+            }
+        } else {
+            if (object.IsDeleteRequested()) {
+                ErrorInvalidOperation("%s: Object argument cannot have been marked for"
+                                      " deletion.",
+                                      funcName);
+                return false;
+            }
+        }
 
-    // Returns false if `object` is not valid, but considers deleted objects and
-    // null objects valid.
-    template<class ObjectType>
-    bool ValidateObjectAllowDeletedOrNull(const char* info, const ObjectType* object);
+        return true;
+    }
+
+    ////
+
+    bool ValidateObject(const char* funcName, const WebGLProgram& object);
+    bool ValidateObject(const char* funcName, const WebGLShader& object);
+
+    ////
+
+    bool ValidateIsObject(const char* funcName,
+                          const WebGLDeletableObject* object) const
+    {
+        if (IsContextLost())
+            return false;
+
+        if (!object)
+            return false;
+
+        if (!object->IsCompatibleWithContext(this))
+            return false;
 
-    // Returns false if `object` is null or not valid, but considers deleted
-    // objects valid.
-    template<class ObjectType>
-    bool ValidateObjectAllowDeleted(const char* info, const ObjectType* object);
+        if (object->IsDeleted())
+            return false;
+
+        return true;
+    }
+
+    bool ValidateDeleteObject(const char* funcName, const WebGLDeletableObject* object) {
+        if (IsContextLost())
+            return false;
 
-private:
-    // Like ValidateObject, but only for cases when `object` is known to not be
-    // null already.
-    template<class ObjectType>
-    bool ValidateObjectAssumeNonNull(const char* info, const ObjectType* object);
+        if (!object)
+            return false;
+
+        if (!ValidateObjectAllowDeleted(funcName, *object))
+            return false;
+
+        if (object->IsDeleteRequested())
+            return false;
+
+        return true;
+    }
+
+    ////
 
 private:
     // -------------------------------------------------------------------------
     // Context customization points
     virtual WebGLVertexArray* CreateVertexArrayImpl();
 
     virtual bool ValidateAttribPointerType(bool integerMode, GLenum type, uint32_t* alignment, const char* info) = 0;
     virtual bool ValidateUniformMatrixTranspose(bool transpose, const char* info) = 0;
@@ -1682,16 +1739,22 @@ protected:
     nsTArray<WebGLRefPtr<WebGLSampler> > mBoundSamplers;
 
     void ResolveTexturesForDraw() const;
 
     WebGLRefPtr<WebGLProgram> mCurrentProgram;
     RefPtr<const webgl::LinkedProgramInfo> mActiveProgramLinkInfo;
 
     bool ValidateFramebufferTarget(GLenum target, const char* const info);
+    bool ValidateInvalidateFramebuffer(const char* funcName, GLenum target,
+                                       const dom::Sequence<GLenum>& attachments,
+                                       ErrorResult* const out_rv,
+                                       std::vector<GLenum>* const scopedVector,
+                                       GLsizei* const out_glNumAttachments,
+                                       const GLenum** const out_glAttachments);
 
     WebGLRefPtr<WebGLFramebuffer> mBoundDrawFramebuffer;
     WebGLRefPtr<WebGLFramebuffer> mBoundReadFramebuffer;
     WebGLRefPtr<WebGLRenderbuffer> mBoundRenderbuffer;
     WebGLRefPtr<WebGLTransformFeedback> mBoundTransformFeedback;
     WebGLRefPtr<WebGLVertexArray> mBoundVertexArray;
 
     LinkedList<WebGLBuffer> mBuffers;
@@ -1823,16 +1886,18 @@ protected:
 
     uint64_t mLastUseIndex;
 
     bool mNeedsFakeNoAlpha;
     bool mNeedsFakeNoDepth;
     bool mNeedsFakeNoStencil;
     bool mNeedsEmulatedLoneDepthStencil;
 
+    const bool mAllowFBInvalidation;
+
     bool Has64BitTimestamps() const;
 
     struct ScopedMaskWorkaround {
         WebGLContext& mWebGL;
         const bool mFakeNoAlpha;
         const bool mFakeNoDepth;
         const bool mFakeNoStencil;
 
@@ -1945,92 +2010,16 @@ public:
 
 // used by DOM bindings in conjunction with GetParentObject
 inline nsISupports*
 ToSupports(WebGLContext* webgl)
 {
     return static_cast<nsIDOMWebGLRenderingContext*>(webgl);
 }
 
-/**
- ** Template implementations
- **/
-
-template<class ObjectType>
-inline bool
-WebGLContext::ValidateObjectAllowDeletedOrNull(const char* info, const ObjectType* object)
-{
-    if (object && !object->IsCompatibleWithContext(this)) {
-        ErrorInvalidOperation("%s: object from different WebGL context "
-                              "(or older generation of this one) "
-                              "passed as argument", info);
-        return false;
-    }
-
-    return true;
-}
-
-template<class ObjectType>
-inline bool
-WebGLContext::ValidateObjectAssumeNonNull(const char* info, const ObjectType* object)
-{
-    MOZ_ASSERT(object);
-
-    if (!ValidateObjectAllowDeletedOrNull(info, object))
-        return false;
-
-    if (object->IsDeleted()) {
-        ErrorInvalidValue("%s: Deleted object passed as argument.", info);
-        return false;
-    }
-
-    return true;
-}
-
-template<class ObjectType>
-inline bool
-WebGLContext::ValidateObjectAllowNull(const char* info, const ObjectType* object)
-{
-    if (!object)
-        return true;
-
-    return ValidateObjectAssumeNonNull(info, object);
-}
-
-template<class ObjectType>
-inline bool
-WebGLContext::ValidateObjectAllowDeleted(const char* info, const ObjectType* object)
-{
-    if (!object) {
-        ErrorInvalidValue("%s: null object passed as argument", info);
-        return false;
-    }
-
-    return ValidateObjectAllowDeletedOrNull(info, object);
-}
-
-template<class ObjectType>
-inline bool
-WebGLContext::ValidateObject(const char* info, const ObjectType* object)
-{
-    if (!object) {
-        ErrorInvalidValue("%s: null object passed as argument", info);
-        return false;
-    }
-
-    return ValidateObjectAssumeNonNull(info, object);
-}
-
-template<class ObjectType>
-inline bool
-WebGLContext::ValidateObjectRef(const char* info, const ObjectType& object)
-{
-    return ValidateObjectAssumeNonNull(info, &object);
-}
-
 // Returns `value` rounded to the next highest multiple of `multiple`.
 // AKA PadToAlignment, StrideForAlignment.
 template<typename V, typename M>
 V
 RoundUpToMultipleOf(const V& value, const M& multiple)
 {
     return ((value + multiple - 1) / multiple) * multiple;
 }
--- a/dom/canvas/WebGLContextBuffers.cpp
+++ b/dom/canvas/WebGLContextBuffers.cpp
@@ -114,22 +114,19 @@ WebGLContext::ValidateIndexedBufferSlot(
 
 void
 WebGLContext::BindBuffer(GLenum target, WebGLBuffer* buffer)
 {
     const char funcName[] = "bindBuffer";
     if (IsContextLost())
         return;
 
-    if (!ValidateObjectAllowDeletedOrNull(funcName, buffer))
+    if (buffer && !ValidateObject(funcName, *buffer))
         return;
 
-    if (buffer && buffer->IsDeleted())
-        return ErrorInvalidOperation("%s: Cannot bind a deleted object.", funcName);
-
     const auto& slot = ValidateBufferSlot(funcName, target);
     if (!slot)
         return;
 
     if (buffer && !buffer->ValidateCanBindToTarget(funcName, target))
         return;
 
     gl->MakeCurrent();
@@ -178,22 +175,19 @@ WebGLContext::ValidateIndexedBufferBindi
 
 void
 WebGLContext::BindBufferBase(GLenum target, GLuint index, WebGLBuffer* buffer)
 {
     const char funcName[] = "bindBufferBase";
     if (IsContextLost())
         return;
 
-    if (!ValidateObjectAllowDeletedOrNull(funcName, buffer))
+    if (buffer && !ValidateObject(funcName, *buffer))
         return;
 
-    if (buffer && buffer->IsDeleted())
-        return ErrorInvalidOperation("%s: Cannot bind a deleted object.", funcName);
-
     WebGLRefPtr<WebGLBuffer>* genericBinding;
     IndexedBufferBinding* indexedBinding;
     if (!ValidateIndexedBufferBinding(funcName, target, index, &genericBinding,
                                       &indexedBinding))
     {
         return;
     }
 
@@ -229,22 +223,19 @@ WebGLContext::BindBufferBase(GLenum targ
 void
 WebGLContext::BindBufferRange(GLenum target, GLuint index, WebGLBuffer* buffer,
                               WebGLintptr offset, WebGLsizeiptr size)
 {
     const char funcName[] = "bindBufferRange";
     if (IsContextLost())
         return;
 
-    if (!ValidateObjectAllowDeletedOrNull(funcName, buffer))
+    if (buffer && !ValidateObject(funcName, *buffer))
         return;
 
-    if (buffer && buffer->IsDeleted())
-        return ErrorInvalidOperation("%s: Cannot bind a deleted object.", funcName);
-
     if (!ValidateNonNegative(funcName, "offset", offset) ||
         !ValidateNonNegative(funcName, "size", size))
     {
         return;
     }
 
     WebGLRefPtr<WebGLBuffer>* genericBinding;
     IndexedBufferBinding* indexedBinding;
@@ -471,23 +462,17 @@ WebGLContext::CreateBuffer()
 
     RefPtr<WebGLBuffer> globj = new WebGLBuffer(this, buf);
     return globj.forget();
 }
 
 void
 WebGLContext::DeleteBuffer(WebGLBuffer* buffer)
 {
-    if (IsContextLost())
-        return;
-
-    if (!ValidateObjectAllowDeletedOrNull("deleteBuffer", buffer))
-        return;
-
-    if (!buffer || buffer->IsDeleted())
+    if (!ValidateDeleteObject("deleteBuffer", buffer))
         return;
 
     ////
 
     const auto fnClearIfBuffer = [&](WebGLRefPtr<WebGLBuffer>& bindPoint) {
         if (bindPoint == buffer) {
             bindPoint = nullptr;
         }
@@ -525,22 +510,16 @@ WebGLContext::DeleteBuffer(WebGLBuffer* 
     ////
 
     buffer->RequestDelete();
 }
 
 bool
 WebGLContext::IsBuffer(WebGLBuffer* buffer)
 {
-    if (IsContextLost())
-        return false;
-
-    if (!ValidateObjectAllowDeleted("isBuffer", buffer))
-        return false;
-
-    if (buffer->IsDeleted())
+    if (!ValidateIsObject("isBuffer", buffer))
         return false;
 
     MakeContextCurrent();
     return gl->fIsBuffer(buffer->mGLName);
 }
 
 } // namespace mozilla
--- a/dom/canvas/WebGLContextGL.cpp
+++ b/dom/canvas/WebGLContextGL.cpp
@@ -52,16 +52,28 @@
 #include "mozilla/dom/ImageData.h"
 #include "mozilla/dom/ToJSValue.h"
 #include "mozilla/EndianUtils.h"
 #include "mozilla/RefPtr.h"
 #include "mozilla/UniquePtrExtensions.h"
 
 namespace mozilla {
 
+bool
+WebGLContext::ValidateObject(const char* funcName, const WebGLProgram& object)
+{
+    return ValidateObject(funcName, object, true);
+}
+
+bool
+WebGLContext::ValidateObject(const char* funcName, const WebGLShader& object)
+{
+    return ValidateObject(funcName, object, true);
+}
+
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
 using namespace mozilla::gl;
 
 //
 //  WebGL API
 //
 
@@ -87,53 +99,50 @@ WebGLContext::ActiveTexture(GLenum textu
 }
 
 void
 WebGLContext::AttachShader(WebGLProgram& program, WebGLShader& shader)
 {
     if (IsContextLost())
         return;
 
-    if (!ValidateObjectRef("attachShader: program", program) ||
-        !ValidateObjectRef("attachShader: shader", shader))
+    if (!ValidateObject("attachShader: program", program) ||
+        !ValidateObject("attachShader: shader", shader))
     {
         return;
     }
 
     program.AttachShader(&shader);
 }
 
 void
 WebGLContext::BindAttribLocation(WebGLProgram& prog, GLuint location,
                                  const nsAString& name)
 {
     if (IsContextLost())
         return;
 
-    if (!ValidateObjectRef("bindAttribLocation: program", prog))
+    if (!ValidateObject("bindAttribLocation: program", prog))
         return;
 
     prog.BindAttribLocation(location, name);
 }
 
 void
 WebGLContext::BindFramebuffer(GLenum target, WebGLFramebuffer* wfb)
 {
     if (IsContextLost())
         return;
 
     if (!ValidateFramebufferTarget(target, "bindFramebuffer"))
         return;
 
-    if (!ValidateObjectAllowDeletedOrNull("bindFramebuffer", wfb))
+    if (wfb && !ValidateObject("bindFramebuffer", *wfb))
         return;
 
-    if (wfb && wfb->IsDeleted())
-        return ErrorInvalidOperation("bindFramebuffer: Cannot bind a deleted object.");
-
     MakeContextCurrent();
 
     if (!wfb) {
         gl->fBindFramebuffer(target, 0);
     } else {
         GLuint framebuffername = wfb->mGLName;
         gl->fBindFramebuffer(target, framebuffername);
 #ifdef ANDROID
@@ -161,22 +170,19 @@ void
 WebGLContext::BindRenderbuffer(GLenum target, WebGLRenderbuffer* wrb)
 {
     if (IsContextLost())
         return;
 
     if (target != LOCAL_GL_RENDERBUFFER)
         return ErrorInvalidEnumInfo("bindRenderbuffer: target", target);
 
-    if (!ValidateObjectAllowDeletedOrNull("bindRenderbuffer", wrb))
+    if (wrb && !ValidateObject("bindRenderbuffer", *wrb))
         return;
 
-    if (wrb && wrb->IsDeleted())
-        return ErrorInvalidOperation("bindRenderbuffer: Cannot bind a deleted object.");
-
     // Usually, we would now call into glBindRenderbuffer. However, since we have to
     // potentially emulate packed-depth-stencil, there's not a specific renderbuffer that
     // we know we should bind here.
     // Instead, we do all renderbuffer binding lazily.
 
     if (wrb) {
         wrb->mHasBeenBound = true;
     }
@@ -315,23 +321,17 @@ WebGLContext::CullFace(GLenum face)
 
     MakeContextCurrent();
     gl->fCullFace(face);
 }
 
 void
 WebGLContext::DeleteFramebuffer(WebGLFramebuffer* fbuf)
 {
-    if (IsContextLost())
-        return;
-
-    if (!ValidateObjectAllowDeletedOrNull("deleteFramebuffer", fbuf))
-        return;
-
-    if (!fbuf || fbuf->IsDeleted())
+    if (!ValidateDeleteObject("deleteFramebuffer", fbuf))
         return;
 
     fbuf->RequestDelete();
 
     if (mBoundReadFramebuffer == mBoundDrawFramebuffer) {
         if (mBoundDrawFramebuffer == fbuf) {
             BindFramebuffer(LOCAL_GL_FRAMEBUFFER,
                             static_cast<WebGLFramebuffer*>(nullptr));
@@ -343,23 +343,17 @@ WebGLContext::DeleteFramebuffer(WebGLFra
         BindFramebuffer(LOCAL_GL_READ_FRAMEBUFFER,
                         static_cast<WebGLFramebuffer*>(nullptr));
     }
 }
 
 void
 WebGLContext::DeleteRenderbuffer(WebGLRenderbuffer* rbuf)
 {
-    if (IsContextLost())
-        return;
-
-    if (!ValidateObjectAllowDeletedOrNull("deleteRenderbuffer", rbuf))
-        return;
-
-    if (!rbuf || rbuf->IsDeleted())
+    if (!ValidateDeleteObject("deleteRenderbuffer", rbuf))
         return;
 
     if (mBoundDrawFramebuffer)
         mBoundDrawFramebuffer->DetachRenderbuffer(rbuf);
 
     if (mBoundReadFramebuffer)
         mBoundReadFramebuffer->DetachRenderbuffer(rbuf);
 
@@ -369,23 +363,17 @@ WebGLContext::DeleteRenderbuffer(WebGLRe
         BindRenderbuffer(LOCAL_GL_RENDERBUFFER, nullptr);
 
     rbuf->RequestDelete();
 }
 
 void
 WebGLContext::DeleteTexture(WebGLTexture* tex)
 {
-    if (IsContextLost())
-        return;
-
-    if (!ValidateObjectAllowDeletedOrNull("deleteTexture", tex))
-        return;
-
-    if (!tex || tex->IsDeleted())
+    if (!ValidateDeleteObject("deleteTexture", tex))
         return;
 
     if (mBoundDrawFramebuffer)
         mBoundDrawFramebuffer->DetachTexture(tex);
 
     if (mBoundReadFramebuffer)
         mBoundReadFramebuffer->DetachTexture(tex);
 
@@ -403,53 +391,41 @@ WebGLContext::DeleteTexture(WebGLTexture
     ActiveTexture(LOCAL_GL_TEXTURE0 + activeTexture);
 
     tex->RequestDelete();
 }
 
 void
 WebGLContext::DeleteProgram(WebGLProgram* prog)
 {
-    if (IsContextLost())
-        return;
-
-    if (!ValidateObjectAllowDeletedOrNull("deleteProgram", prog))
-        return;
-
-    if (!prog || prog->IsDeleted())
+    if (!ValidateDeleteObject("deleteProgram", prog))
         return;
 
     prog->RequestDelete();
 }
 
 void
 WebGLContext::DeleteShader(WebGLShader* shader)
 {
-    if (IsContextLost())
-        return;
-
-    if (!ValidateObjectAllowDeletedOrNull("deleteShader", shader))
-        return;
-
-    if (!shader || shader->IsDeleted())
+    if (!ValidateDeleteObject("deleteShader", shader))
         return;
 
     shader->RequestDelete();
 }
 
 void
 WebGLContext::DetachShader(WebGLProgram& program, const WebGLShader& shader)
 {
     if (IsContextLost())
         return;
 
     // It's valid to attempt to detach a deleted shader, since it's still a
     // shader.
-    if (!ValidateObjectRef("detachShader: program", program) ||
-        !ValidateObjectAllowDeleted("detachShader: shader", &shader))
+    if (!ValidateObject("detachShader: program", program) ||
+        !ValidateObjectAllowDeleted("detachShader: shader", shader))
     {
         return;
     }
 
     program.DetachShader(&shader);
 }
 
 void
@@ -564,55 +540,55 @@ WebGLContext::FrontFace(GLenum mode)
 }
 
 already_AddRefed<WebGLActiveInfo>
 WebGLContext::GetActiveAttrib(const WebGLProgram& prog, GLuint index)
 {
     if (IsContextLost())
         return nullptr;
 
-    if (!ValidateObjectRef("getActiveAttrib: program", prog))
+    if (!ValidateObject("getActiveAttrib: program", prog))
         return nullptr;
 
     return prog.GetActiveAttrib(index);
 }
 
 already_AddRefed<WebGLActiveInfo>
 WebGLContext::GetActiveUniform(const WebGLProgram& prog, GLuint index)
 {
     if (IsContextLost())
         return nullptr;
 
-    if (!ValidateObjectRef("getActiveUniform: program", prog))
+    if (!ValidateObject("getActiveUniform: program", prog))
         return nullptr;
 
     return prog.GetActiveUniform(index);
 }
 
 void
 WebGLContext::GetAttachedShaders(const WebGLProgram& prog,
                                  dom::Nullable<nsTArray<RefPtr<WebGLShader>>>& retval)
 {
     retval.SetNull();
     if (IsContextLost())
         return;
 
-    if (!ValidateObjectRef("getAttachedShaders", prog))
+    if (!ValidateObject("getAttachedShaders", prog))
         return;
 
     prog.GetAttachedShaders(&retval.SetValue());
 }
 
 GLint
 WebGLContext::GetAttribLocation(const WebGLProgram& prog, const nsAString& name)
 {
     if (IsContextLost())
         return -1;
 
-    if (!ValidateObjectRef("getAttribLocation: program", prog))
+    if (!ValidateObject("getAttribLocation: program", prog))
         return -1;
 
     return prog.GetAttribLocation(name);
 }
 
 JS::Value
 WebGLContext::GetBufferParameter(GLenum target, GLenum pname)
 {
@@ -905,62 +881,62 @@ WebGLContext::GetError()
 }
 
 JS::Value
 WebGLContext::GetProgramParameter(const WebGLProgram& prog, GLenum pname)
 {
     if (IsContextLost())
         return JS::NullValue();
 
-    if (!ValidateObjectAllowDeleted("getProgramParameter: program", &prog))
+    if (!ValidateObjectAllowDeleted("getProgramParameter: program", prog))
         return JS::NullValue();
 
     return prog.GetProgramParameter(pname);
 }
 
 void
 WebGLContext::GetProgramInfoLog(const WebGLProgram& prog, nsAString& retval)
 {
     retval.SetIsVoid(true);
 
     if (IsContextLost())
         return;