Merge inbound to central, a=merge
authorWes Kocher <wkocher@mozilla.com>
Tue, 20 Jun 2017 17:58:46 -0700
changeset 365114 f31652d75fb5f377db8de3da30b0252600d3c8ca
parent 365113 464b2a3c25aa1065760d9ecbb0870bca4a66c62e (current diff)
parent 364963 cea239134ab9ce91c5f8ebae5bb4910aba678413 (diff)
child 365115 c55e582aee5f4dd7c28cd9820156ecd0335e4e79
push id91680
push userkwierso@gmail.com
push dateWed, 21 Jun 2017 01:32:01 +0000
treeherdermozilla-inbound@f7b9dc31956c [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone56.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge inbound to central, a=merge MozReview-Commit-ID: 1SvQU51m5qC
browser/themes/shared/urlbar-star.svg
toolkit/themes/shared/icons/find-arrows.svg
--- a/accessible/base/ARIAMap.cpp
+++ b/accessible/base/ARIAMap.cpp
@@ -136,17 +136,17 @@ static const nsRoleMapEntry sWAIRoleMaps
   { // combobox
     &nsGkAtoms::combobox,
     roles::COMBOBOX,
     kUseMapRole,
     eNoValue,
     eOpenCloseAction,
     eNoLiveAttr,
     kGenericAccType,
-    states::COLLAPSED | states::HASPOPUP | states::VERTICAL,
+    states::COLLAPSED | states::HASPOPUP,
     eARIAAutoComplete,
     eARIAReadonly,
     eARIAOrientation
   },
   { // complementary
     &nsGkAtoms::complementary,
     roles::NOTHING,
     kUseNativeRole,
@@ -783,40 +783,41 @@ static const nsRoleMapEntry sWAIRoleMaps
   { // menuitem
     &nsGkAtoms::menuitem,
     roles::MENUITEM,
     kUseMapRole,
     eNoValue,
     eClickAction,
     eNoLiveAttr,
     kGenericAccType,
-    kNoReqStates,
-    eARIACheckedMixed
+    kNoReqStates
   },
   { // menuitemcheckbox
     &nsGkAtoms::menuitemcheckbox,
     roles::CHECK_MENU_ITEM,
     kUseMapRole,
     eNoValue,
     eClickAction,
     eNoLiveAttr,
     kGenericAccType,
     kNoReqStates,
-    eARIACheckableMixed
+    eARIACheckableMixed,
+    eARIAReadonly
   },
   { // menuitemradio
     &nsGkAtoms::menuitemradio,
     roles::RADIO_MENU_ITEM,
     kUseMapRole,
     eNoValue,
     eClickAction,
     eNoLiveAttr,
     kGenericAccType,
     kNoReqStates,
-    eARIACheckableBool
+    eARIACheckableBool,
+    eARIAReadonly
   },
   { // navigation
     &nsGkAtoms::navigation,
     roles::NOTHING,
     kUseNativeRole,
     eNoValue,
     eNoAction,
     eNoLiveAttr,
@@ -891,17 +892,18 @@ static const nsRoleMapEntry sWAIRoleMaps
     &nsGkAtoms::radiogroup,
     roles::RADIO_GROUP,
     kUseMapRole,
     eNoValue,
     eNoAction,
     eNoLiveAttr,
     kGenericAccType,
     kNoReqStates,
-    eARIAOrientation
+    eARIAOrientation,
+    eARIAReadonly
   },
   { // region
     &nsGkAtoms::region,
     roles::PANE,
     kUseMapRole,
     eNoValue,
     eNoAction,
     eNoLiveAttr,
@@ -1024,17 +1026,18 @@ static const nsRoleMapEntry sWAIRoleMaps
     &nsGkAtoms::_switch,
     roles::SWITCH,
     kUseMapRole,
     eNoValue,
     eCheckUncheckAction,
     eNoLiveAttr,
     kGenericAccType,
     kNoReqStates,
-    eARIACheckableBool
+    eARIACheckableBool,
+    eARIAReadonly
   },
   { // tab
     &nsGkAtoms::tab,
     roles::PAGETAB,
     kUseMapRole,
     eNoValue,
     eSwitchAction,
     eNoLiveAttr,
@@ -1144,17 +1147,17 @@ static const nsRoleMapEntry sWAIRoleMaps
   { // treegrid
     &nsGkAtoms::treegrid,
     roles::TREE_TABLE,
     kUseMapRole,
     eNoValue,
     eNoAction,
     eNoLiveAttr,
     eSelect | eTable,
-    states::VERTICAL,
+    kNoReqStates,
     eARIAReadonlyOrEditable,
     eARIAMultiSelectable,
     eFocusableUntilDisabled,
     eARIAOrientation
   },
   { // treeitem
     &nsGkAtoms::treeitem,
     roles::OUTLINEITEM,
--- a/accessible/ipc/win/HandlerProvider.h
+++ b/accessible/ipc/win/HandlerProvider.h
@@ -59,17 +59,17 @@ private:
                                      mscom::ProxyUniquePtr<IHandlerControl> aCtrl);
   void GetAndSerializePayload(const MutexAutoLock&);
   void BuildIA2Data(IA2Data* aOutIA2Data);
   static void ClearIA2Data(IA2Data& aData);
   bool IsTargetInterfaceCacheable();
 
   Atomic<uint32_t>                  mRefCnt;
   Mutex                             mMutex; // Protects mSerializer
-  REFIID                            mTargetUnkIid;
+  const IID                         mTargetUnkIid;
   mscom::InterceptorTargetPtr<IUnknown> mTargetUnk; // Constant, main thread only
   UniquePtr<mscom::StructToStream>  mSerializer;
 };
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif // mozilla_a11y_HandlerProvider_h
--- a/accessible/tests/mochitest/attributes/test_obj.html
+++ b/accessible/tests/mochitest/attributes/test_obj.html
@@ -25,17 +25,19 @@ https://bugzilla.mozilla.org/show_bug.cg
       testAttrs("atomic", {"atomic" : "true", "container-atomic" : "true"}, true);
       testAttrs(getNode("atomic").firstChild, {"container-atomic" : "true"}, true);
       testAbsentAttrs("atomic_false", {"atomic" : "false", "container-atomic" : "false"});
       testAbsentAttrs(getNode("atomic_false").firstChild, {"container-atomic" : "false"});
 
       testAttrs("autocomplete", {"autocomplete" : "true"}, true);
       testAttrs("checkbox", {"checkable" : "true"}, true);
       testAttrs("checkedCheckbox", {"checkable" : "true"}, true);
-      testAttrs("checkedMenuitem", {"checkable" : "true"}, true);
+      testAbsentAttrs("checkedMenuitem", {"checkable" : "true"}, true);
+      testAttrs("checkedMenuitemCheckbox", {"checkable" : "true"}, true);
+      testAttrs("checkedMenuitemRadio", {"checkable" : "true"}, true);
       testAttrs("checkedOption", {"checkable" : "true"}, true);
       testAttrs("checkedRadio", {"checkable" : "true"}, true);
       testAttrs("checkedTreeitem", {"checkable" : "true"}, true);
       testAttrs("dropeffect", {"dropeffect" : "copy"}, true);
       testAttrs("grabbed", {"grabbed" : "true"}, true);
       testAbsentAttrs("haspopup", { "haspopup": "false" });
       testAttrs("hidden", {"hidden" : "true"}, true);
       testAbsentAttrs("hidden_false", { "hidden": "false" });
@@ -200,16 +202,18 @@ https://bugzilla.mozilla.org/show_bug.cg
 
   <!-- aria -->
   <div id="atomic" aria-atomic="true">live region</div>
   <div id="atomic_false" aria-atomic="false">live region</div>
   <div id="autocomplete" role="textbox" aria-autocomplete="true"></div>
   <div id="checkbox" role="checkbox"></div>
   <div id="checkedCheckbox" role="checkbox" aria-checked="true"></div>
   <div id="checkedMenuitem" role="menuitem" aria-checked="true"></div>
+  <div id="checkedMenuitemCheckbox" role="menuitemcheckbox" aria-checked="true"></div>
+  <div id="checkedMenuitemRadio" role="menuitemradio" aria-checked="true"></div>
   <div id="checkedOption" role="option" aria-checked="true"></div>
   <div id="checkedRadio" role="radio" aria-checked="true"></div>
   <div id="checkedTreeitem" role="treeitem" aria-checked="true"></div>
   <div id="dropeffect" aria-dropeffect="copy"></div>
   <div id="grabbed" aria-grabbed="true"></div>
   <div id="haspopup" aria-haspopup="true"></div>
   <div id="hidden" aria-hidden="true"></div>
   <div id="hidden_false" aria-hidden="false"></div>
--- a/accessible/tests/mochitest/states/test_aria.html
+++ b/accessible/tests/mochitest/states/test_aria.html
@@ -218,17 +218,17 @@
 
       // some weak landmarks
       testStates("aria_main_link", STATE_LINKED);
       testStates("aria_navigation_link", STATE_LINKED);
       testStates("aria_main_anchor", STATE_SELECTABLE);
       testStates("aria_navigation_anchor", STATE_SELECTABLE);
 
       // aria-orientation
-      testStates("aria_combobox", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+      testStates("aria_combobox", 0, 0, 0, EXT_STATE_HORIZONTAL | EXT_STATE_VERTICAL);
       testStates("aria_hcombobox", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
       testStates("aria_vcombobox", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
       testStates("aria_listbox", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
       testStates("aria_hlistbox", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
       testStates("aria_vlistbox", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
       testStates("aria_menu", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
       testStates("aria_hmenu", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
       testStates("aria_vmenu", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
@@ -251,17 +251,17 @@
       testStates("aria_htablist", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
       testStates("aria_vtablist", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
       testStates("aria_toolbar", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
       testStates("aria_htoolbar", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
       testStates("aria_vtoolbar", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
       testStates("aria_tree", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
       testStates("aria_htree", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
       testStates("aria_vtree", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
-      testStates("aria_treegrid", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
+      testStates("aria_treegrid", 0, 0, 0, EXT_STATE_HORIZONTAL | EXT_STATE_VERTICAL);
       testStates("aria_htreegrid", 0, EXT_STATE_HORIZONTAL, 0, EXT_STATE_VERTICAL);
       testStates("aria_vtreegrid", 0, EXT_STATE_VERTICAL, 0, EXT_STATE_HORIZONTAL);
 
       // indeterminate ARIA progressbars (no aria-valuenow or aria-valuetext attribute)
       // should expose mixed state
       testStates("aria_progressbar", STATE_MIXED);
       testStates("aria_progressbar_valuenow", 0, 0, STATE_MIXED);
       testStates("aria_progressbar_valuetext", 0, 0, STATE_MIXED);
--- a/accessible/tests/mochitest/test_aria_token_attrs.html
+++ b/accessible/tests/mochitest/test_aria_token_attrs.html
@@ -91,37 +91,72 @@ https://bugzilla.mozilla.org/show_bug.cg
 
       // test (option) checkable and checked states
       testStates("option_checked_true", (STATE_CHECKABLE | STATE_CHECKED));
       testStates("option_checked_false", STATE_CHECKABLE, 0, STATE_CHECKED);
       testStates("option_checked_empty", 0 , 0, STATE_CHECKABLE | STATE_CHECKED);
       testStates("option_checked_undefined", 0, 0, STATE_CHECKABLE | STATE_CHECKED);
       testStates("option_checked_absent", 0, 0, STATE_CHECKABLE | STATE_CHECKED);
 
-      // test (menuitem) checkable and checked states
-      testStates("menuitem_checked_true", (STATE_CHECKABLE | STATE_CHECKED));
-      testStates("menuitem_checked_false", STATE_CHECKABLE, 0, STATE_CHECKED);
+      // test (menuitem) checkable and checked states, which are unsupported on this role
+      testStates("menuitem_checked_true", 0, 0, (STATE_CHECKABLE | STATE_CHECKED));
+      testStates("menuitem_checked_false", 0, 0, (STATE_CHECKABLE | STATE_CHECKED));
       testStates("menuitem_checked_empty", 0, 0, (STATE_CHECKABLE | STATE_CHECKED));
       testStates("menuitem_checked_undefined", 0, 0, (STATE_CHECKABLE | STATE_CHECKED));
       testStates("menuitem_checked_absent", 0, 0, (STATE_CHECKABLE | STATE_CHECKED));
 
+      // test (menuitemcheckbox) checkable and checked states
+      testStates("menuitemcheckbox_checked_true", (STATE_CHECKABLE | STATE_CHECKED));
+      testStates("menuitemcheckbox_checked_false", STATE_CHECKABLE, 0, STATE_CHECKED);
+      testStates("menuitemcheckbox_checked_empty", STATE_CHECKABLE, 0, STATE_CHECKED);
+      testStates("menuitemcheckbox_checked_undefined", STATE_CHECKABLE, 0, STATE_CHECKED);
+      testStates("menuitemcheckbox_checked_absent", STATE_CHECKABLE, 0, STATE_CHECKED);
+
+      // test (menuitemcheckbox) readonly states
+      testStates("menuitemcheckbox_readonly_true", STATE_READONLY);
+      testStates("menuitemcheckbox_readonly_false", 0, 0, STATE_READONLY);
+      testStates("menuitemcheckbox_readonly_empty", 0, 0, STATE_READONLY);
+      testStates("menuitemcheckbox_readonly_undefined", 0, 0, STATE_READONLY);
+      testStates("menuitemcheckbox_readonly_absent", 0, 0, STATE_READONLY);
+
       // test (menuitemradio) checkable and checked states
       testStates("menuitemradio_checked_true", (STATE_CHECKABLE | STATE_CHECKED));
       testStates("menuitemradio_checked_false", STATE_CHECKABLE, 0, STATE_CHECKED);
       testStates("menuitemradio_checked_empty", STATE_CHECKABLE, 0, STATE_CHECKED);
       testStates("menuitemradio_checked_undefined", STATE_CHECKABLE, 0, STATE_CHECKED);
       testStates("menuitemradio_checked_absent", STATE_CHECKABLE, 0, STATE_CHECKED);
 
+      // test (menuitemradio) readonly states
+      testStates("menuitemradio_readonly_true", STATE_READONLY);
+      testStates("menuitemradio_readonly_false", 0, 0, STATE_READONLY);
+      testStates("menuitemradio_readonly_empty", 0, 0, STATE_READONLY);
+      testStates("menuitemradio_readonly_undefined", 0, 0, STATE_READONLY);
+      testStates("menuitemradio_readonly_absent", 0, 0, STATE_READONLY);
+
       // test (radio) checkable and checked states
       testStates("radio_checked_true", (STATE_CHECKABLE | STATE_CHECKED));
       testStates("radio_checked_false", STATE_CHECKABLE, 0, STATE_CHECKED);
       testStates("radio_checked_empty", STATE_CHECKABLE, 0, STATE_CHECKED);
       testStates("radio_checked_undefined", STATE_CHECKABLE, 0, STATE_CHECKED);
       testStates("radio_checked_absent", STATE_CHECKABLE, 0, STATE_CHECKED);
 
+      // test (radiogroup) readonly states
+      testStates("radiogroup_readonly_true", STATE_READONLY);
+      testStates("radiogroup_readonly_false", 0, 0, STATE_READONLY);
+      testStates("radiogroup_readonly_empty", 0, 0, STATE_READONLY);
+      testStates("radiogroup_readonly_undefined", 0, 0, STATE_READONLY);
+      testStates("radiogroup_readonly_absent", 0, 0, STATE_READONLY);
+
+      // test (switch) readonly states
+      testStates("switch_readonly_true", STATE_READONLY);
+      testStates("switch_readonly_false", 0, 0, STATE_READONLY);
+      testStates("switch_readonly_empty", 0, 0, STATE_READONLY);
+      testStates("switch_readonly_undefined", 0, 0, STATE_READONLY);
+      testStates("switch_readonly_absent", 0, 0, STATE_READONLY);
+
       // test (textbox) multiline states
       testStates("textbox_multiline_true", 0, EXT_STATE_MULTI_LINE);
       testStates("textbox_multiline_false", 0, EXT_STATE_SINGLE_LINE);
       testStates("textbox_multiline_empty", 0, EXT_STATE_SINGLE_LINE);
       testStates("textbox_multiline_undefined", 0, EXT_STATE_SINGLE_LINE);
       testStates("textbox_multiline_absent", 0, EXT_STATE_SINGLE_LINE);
 
       // test (textbox) readonly states
@@ -255,35 +290,80 @@ https://bugzilla.mozilla.org/show_bug.cg
   <div id="listbox_multiselectable_undefined" role="listbox" aria-multiselectable="undefined">
     <div id="option_checked_undefined" role="option" aria-checked="undefined">item</div>
   </div>
   <div id="listbox_multiselectable_absent" role="listbox">
     <div id="option_checked_absent" role="option">item</div>
   </div>
 
   <div role="menu">
-    <div id="menuitem_checked_true" role="menuitem" aria-checked="true">This menuitem has aria-checked="true" and should get STATE_CHECKABLE. It should also get STATE_checked.</div>
-    <div id="menuitem_checked_false" role="menuitem" aria-checked="false">This menuitem has aria-checked="false" and should get STATE_CHECKABLE.</div>
-    <div id="menuitem_checked_empty" role="menuitem" aria-checked="">This menuitem has aria-checked="" and should <emph>not</emph> get STATE_CHECKABLE.</div>
-    <div id="menuitem_checked_undefined" role="menuitem" aria-checked="undefined">This menuitem has aria-checked="undefined" and should <emph>not</emph> get STATE_CHECKABLE.</div>
-    <div id="menuitem_checked_absent" role="menuitem">This menuitem has <emph>no</emph> aria-checked attribute and should <emph>not</emph> get STATE_CHECKABLE.</div>
+    <div id="menuitem_checked_true" role="menuitem" aria-checked="true">Generic menuitems don't support aria-checked.</div>
+    <div id="menuitem_checked_false" role="menuitem" aria-checked="false">Generic menuitems don't support aria-checked.</div>
+    <div id="menuitem_checked_empty" role="menuitem" aria-checked="">Generic menuitems don't support aria-checked.</div>
+    <div id="menuitem_checked_undefined" role="menuitem" aria-checked="undefined">Generic menuitems don't support aria-checked.</div>
+    <div id="menuitem_checked_absent" role="menuitem">Generic menuitems don't support aria-checked.</div>
+
+    <div id="menuitemcheckbox_checked_true" role="menuitemcheckbox" aria-checked="true">This menuitemcheckbox has aria-checked="true" and should get STATE_CHECKABLE. It should also get STATE_checked.</div>
+    <div id="menuitemcheckbox_checked_false" role="menuitemcheckbox" aria-checked="false">This menuitemcheckbox has aria-checked="false" and should get STATE_CHECKABLE.</div>
+    <div id="menuitemcheckbox_checked_empty" role="menuitemcheckbox" aria-checked="">This menuitemcheckbox has aria-checked="" and should <emph>not</emph> get STATE_CHECKABLE.</div>
+    <div id="menuitemcheckbox_checked_undefined" role="menuitemcheckbox" aria-checked="undefined">This menuitemcheckbox has aria-checked="undefined" and should <emph>not</emph> get STATE_CHECKABLE.</div>
+    <div id="menuitemcheckbox_checked_absent" role="menuitemcheckbox">This menuitemcheckbox has <emph>no</emph> aria-checked attribute and should <emph>not</emph> get STATE_CHECKABLE.</div>
+
+    <div id="menuitemcheckbox_readonly_true" role="menuitemcheckbox" aria-readonly="true">This menuitemcheckbox has aria-readonly="true" and should get STATE_READONLY.</div>
+    <div id="menuitemcheckbox_readonly_false" role="menuitemcheckbox" aria-readonly="false">This menuitemcheckbox has aria-readonly="false" and should <emph>not</emph> get STATE_READONLY.</div>
+    <div id="menuitemcheckbox_readonly_empty" role="menuitemcheckbox" aria-readonly="">This menuitemcheckbox has aria-readonly="" and should <emph>not</emph> get STATE_READONLY.</div>
+    <div id="menuitemcheckbox_readonly_undefined" role="menuitemcheckbox" aria-readonly="undefined">This menuitemcheckbox has aria-readonly="undefined" and should <emph>not</emph> get STATE_READONLY.</div>
+    <div id="menuitemcheckbox_readonly_absent" role="menuitemcheckbox">This menuitemcheckbox has <emph>no</emph> aria-readonly attribute and should <emph>not</emph> get STATE_READONLY.</div>
 
     <div id="menuitemradio_checked_true" role="menuitemradio" aria-checked="true">This menuitem has aria-checked="true" and should get STATE_CHECKABLE. It should also get STATE_checked.</div>
     <div id="menuitemradio_checked_false" role="menuitemradio" aria-checked="false">This menuitem has aria-checked="false" and should get STATE_CHECKABLE.</div>
     <div id="menuitemradio_checked_empty" role="menuitemradio" aria-checked="">This menuitem has aria-checked="" and should <emph>not</emph> get STATE_CHECKABLE.</div>
     <div id="menuitemradio_checked_undefined" role="menuitemradio" aria-checked="undefined">This menuitem has aria-checked="undefined" and should <emph>not</emph> get STATE_CHECKABLE.</div>
     <div id="menuitemradio_checked_absent" role="menuitemradio">This menuitem has <emph>no</emph> aria-checked attribute but should get STATE_CHECKABLE.</div>
   </div>
 
+    <div id="menuitemradio_readonly_true" role="menuitemradio" aria-readonly="true">This menuitemradio has aria-readonly="true" and should get STATE_READONLY.</div>
+    <div id="menuitemradio_readonly_false" role="menuitemradio" aria-readonly="false">This menuitemradio has aria-readonly="false" and should <emph>not</emph> get STATE_READONLY.</div>
+    <div id="menuitemradio_readonly_empty" role="menuitemradio" aria-readonly="">This menuitemradio has aria-readonly="" and should <emph>not</emph> get STATE_READONLY.</div>
+    <div id="menuitemradio_readonly_undefined" role="menuitemradio" aria-readonly="undefined">This menuitemradio has aria-readonly="undefined" and should <emph>not</emph> get STATE_READONLY.</div>
+    <div id="menuitemradio_readonly_absent" role="menuitemradio">This menuitemradio has <emph>no</emph> aria-readonly attribute and should <emph>not</emph> get STATE_READONLY.</div>
+
   <div id="radio_checked_true" role="radio" aria-checked="true">This menuitem has aria-checked="true" and should get STATE_CHECKABLE. It should also get STATE_CHECKED.</div>
   <div id="radio_checked_false" role="radio" aria-checked="false">This menuitem has aria-checked="false" and should get STATE_CHECKABLE.</div>
   <div id="radio_checked_empty" role="radio" aria-checked="">This menuitem has aria-checked="" and should <emph>not</emph> get STATE_CHECKABLE.</div>
   <div id="radio_checked_undefined" role="radio" aria-checked="undefined">This menuitem has aria-checked="undefined" and should <emph>not</emph> get STATE_CHECKABLE.</div>
   <div id="radio_checked_absent" role="radio">This menuitem has <emph>no</emph> aria-checked attribute but should get STATE_CHECKABLE.</div>
 
+  <div id="radiogroup_readonly_true" role="radiogroup" aria-readonly="true">
+    <div role="radio">yes</div>
+    <div role="radio">no</div>
+  </div>
+  <div id="radiogroup_readonly_false" role="radiogroup" aria-readonly="false">
+    <div role="radio">yes</div>
+    <div role="radio">no</div>
+  </div>
+  <div id="radiogroup_readonly_empty" role="radiogroup" aria-readonly="">
+    <div role="radio">yes</div>
+    <div role="radio">no</div>
+  </div>
+  <div id="radiogroup_readonly_undefined" role="radiogroup" aria-readonly="undefined">
+    <div role="radio">yes</div>
+    <div role="radio">no</div>
+  </div>
+  <div id="radiogroup_readonly_absent" role="radiogroup">
+    <div role="radio">yes</div>
+    <div role="radio">no</div>
+  </div>
+
+  <div id="switch_readonly_true" role="switch" aria-readonly="true">This switch has aria-readonly="true" and should get STATE_READONLY.</div>
+  <div id="switch_readonly_false" role="switch" aria-readonly="false">This switch has aria-readonly="false" and should <emph>not</emph> get STATE_READONLY.</div>
+  <div id="switch_readonly_empty" role="switch" aria-readonly="">This switch has aria-readonly="" and should <emph>not</emph> get STATE_READONLY.</div>
+  <div id="switch_readonly_undefined" role="switch" aria-readonly="undefined">This switch has aria-readonly="undefined" and should <emph>not</emph> get STATE_READONLY.</div>
+  <div id="switch_readonly_absent" role="switch">This switch has <emph>no</emph> aria-readonly attribute and should <emph>not</emph> get STATE_READONLY.</div>
+
   <div id="textbox_readonly_true" role="textbox" aria-readonly="true"></div>
   <div id="textbox_readonly_false" role="textbox" aria-readonly="false"></div>
   <div id="textbox_readonly_empty" role="textbox" aria-readonly=""></div>
   <div id="textbox_readonly_undefined" role="textbox" aria-readonly="undefined"></div>
   <div id="textbox_readonly_absent" role="textbox"></div>
 
   <div id="textbox_multiline_true" role="textbox" aria-multiline="true"></div>
   <div id="textbox_multiline_false" role="textbox" aria-multiline="false"></div>
new file mode 100644
--- /dev/null
+++ b/accessible/windows/msaa/LazyInstantiator.cpp
@@ -0,0 +1,832 @@
+/* -*- 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 "LazyInstantiator.h"
+
+#include "MainThreadUtils.h"
+#include "mozilla/a11y/Accessible.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/mscom/MainThreadRuntime.h"
+#include "mozilla/mscom/Ptr.h"
+#include "mozilla/mscom/Registration.h"
+#include "mozilla/UniquePtr.h"
+#include "nsAccessibilityService.h"
+#include "nsWindowsHelpers.h"
+#include "nsCOMPtr.h"
+#include "nsIFile.h"
+#include "nsXPCOM.h"
+#include "RootAccessibleWrap.h"
+#include "WinUtils.h"
+
+#if defined(MOZ_TELEMETRY_REPORTING)
+#include "mozilla/Telemetry.h"
+#endif // defined(MOZ_TELEMETRY_REPORTING)
+
+#include <oaidl.h>
+
+#if !defined(STATE_SYSTEM_NORMAL)
+#define STATE_SYSTEM_NORMAL (0)
+#endif // !defined(STATE_SYSTEM_NORMAL)
+
+/**
+ * Because our wrapped accessible is cycle-collected, we can't safely AddRef()
+ * or Release() ourselves off the main thread. This template specialization
+ * forces NewRunnableMethod() to use STAUniquePtr instead of RefPtr for managing
+ * a runnable's lifetime. Once the runnable has completed, the STAUniquePtr will
+ * post a runnable to the main thread to release ourselves from there.
+ */
+template<>
+struct nsRunnableMethodReceiver<mozilla::a11y::LazyInstantiator, true>
+{
+  mozilla::mscom::STAUniquePtr<mozilla::a11y::LazyInstantiator> mObj;
+  explicit nsRunnableMethodReceiver(mozilla::a11y::LazyInstantiator* aObj)
+    : mObj(aObj)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    // STAUniquePtr does not implicitly AddRef(), so we must explicitly do so
+    // here.
+    aObj->AddRef();
+  }
+  ~nsRunnableMethodReceiver() { Revoke(); }
+  mozilla::a11y::LazyInstantiator* Get() const { return mObj.get(); }
+  void Revoke() { mObj = nullptr; }
+};
+
+namespace mozilla {
+namespace a11y {
+
+static const wchar_t kLazyInstantiatorProp[] = L"mozilla::a11y::LazyInstantiator";
+
+/* static */
+already_AddRefed<IAccessible>
+LazyInstantiator::GetRootAccessible(HWND aHwnd)
+{
+  // There must only be one LazyInstantiator per HWND.
+  // To track this, we set the kLazyInstantiatorProp on the HWND with a pointer
+  // to an existing instance. We only create a new LazyInstatiator if that prop
+  // has not already been set.
+  LazyInstantiator* existingInstantiator =
+    reinterpret_cast<LazyInstantiator*>(::GetProp(aHwnd, kLazyInstantiatorProp));
+
+  RefPtr<IAccessible> result;
+  if (existingInstantiator) {
+    // Temporarily disable blind aggregation until we know that we have been
+    // marshaled. See EnableBlindAggregation for more information.
+    existingInstantiator->mAllowBlindAggregation = false;
+    result = existingInstantiator;
+    return result.forget();
+  }
+
+  // At this time we only want to check whether the acc service is running; We
+  // don't actually want to create the acc service yet.
+  if (!GetAccService()) {
+    // a11y is not running yet, there are no existing LazyInstantiators for this
+    // HWND, so create a new one and return it as a surrogate for the root
+    // accessible.
+    result = new LazyInstantiator(aHwnd);
+    return result.forget();
+  }
+
+  // a11y is running, so we just resolve the real root accessible.
+  a11y::Accessible* rootAcc = widget::WinUtils::GetRootAccessibleForHWND(aHwnd);
+  if (!rootAcc || !rootAcc->IsRoot()) {
+    return nullptr;
+  }
+
+  // Subtle: rootAcc might still be wrapped by a LazyInstantiator, but we
+  // don't need LazyInstantiator's capabilities anymore (since a11y is already
+  // running). We can bypass LazyInstantiator by retrieving the internal
+  // unknown (which is not wrapped by the LazyInstantiator) and then querying
+  // that for IID_IAccessible.
+  a11y::RootAccessibleWrap* rootWrap =
+    static_cast<a11y::RootAccessibleWrap*>(rootAcc);
+  RefPtr<IUnknown> punk(rootWrap->GetInternalUnknown());
+
+  MOZ_ASSERT(punk);
+  if (!punk) {
+    return nullptr;
+  }
+
+  punk->QueryInterface(IID_IAccessible, getter_AddRefs(result));
+  return result.forget();
+}
+
+/**
+ * When marshaling an interface, COM makes a whole bunch of QueryInterface
+ * calls to determine what kind of marshaling the interface supports. We need
+ * to handle those queries without instantiating a11y, so we temporarily
+ * disable passing through of QueryInterface calls to a11y. Once we know that
+ * COM is finished marshaling, we call EnableBlindAggregation to re-enable
+ * QueryInterface passthrough.
+ */
+/* static */
+void
+LazyInstantiator::EnableBlindAggregation(HWND aHwnd)
+{
+  LazyInstantiator* existingInstantiator =
+    reinterpret_cast<LazyInstantiator*>(::GetProp(aHwnd, kLazyInstantiatorProp));
+
+  if (!existingInstantiator) {
+    return;
+  }
+
+  existingInstantiator->mAllowBlindAggregation = true;
+}
+
+LazyInstantiator::LazyInstantiator(HWND aHwnd)
+  : mHwnd(aHwnd)
+  , mAllowBlindAggregation(false)
+  , mWeakRootAccWrap(nullptr)
+  , mWeakAccessible(nullptr)
+  , mWeakDispatch(nullptr)
+{
+  MOZ_ASSERT(aHwnd);
+  // Assign ourselves as the designated LazyInstantiator for aHwnd
+  DebugOnly<BOOL> setPropOk = ::SetProp(aHwnd, kLazyInstantiatorProp,
+                                        reinterpret_cast<HANDLE>(this));
+  MOZ_ASSERT(setPropOk);
+}
+
+LazyInstantiator::~LazyInstantiator()
+{
+  if (mRealRootUnk) {
+    // Disconnect ourselves from the root accessible.
+    RefPtr<IUnknown> dummy(mWeakRootAccWrap->Aggregate(nullptr));
+  }
+
+  ClearProp();
+}
+
+void
+LazyInstantiator::ClearProp()
+{
+  // Remove ourselves as the designated LazyInstantiator for mHwnd
+  DebugOnly<HANDLE> removedProp = ::RemoveProp(mHwnd, kLazyInstantiatorProp);
+  MOZ_ASSERT(!removedProp ||
+             reinterpret_cast<LazyInstantiator*>(removedProp.value) == this);
+}
+
+/**
+ * Given the remote client's thread ID, resolve its execuatable image name.
+ */
+bool
+LazyInstantiator::GetClientExecutableName(const DWORD aClientTid,
+                                          nsIFile** aOutClientExe)
+{
+  nsAutoHandle callingThread(::OpenThread(THREAD_QUERY_LIMITED_INFORMATION,
+                                          FALSE, aClientTid));
+  if (!callingThread) {
+    return false;
+  }
+
+  DWORD callingPid = ::GetProcessIdOfThread(callingThread);
+
+  nsAutoHandle callingProcess(::OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION,
+                                            FALSE, callingPid));
+  if (!callingProcess) {
+    return false;
+  }
+
+  DWORD bufLen = MAX_PATH;
+  UniquePtr<wchar_t[]> buf;
+
+  while (true) {
+    buf = MakeUnique<wchar_t[]>(bufLen);
+    if (::QueryFullProcessImageName(callingProcess, 0, buf.get(), &bufLen)) {
+      break;
+    }
+
+    DWORD lastError = ::GetLastError();
+    MOZ_ASSERT(lastError == ERROR_INSUFFICIENT_BUFFER);
+    if (lastError != ERROR_INSUFFICIENT_BUFFER) {
+      return false;
+    }
+
+    bufLen *= 2;
+  }
+
+  nsCOMPtr<nsIFile> file;
+  nsresult rv = NS_NewLocalFile(nsDependentString(buf.get(), bufLen), false,
+                                getter_AddRefs(file));
+  if (NS_FAILED(rv)) {
+    return false;
+  }
+
+  file.forget(aOutClientExe);
+  return NS_SUCCEEDED(rv);
+}
+
+/**
+ * Given a remote client's thread ID, determine whether we should proceed with
+ * a11y instantiation. This is where telemetry should be gathered and any
+ * potential blocking of unwanted a11y clients should occur.
+ *
+ * @return true if we should instantiate a11y
+ */
+bool
+LazyInstantiator::ShouldInstantiate(const DWORD aClientTid)
+{
+  if (!aClientTid) {
+    // aClientTid == 0 implies that this is either an in-process call, or else
+    // we failed to retrieve information about the remote caller.
+    // We should always default to instantiating a11y in this case.
+    return true;
+  }
+
+  nsCOMPtr<nsIFile> clientExe;
+  GetClientExecutableName(aClientTid, getter_AddRefs(clientExe));
+
+  // Blocklist checks should go here. return false if we should not instantiate.
+  /*
+  if (ClientShouldBeBlocked(clientExe)) {
+    return false;
+  }
+  */
+
+#if defined(MOZ_TELEMETRY_REPORTING)
+  if (!mTelemetryThread) {
+    // Call GatherTelemetry on a background thread because it does I/O on
+    // the executable file to retrieve version information.
+    nsCOMPtr<nsIRunnable> runnable(
+        NewRunnableMethod<nsCOMPtr<nsIFile>>(this,
+                                             &LazyInstantiator::GatherTelemetry,
+                                             clientExe));
+    NS_NewThread(getter_AddRefs(mTelemetryThread), runnable);
+  }
+#endif // defined(MOZ_TELEMETRY_REPORTING)
+  return true;
+}
+
+#if defined(MOZ_TELEMETRY_REPORTING)
+/**
+ * Appends version information in the format "|a.b.c.d".
+ * If there is no version information, we append nothing.
+ */
+void
+LazyInstantiator::AppendVersionInfo(nsIFile* aClientExe,
+                                    nsAString& aStrToAppend)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  nsAutoString fullPath;
+  nsresult rv = aClientExe->GetPath(fullPath);
+  if (NS_FAILED(rv)) {
+    return;
+  }
+
+  DWORD verInfoSize = ::GetFileVersionInfoSize(fullPath.get(), nullptr);
+  if (!verInfoSize) {
+    return;
+  }
+
+  auto verInfoBuf = MakeUnique<BYTE[]>(verInfoSize);
+
+  if (!::GetFileVersionInfo(fullPath.get(), 0, verInfoSize, verInfoBuf.get())) {
+    return;
+  }
+
+  VS_FIXEDFILEINFO* fixedInfo = nullptr;
+  UINT fixedInfoLen = 0;
+
+  if (!::VerQueryValue(verInfoBuf.get(), L"\\", (LPVOID*) &fixedInfo,
+                       &fixedInfoLen)) {
+    return;
+  }
+
+  uint32_t major = HIWORD(fixedInfo->dwFileVersionMS);
+  uint32_t minor = LOWORD(fixedInfo->dwFileVersionMS);
+  uint32_t patch = HIWORD(fixedInfo->dwFileVersionLS);
+  uint32_t build = LOWORD(fixedInfo->dwFileVersionLS);
+
+  aStrToAppend.AppendLiteral(u"|");
+
+  NS_NAMED_LITERAL_STRING(dot, ".");
+
+  aStrToAppend.AppendInt(major);
+  aStrToAppend.Append(dot);
+  aStrToAppend.AppendInt(minor);
+  aStrToAppend.Append(dot);
+  aStrToAppend.AppendInt(patch);
+  aStrToAppend.Append(dot);
+  aStrToAppend.AppendInt(build);
+}
+
+void
+LazyInstantiator::GatherTelemetry(nsIFile* aClientExe)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  nsAutoString value;
+  nsresult rv = aClientExe->GetLeafName(value);
+  if (NS_SUCCEEDED(rv)) {
+    AppendVersionInfo(aClientExe, value);
+  }
+
+  // Now that we've (possibly) obtained version info, send the resulting
+  // string back to the main thread to accumulate in telemetry.
+  NS_DispatchToMainThread(NewNonOwningRunnableMethod<nsString>(this,
+        &LazyInstantiator::AccumulateTelemetry, value));
+}
+
+void
+LazyInstantiator::AccumulateTelemetry(const nsString& aValue)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  if (!aValue.IsEmpty()) {
+    Telemetry::ScalarSet(Telemetry::ScalarID::A11Y_INSTANTIATORS, aValue);
+  }
+
+  mTelemetryThread->Shutdown();
+  mTelemetryThread = nullptr;
+}
+#endif // defined(MOZ_TELEMETRY_REPORTING)
+
+RootAccessibleWrap*
+LazyInstantiator::ResolveRootAccWrap()
+{
+  Accessible* acc = widget::WinUtils::GetRootAccessibleForHWND(mHwnd);
+  if (!acc || !acc->IsRoot()) {
+    return nullptr;
+  }
+
+  return static_cast<RootAccessibleWrap*>(acc);
+}
+
+/**
+ * With COM aggregation, the aggregated inner object usually delegates its
+ * reference counting to the outer object. In other words, we would expect
+ * mRealRootUnk to delegate its AddRef() and Release() to this LazyInstantiator.
+ *
+ * This scheme will not work in our case because the RootAccessibleWrap is
+ * cycle-collected!
+ *
+ * Instead, once a LazyInstantiator aggregates a RootAccessibleWrap, we transfer
+ * our strong references into mRealRootUnk. Any future calls to AddRef or
+ * Release now operate on mRealRootUnk instead of our intrinsic reference
+ * count. This is a bit strange, but it is the only way for these objects to
+ * share their reference count in a way that is safe for cycle collection.
+ *
+ * How do we know when it is safe to destroy ourselves? In
+ * LazyInstantiator::Release, we examine the result of mRealRootUnk->Release().
+ * If mRealRootUnk's resulting refcount is 1, then we know that the only
+ * remaining reference to mRealRootUnk is the mRealRootUnk reference itself (and
+ * thus nobody else holds references to either this or mRealRootUnk). Therefore
+ * we may now delete ourselves.
+ */
+void
+LazyInstantiator::TransplantRefCnt()
+{
+  MOZ_ASSERT(mRefCnt > 0);
+  MOZ_ASSERT(mRealRootUnk);
+
+  while (mRefCnt > 0) {
+    mRealRootUnk.get()->AddRef();
+    --mRefCnt;
+  }
+}
+
+HRESULT
+LazyInstantiator::MaybeResolveRoot()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (mWeakAccessible) {
+    return S_OK;
+  }
+
+  if (GetAccService() ||
+      ShouldInstantiate(mscom::MainThreadRuntime::GetClientThreadId())) {
+    mWeakRootAccWrap = ResolveRootAccWrap();
+    if (!mWeakRootAccWrap) {
+      return E_POINTER;
+    }
+
+    // Wrap ourselves around the root accessible wrap
+    mRealRootUnk = mWeakRootAccWrap->Aggregate(static_cast<IAccessible*>(this));
+    if (!mRealRootUnk) {
+      return E_FAIL;
+    }
+
+    // Move our strong references into the root accessible (see the comments
+    // above TransplantRefCnt for explanation).
+    TransplantRefCnt();
+
+    // Now obtain mWeakAccessible which we use to forward our incoming calls
+    // to the real accesssible.
+    HRESULT hr = mRealRootUnk->QueryInterface(IID_IAccessible,
+                                              (void**) &mWeakAccessible);
+    if (FAILED(hr)) {
+      return hr;
+    }
+
+    // mWeakAccessible is weak, so don't hold a strong ref
+    mWeakAccessible->Release();
+
+    // Now that a11y is running, we don't need to remain registered with our
+    // HWND anymore.
+    ClearProp();
+
+    return S_OK;
+  }
+
+  // If we don't want a real root, let's resolve a fake one.
+
+  const WPARAM flags = 0xFFFFFFFFUL;
+  // Synthesize a WM_GETOBJECT request to obtain a system-implemented
+  // IAccessible object from DefWindowProc
+  LRESULT lresult = ::DefWindowProc(mHwnd, WM_GETOBJECT, flags,
+                                    static_cast<LPARAM>(OBJID_CLIENT));
+
+  HRESULT hr = ObjectFromLresult(lresult, IID_IAccessible, flags,
+                                 getter_AddRefs(mRealRootUnk));
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  if (!mRealRootUnk) {
+    return E_NOTIMPL;
+  }
+
+  hr = mRealRootUnk->QueryInterface(IID_IAccessible,
+                                    (void**) &mWeakAccessible);
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  // mWeakAccessible is weak, so don't hold a strong ref
+  mWeakAccessible->Release();
+
+  return S_OK;
+}
+
+#define RESOLVE_ROOT \
+  { \
+    HRESULT hr = MaybeResolveRoot(); \
+    if (FAILED(hr)) { \
+      return hr; \
+    } \
+  }
+
+IMPL_IUNKNOWN_QUERY_HEAD(LazyInstantiator)
+IMPL_IUNKNOWN_QUERY_IFACE_AMBIGIOUS(IUnknown, IAccessible)
+IMPL_IUNKNOWN_QUERY_IFACE(IAccessible)
+IMPL_IUNKNOWN_QUERY_IFACE(IDispatch)
+IMPL_IUNKNOWN_QUERY_IFACE(IServiceProvider)
+  // See EnableBlindAggregation for comments.
+  if (!mAllowBlindAggregation) {
+    return E_NOINTERFACE;
+  }
+// If the client queries for an interface that LazyInstantiator does not
+// intrinsically support, then we must resolve the root accessible and pass
+// on the QueryInterface call to mRealRootUnk.
+RESOLVE_ROOT
+IMPL_IUNKNOWN_QUERY_TAIL_AGGREGATED(mRealRootUnk)
+
+ULONG
+LazyInstantiator::AddRef()
+{
+  // Always delegate refcounting to mRealRootUnk when it exists
+  if (mRealRootUnk) {
+    return mRealRootUnk.get()->AddRef();
+  }
+
+  return ++mRefCnt;
+}
+
+ULONG
+LazyInstantiator::Release()
+{
+  ULONG result;
+
+  // Always delegate refcounting to mRealRootUnk when it exists
+  if (mRealRootUnk) {
+    result = mRealRootUnk.get()->Release();
+    if (result == 1) {
+      // mRealRootUnk is the only strong reference left, so nothing else holds
+      // a strong reference to us. Drop result to zero so that we destroy
+      // ourselves (See the comments above LazyInstantiator::TransplantRefCnt
+      // for more info).
+      --result;
+    }
+  } else {
+    result = --mRefCnt;
+  }
+
+  if (!result) {
+    delete this;
+  }
+  return result;
+}
+
+/**
+ * Create a standard IDispatch implementation. mStdDispatch will translate any
+ * IDispatch::Invoke calls into real IAccessible calls.
+ */
+HRESULT
+LazyInstantiator::ResolveDispatch()
+{
+  if (mWeakDispatch) {
+    return S_OK;
+  }
+
+  // The IAccessible typelib is embedded in oleacc.dll's resources.
+  auto typelib = mscom::RegisterTypelib(L"oleacc.dll",
+                                        mscom::RegistrationFlags::eUseSystemDirectory);
+  if (!typelib) {
+    return E_UNEXPECTED;
+  }
+
+  // Extract IAccessible's type info
+  RefPtr<ITypeInfo> accTypeInfo;
+  HRESULT hr = typelib->GetTypeInfoForGuid(IID_IAccessible,
+                                           getter_AddRefs(accTypeInfo));
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  // Now create the standard IDispatch for IAccessible
+  hr = ::CreateStdDispatch(static_cast<IAccessible*>(this),
+                           static_cast<IAccessible*>(this),
+                           accTypeInfo, getter_AddRefs(mStdDispatch));
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  hr = mStdDispatch->QueryInterface(IID_IDispatch, (void**)&mWeakDispatch);
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  // WEAK reference
+  mWeakDispatch->Release();
+  return S_OK;
+}
+
+#define RESOLVE_IDISPATCH \
+  { \
+    HRESULT hr = ResolveDispatch(); \
+    if (FAILED(hr)) { \
+      return hr; \
+    } \
+  }
+
+/**
+ * The remaining methods implement IDispatch, IAccessible, and IServiceProvider,
+ * lazily resolving the real a11y objects and passing the call through.
+ */
+
+HRESULT
+LazyInstantiator::GetTypeInfoCount(UINT* pctinfo)
+{
+  RESOLVE_IDISPATCH;
+  return mWeakDispatch->GetTypeInfoCount(pctinfo);
+}
+
+HRESULT
+LazyInstantiator::GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo)
+{
+  RESOLVE_IDISPATCH;
+  return mWeakDispatch->GetTypeInfo(iTInfo, lcid, ppTInfo);
+}
+
+HRESULT
+LazyInstantiator::GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames,
+                                LCID lcid, DISPID* rgDispId)
+{
+  RESOLVE_IDISPATCH;
+  return mWeakDispatch->GetIDsOfNames(riid, rgszNames, cNames, lcid, rgDispId);
+}
+
+HRESULT
+LazyInstantiator::Invoke(DISPID dispIdMember, REFIID riid, LCID lcid,
+                         WORD wFlags, DISPPARAMS* pDispParams,
+                         VARIANT* pVarResult, EXCEPINFO* pExcepInfo,
+                         UINT* puArgErr)
+{
+  RESOLVE_IDISPATCH;
+  return mWeakDispatch->Invoke(dispIdMember, riid, lcid, wFlags, pDispParams,
+                               pVarResult, pExcepInfo, puArgErr);
+}
+
+HRESULT
+LazyInstantiator::get_accParent(IDispatch **ppdispParent)
+{
+  RESOLVE_ROOT;
+  return mWeakAccessible->get_accParent(ppdispParent);
+}
+
+HRESULT
+LazyInstantiator::get_accChildCount(long *pcountChildren)
+{
+  if (!pcountChildren) {
+    return E_INVALIDARG;
+  }
+
+  RESOLVE_ROOT;
+  return mWeakAccessible->get_accChildCount(pcountChildren);
+}
+
+HRESULT
+LazyInstantiator::get_accChild(VARIANT varChild, IDispatch **ppdispChild)
+{
+  if (!ppdispChild) {
+    return E_INVALIDARG;
+  }
+
+  RESOLVE_ROOT;
+  return mWeakAccessible->get_accChild(varChild, ppdispChild);
+}
+
+HRESULT
+LazyInstantiator::get_accName(VARIANT varChild, BSTR *pszName)
+{
+  if (!pszName) {
+    return E_INVALIDARG;
+  }
+
+  RESOLVE_ROOT;
+  return mWeakAccessible->get_accName(varChild, pszName);
+}
+
+HRESULT
+LazyInstantiator::get_accValue(VARIANT varChild, BSTR *pszValue)
+{
+  if (!pszValue) {
+    return E_INVALIDARG;
+  }
+
+  RESOLVE_ROOT;
+  return mWeakAccessible->get_accValue(varChild, pszValue);
+}
+
+HRESULT
+LazyInstantiator::get_accDescription(VARIANT varChild, BSTR *pszDescription)
+{
+  if (!pszDescription) {
+    return E_INVALIDARG;
+  }
+
+  RESOLVE_ROOT;
+  return mWeakAccessible->get_accDescription(varChild, pszDescription);
+}
+
+HRESULT
+LazyInstantiator::get_accRole(VARIANT varChild, VARIANT *pvarRole)
+{
+  if (!pvarRole) {
+    return E_INVALIDARG;
+  }
+
+  RESOLVE_ROOT;
+  return mWeakAccessible->get_accRole(varChild, pvarRole);
+}
+
+HRESULT
+LazyInstantiator::get_accState(VARIANT varChild, VARIANT *pvarState)
+{
+  if (!pvarState) {
+    return E_INVALIDARG;
+  }
+
+  RESOLVE_ROOT;
+  return mWeakAccessible->get_accState(varChild, pvarState);
+}
+
+HRESULT
+LazyInstantiator::get_accHelp(VARIANT varChild, BSTR *pszHelp)
+{
+  return E_NOTIMPL;
+}
+
+HRESULT
+LazyInstantiator::get_accHelpTopic(BSTR *pszHelpFile, VARIANT varChild,
+                                   long *pidTopic)
+{
+  return E_NOTIMPL;
+}
+
+HRESULT
+LazyInstantiator::get_accKeyboardShortcut(VARIANT varChild,
+                                          BSTR *pszKeyboardShortcut)
+{
+  if (!pszKeyboardShortcut) {
+    return E_INVALIDARG;
+  }
+
+  RESOLVE_ROOT;
+  return mWeakAccessible->get_accKeyboardShortcut(varChild, pszKeyboardShortcut);
+}
+
+HRESULT
+LazyInstantiator::get_accFocus(VARIANT *pvarChild)
+{
+  if (!pvarChild) {
+    return E_INVALIDARG;
+  }
+
+  RESOLVE_ROOT;
+  return mWeakAccessible->get_accFocus(pvarChild);
+}
+
+HRESULT
+LazyInstantiator::get_accSelection(VARIANT *pvarChildren)
+{
+  if (!pvarChildren) {
+    return E_INVALIDARG;
+  }
+
+  RESOLVE_ROOT;
+  return mWeakAccessible->get_accSelection(pvarChildren);
+}
+
+HRESULT
+LazyInstantiator::get_accDefaultAction(VARIANT varChild, BSTR *pszDefaultAction)
+{
+  if (!pszDefaultAction) {
+    return E_INVALIDARG;
+  }
+
+  RESOLVE_ROOT;
+  return mWeakAccessible->get_accDefaultAction(varChild, pszDefaultAction);
+}
+
+HRESULT
+LazyInstantiator::accSelect(long flagsSelect, VARIANT varChild)
+{
+  RESOLVE_ROOT;
+  return mWeakAccessible->accSelect(flagsSelect, varChild);
+}
+
+HRESULT
+LazyInstantiator::accLocation(long *pxLeft, long *pyTop, long *pcxWidth,
+                              long *pcyHeight, VARIANT varChild)
+{
+  RESOLVE_ROOT;
+  return mWeakAccessible->accLocation(pxLeft, pyTop, pcxWidth, pcyHeight, varChild);
+}
+
+HRESULT
+LazyInstantiator::accNavigate(long navDir, VARIANT varStart,
+                              VARIANT *pvarEndUpAt)
+{
+  return E_NOTIMPL;
+}
+
+HRESULT
+LazyInstantiator::accHitTest(long xLeft, long yTop, VARIANT *pvarChild)
+{
+  if (!pvarChild) {
+    return E_INVALIDARG;
+  }
+
+  RESOLVE_ROOT;
+  return mWeakAccessible->accHitTest(xLeft, yTop, pvarChild);
+}
+
+HRESULT
+LazyInstantiator::accDoDefaultAction(VARIANT varChild)
+{
+  RESOLVE_ROOT;
+  return mWeakAccessible->accDoDefaultAction(varChild);
+}
+
+HRESULT
+LazyInstantiator::put_accName(VARIANT varChild, BSTR szName)
+{
+  return E_NOTIMPL;
+}
+
+HRESULT
+LazyInstantiator::put_accValue(VARIANT varChild, BSTR szValue)
+{
+  return E_NOTIMPL;
+}
+
+HRESULT
+LazyInstantiator::QueryService(REFGUID aServiceId, REFIID aServiceIid,
+                               void** aOutInterface)
+{
+  if (!aOutInterface) {
+    return E_INVALIDARG;
+  }
+
+  *aOutInterface = nullptr;
+
+  RESOLVE_ROOT;
+
+  RefPtr<IServiceProvider> servProv;
+  HRESULT hr = mRealRootUnk->QueryInterface(IID_IServiceProvider,
+                                            getter_AddRefs(servProv));
+  if (FAILED(hr)) {
+    return hr;
+  }
+
+  return servProv->QueryService(aServiceId, aServiceIid, aOutInterface);
+}
+
+} // namespace a11y
+} // namespace mozilla
+
new file mode 100644
--- /dev/null
+++ b/accessible/windows/msaa/LazyInstantiator.h
@@ -0,0 +1,133 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_a11y_LazyInstantiator_h
+#define mozilla_a11y_LazyInstantiator_h
+
+#include "IUnknownImpl.h"
+#include "mozilla/RefPtr.h"
+#include "nsString.h"
+#if defined(MOZ_TELEMETRY_REPORTING)
+#include "nsThreadUtils.h"
+#endif // defined(MOZ_TELEMETRY_REPORTING)
+
+#include <oleacc.h>
+
+class nsIFile;
+
+namespace mozilla {
+namespace a11y {
+
+class RootAccessibleWrap;
+
+/**
+ * LazyInstantiator is an IAccessible that initially acts as a placeholder.
+ * The a11y service is not actually started until two conditions are met:
+ *
+ * (1) A method is called on the LazyInstantiator that would require a11y
+ *     services in order to fulfill; and
+ * (2) LazyInstantiator::ShouldInstantiate returns true.
+ */
+class LazyInstantiator final : public IAccessible
+                             , public IServiceProvider
+{
+public:
+  static already_AddRefed<IAccessible> GetRootAccessible(HWND aHwnd);
+  static void EnableBlindAggregation(HWND aHwnd);
+
+  // IUnknown
+  STDMETHODIMP QueryInterface(REFIID aIid, void** aOutInterface) override;
+  STDMETHODIMP_(ULONG) AddRef() override;
+  STDMETHODIMP_(ULONG) Release() override;
+
+  // IDispatch
+  STDMETHODIMP GetTypeInfoCount(UINT* pctinfo) override;
+  STDMETHODIMP GetTypeInfo(UINT iTInfo, LCID lcid, ITypeInfo **ppTInfo) override;
+  STDMETHODIMP GetIDsOfNames(REFIID riid, LPOLESTR *rgszNames, UINT cNames,
+                             LCID lcid, DISPID* rgDispId) override;
+  STDMETHODIMP Invoke(DISPID dispIdMember, REFIID riid, LCID lcid, WORD wFlags,
+                      DISPPARAMS* pDispParams, VARIANT* pVarResult,
+                      EXCEPINFO* pExcepInfo, UINT* puArgErr) override;
+
+  // IAccessible
+  STDMETHODIMP get_accParent(IDispatch **ppdispParent) override;
+  STDMETHODIMP get_accChildCount(long *pcountChildren) override;
+  STDMETHODIMP get_accChild(VARIANT varChild, IDispatch **ppdispChild) override;
+  STDMETHODIMP get_accName(VARIANT varChild, BSTR *pszName) override;
+  STDMETHODIMP get_accValue(VARIANT varChild, BSTR *pszValue) override;
+  STDMETHODIMP get_accDescription(VARIANT varChild, BSTR *pszDescription) override;
+  STDMETHODIMP get_accRole(VARIANT varChild, VARIANT *pvarRole) override;
+  STDMETHODIMP get_accState(VARIANT varChild, VARIANT *pvarState) override;
+  STDMETHODIMP get_accHelp(VARIANT varChild, BSTR *pszHelp) override;
+  STDMETHODIMP get_accHelpTopic(BSTR *pszHelpFile, VARIANT varChild, long *pidTopic) override;
+  STDMETHODIMP get_accKeyboardShortcut(VARIANT varChild, BSTR *pszKeyboardShortcut) override;
+  STDMETHODIMP get_accFocus(VARIANT *pvarChild) override;
+  STDMETHODIMP get_accSelection(VARIANT *pvarChildren) override;
+  STDMETHODIMP get_accDefaultAction(VARIANT varChild, BSTR *pszDefaultAction) override;
+  STDMETHODIMP accSelect(long flagsSelect, VARIANT varChild) override;
+  STDMETHODIMP accLocation(long *pxLeft, long *pyTop, long *pcxWidth, long *pcyHeight, VARIANT varChild) override;
+  STDMETHODIMP accNavigate(long navDir, VARIANT varStart, VARIANT *pvarEndUpAt) override;
+  STDMETHODIMP accHitTest(long xLeft, long yTop, VARIANT *pvarChild) override;
+  STDMETHODIMP accDoDefaultAction(VARIANT varChild) override;
+  STDMETHODIMP put_accName(VARIANT varChild, BSTR szName) override;
+  STDMETHODIMP put_accValue(VARIANT varChild, BSTR szValue) override;
+
+  // IServiceProvider
+  STDMETHODIMP QueryService(REFGUID aServiceId, REFIID aServiceIid, void** aOutInterface) override;
+
+private:
+  explicit LazyInstantiator(HWND aHwnd);
+  ~LazyInstantiator();
+
+  bool ShouldInstantiate(const DWORD aClientTid);
+
+  bool GetClientExecutableName(const DWORD aClientTid, nsIFile** aOutClientExe);
+#if defined(MOZ_TELEMETRY_REPORTING)
+  void AppendVersionInfo(nsIFile* aClientExe, nsAString& aStrToAppend);
+  void GatherTelemetry(nsIFile* aClientExe);
+  void AccumulateTelemetry(const nsString& aValue);
+#endif // defined(MOZ_TELEMETRY_REPORTING)
+
+  /**
+   * @return S_OK if we have a valid mRealRoot to invoke methods on
+   */
+  HRESULT MaybeResolveRoot();
+
+  /**
+   * @return S_OK if we have a valid mWeakDispatch to invoke methods on
+   */
+  HRESULT ResolveDispatch();
+
+  RootAccessibleWrap* ResolveRootAccWrap();
+  void TransplantRefCnt();
+  void ClearProp();
+
+private:
+  mozilla::a11y::AutoRefCnt mRefCnt;
+  HWND                mHwnd;
+  bool                mAllowBlindAggregation;
+  RefPtr<IUnknown>    mRealRootUnk;
+  RefPtr<IUnknown>    mStdDispatch;
+  /**
+   * mWeakRootAccWrap, mWeakAccessible and mWeakDispatch are weak because they
+   * are interfaces that come from objects that we aggregate. Aggregated object
+   * interfaces share refcount methods with ours, so if we were to hold strong
+   * references to them, we would be holding strong references to ourselves,
+   * creating a cycle.
+   */
+  RootAccessibleWrap* mWeakRootAccWrap;
+  IAccessible*        mWeakAccessible;
+  IDispatch*          mWeakDispatch;
+#if defined(MOZ_TELEMETRY_REPORTING)
+  nsCOMPtr<nsIThread> mTelemetryThread;
+#endif // defined(MOZ_TELEMETRY_REPORTING)
+};
+
+} // namespace a11y
+} // namespace mozilla
+
+#endif // mozilla_a11y_LazyInstantiator_h
+
--- a/accessible/windows/msaa/RootAccessibleWrap.cpp
+++ b/accessible/windows/msaa/RootAccessibleWrap.cpp
@@ -9,27 +9,84 @@
 #include "nsCoreUtils.h"
 #include "nsWinUtils.h"
 
 using namespace mozilla::a11y;
 
 ////////////////////////////////////////////////////////////////////////////////
 // Constructor/destructor
 
-RootAccessibleWrap::
-  RootAccessibleWrap(nsIDocument* aDocument, nsIPresShell* aPresShell) :
-  RootAccessible(aDocument, aPresShell)
+RootAccessibleWrap::RootAccessibleWrap(nsIDocument* aDocument,
+                                       nsIPresShell* aPresShell)
+  : RootAccessible(aDocument, aPresShell)
+  , mOuter(&mInternalUnknown)
 {
 }
 
 RootAccessibleWrap::~RootAccessibleWrap()
 {
 }
 
 ////////////////////////////////////////////////////////////////////////////////
+// Aggregated IUnknown
+HRESULT
+RootAccessibleWrap::InternalQueryInterface(REFIID aIid, void** aOutInterface)
+{
+  if (!aOutInterface) {
+    return E_INVALIDARG;
+  }
+
+  // InternalQueryInterface should always return its internal unknown
+  // when queried for IID_IUnknown...
+  if (aIid == IID_IUnknown) {
+    RefPtr<IUnknown> punk(&mInternalUnknown);
+    punk.forget(aOutInterface);
+    return S_OK;
+  }
+
+  // ...Otherwise we pass through to the base COM implementation of
+  // QueryInterface which is provided by DocAccessibleWrap.
+  return DocAccessibleWrap::QueryInterface(aIid, aOutInterface);
+}
+
+ULONG
+RootAccessibleWrap::InternalAddRef()
+{
+  return DocAccessible::AddRef();
+}
+
+ULONG
+RootAccessibleWrap::InternalRelease()
+{
+  return DocAccessible::Release();
+}
+
+already_AddRefed<IUnknown>
+RootAccessibleWrap::Aggregate(IUnknown* aOuter)
+{
+  MOZ_ASSERT(mOuter && (mOuter == &mInternalUnknown || mOuter == aOuter || !aOuter));
+  if (!aOuter) {
+    // If there is no aOuter then we should always set mOuter to
+    // mInternalUnknown. This is standard COM aggregation stuff.
+    mOuter = &mInternalUnknown;
+    return nullptr;
+  }
+
+  mOuter = aOuter;
+  return GetInternalUnknown();
+}
+
+already_AddRefed<IUnknown>
+RootAccessibleWrap::GetInternalUnknown()
+{
+  RefPtr<IUnknown> result(&mInternalUnknown);
+  return result.forget();
+}
+
+////////////////////////////////////////////////////////////////////////////////
 // RootAccessible
 
 void
 RootAccessibleWrap::DocumentActivated(DocAccessible* aDocument)
 {
   if (Compatibility::IsDolphin() &&
       nsCoreUtils::IsTabDocument(aDocument->DocumentNode())) {
     uint32_t count = mChildDocuments.Length();
--- a/accessible/windows/msaa/RootAccessibleWrap.h
+++ b/accessible/windows/msaa/RootAccessibleWrap.h
@@ -1,27 +1,51 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_a11y_RootAccessibleWrap_h__
 #define mozilla_a11y_RootAccessibleWrap_h__
 
+#include "mozilla/mscom/Aggregation.h"
 #include "RootAccessible.h"
 
 namespace mozilla {
 namespace a11y {
 
 class RootAccessibleWrap : public RootAccessible
 {
 public:
   RootAccessibleWrap(nsIDocument* aDocument, nsIPresShell* aPresShell);
   virtual ~RootAccessibleWrap();
 
   // RootAccessible
   virtual void DocumentActivated(DocAccessible* aDocument);
+
+  /**
+   * This method enables a RootAccessibleWrap to be wrapped by a
+   * LazyInstantiator.
+   *
+   * @param aOuter The IUnknown of the object that is wrapping this
+   *               RootAccessibleWrap, or nullptr to unwrap the aOuter from
+   *               a previous call.
+   * @return This objects own IUnknown (as opposed to aOuter's IUnknown).
+   */
+  already_AddRefed<IUnknown> Aggregate(IUnknown* aOuter);
+
+  /**
+   * @return This object's own IUnknown, as opposed to its wrapper's IUnknown
+   *         which is what would be returned by QueryInterface(IID_IUnknown).
+   */
+  already_AddRefed<IUnknown> GetInternalUnknown();
+
+private:
+  // DECLARE_AGGREGATABLE declares the internal IUnknown methods as well as
+  // mInternalUnknown.
+  DECLARE_AGGREGATABLE(RootAccessibleWrap);
+  IUnknown* mOuter;
 };
 
 } // namespace a11y
 } // namespace mozilla
 
 #endif
--- a/accessible/windows/msaa/moz.build
+++ b/accessible/windows/msaa/moz.build
@@ -8,16 +8,17 @@ EXPORTS += [
     'IUnknownImpl.h',
 ]
 
 EXPORTS.mozilla.a11y += [
     'AccessibleWrap.h',
     'Compatibility.h',
     'HyperTextAccessibleWrap.h',
     'IDSet.h',
+    'LazyInstantiator.h',
     'MsaaIdGenerator.h',
     'nsWinUtils.h',
 ]
 
 UNIFIED_SOURCES += [
     'AccessibleWrap.cpp',
     'ApplicationAccessibleWrap.cpp',
     'ARIAGridAccessibleWrap.cpp',
@@ -32,18 +33,20 @@ UNIFIED_SOURCES += [
     'IUnknownImpl.cpp',
     'MsaaIdGenerator.cpp',
     'nsWinUtils.cpp',
     'Platform.cpp',
     'RootAccessibleWrap.cpp',
     'TextLeafAccessibleWrap.cpp',
 ]
 
-# This file cannot be built in unified mode because it includes ISimpleDOMNode_i.c.
 SOURCES += [
+    # This file cannot be built in unified mode because it redefines _WIN32_WINNT
+    'LazyInstantiator.cpp',
+    # This file cannot be built in unified mode because it includes ISimpleDOMNode_i.c.
     'ServiceProvider.cpp',
 ]
 
 if CONFIG['MOZ_XUL']:
     UNIFIED_SOURCES += [
         'XULListboxAccessibleWrap.cpp',
         'XULMenuAccessibleWrap.cpp',
         'XULTreeGridAccessibleWrap.cpp',
--- a/addon-sdk/source/lib/sdk/addon/runner.js
+++ b/addon-sdk/source/lib/sdk/addon/runner.js
@@ -65,28 +65,28 @@ function startup(reason, options) {
 
     // NOTE: Module is intentionally required only now because it relies
     // on existence of hidden window, which does not exists until startup.
     let { ready } = require('../addon/window');
     // Load localization manifest and .properties files.
     // Run the addon even in case of error (best effort approach)
     require('../l10n/loader').
       load(rootURI).
-      then(null, function failure(error) {
+      catch(function failure(error) {
         if (!isNative)
           console.info("Error while loading localization: " + error.message);
       }).
       then(function onLocalizationReady(data) {
         // Exports data to a pseudo module so that api-utils/l10n/core
         // can get access to it
         definePseudo(options.loader, '@l10n/data', data ? data : null);
         return ready;
       }).then(function() {
         run(options);
-      }).then(null, console.exception);
+      }).catch(console.exception);
     return void 0; // otherwise we raise a warning, see bug 910304
   });
 }
 
 function run(options) {
   try {
     // Try initializing HTML localization before running main module. Just print
     // an exception in case of error, instead of preventing addon to be run.
--- a/addon-sdk/source/lib/sdk/l10n/loader.js
+++ b/addon-sdk/source/lib/sdk/l10n/loader.js
@@ -10,17 +10,17 @@ module.metadata = {
 const { Cc, Ci } = require("chrome");
 lazyRequire(this, "./locale", "getPreferedLocales", "findClosestLocale");
 lazyRequire(this, "../net/url", "readURI");
 lazyRequire(this, "../core/promise", "resolve");
 
 function parseJsonURI(uri) {
   return readURI(uri).
     then(JSON.parse).
-    then(null, function (error) {
+    catch(function (error) {
       throw Error("Failed to parse locale file:\n" + uri + "\n" + error);
     });
 }
 
 // Returns the array stored in `locales.json` manifest that list available
 // locales files
 function getAvailableLocales(rootURI) {
   let uri = rootURI + "locales.json";
--- a/addon-sdk/source/test/addons/page-mod-debugger-post/main.js
+++ b/addon-sdk/source/test/addons/page-mod-debugger-post/main.js
@@ -38,17 +38,17 @@ exports.testDebugger = function(assert, 
           then(_ => { assert.pass('attachTabActorForUrl called'); return _; }).
           then(attachThread).
           then(testDebuggerStatement).
           then(_ => { assert.pass('testDebuggerStatement called') }).
           then(closeConnection).
           then(_ => { assert.pass('closeConnection called') }).
           then(_ => { tab.close() }).
           then(done).
-          then(null, aError => {
+          catch(aError => {
             ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
           });
       }
     });
   });
 }
 
 function attachThread([aGrip, aResponse]) {
--- a/addon-sdk/source/test/addons/page-mod-debugger-pre/main.js
+++ b/addon-sdk/source/test/addons/page-mod-debugger-pre/main.js
@@ -45,17 +45,17 @@ exports.testDebugger = function(assert, 
           then(_ => { assert.pass('attachTabActorForUrl called'); return _; }).
           then(attachThread).
           then(testDebuggerStatement).
           then(_ => { assert.pass('testDebuggerStatement called') }).
           then(closeConnection).
           then(_ => { assert.pass('closeConnection called') }).
           then(_ => { tab.close() }).
           then(done).
-          then(null, aError => {
+          catch(aError => {
             ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
           });
       }
     });
   });
 }
 
 function attachThread([aGrip, aResponse]) {
--- a/addon-sdk/source/test/addons/places/lib/test-places-host.js
+++ b/addon-sdk/source/test/addons/places/lib/test-places-host.js
@@ -61,17 +61,17 @@ exports.testBookmarksCreateFail = functi
     type: 'bookmark'
   }, {
     type: 'group',
     group: bmsrv.bookmarksMenuFolder
   }, {
     group: bmsrv.unfiledBookmarksFolder
   }];
   all(items.map(function (item) {
-    return send('sdk-places-bookmarks-create', item).then(null, function (reason) {
+    return send('sdk-places-bookmarks-create', item).catch(function (reason) {
       assert.ok(reason, 'bookmark create should fail');
     });
   })).then(done);
 };
 
 exports.testBookmarkLastUpdated = function (assert, done) {
   let timestamp;
   let item;
--- a/addon-sdk/source/test/addons/private-browsing-supported/test-selection.js
+++ b/addon-sdk/source/test/addons/private-browsing-supported/test-selection.js
@@ -228,24 +228,24 @@ exports["test PWPB Selection Listener"] 
           assert.ok(isFocused(window), "the window is focused");
           assert.ok(isPrivate(window), "the window should be a private window");
 
           assert.equal(selection.text, "fo");
 
           closeWindow(window).
             then(loader.unload).
             then(done).
-            then(null, assert.fail);
+            catch(assert.fail);
         });
       });
       return window;
     }).
     then(selectContentFirstDiv).
     then(dispatchSelectionEvent).
-    then(null, assert.fail);
+    catch(assert.fail);
 };
 
 exports["test PWPB Textarea OnSelect Listener"] = function(assert, done) {
   let loader = Loader(module);
   let selection = loader.require("sdk/selection");
 
   open(URL, {private: true, title: "PWPB OnSelect Listener"}).
     then(function(window) {
@@ -258,24 +258,24 @@ exports["test PWPB Textarea OnSelect Lis
 
         // window should be focused, but force the focus anyhow.. see bug 841823
         focus(window).then(function() {
           assert.equal(selection.text, "noodles");
 
           closeWindow(window).
             then(loader.unload).
             then(done).
-            then(null, assert.fail);
+            catch(assert.fail);
         });
       });
       return window;
     }).
     then(selectTextarea).
     then(dispatchOnSelectEvent).
-    then(null, assert.fail);
+    catch(assert.fail);
 };
 
 exports["test PWPB Single DOM Selection"] = function(assert, done) {
   let loader = Loader(module);
   let selection = loader.require("sdk/selection");
 
   open(URL, {private: true, title: "PWPB Single DOM Selection"}).
     then(selectFirstDiv).
@@ -299,17 +299,17 @@ exports["test PWPB Single DOM Selection"
         assert.equal(sel.html, "<div>foo</div>",
           "iterable selection.html with single DOM Selection works.");
       }
 
       assert.equal(selectionCount, 1,
         "One iterable selection");
 
       return closeWindow(window);
-    }).then(loader.unload).then(done).then(null, assert.fail);
+    }).then(loader.unload).then(done).catch(assert.fail);
 }
 
 exports["test PWPB Textarea Selection"] = function(assert, done) {
   let loader = Loader(module);
   let selection = loader.require("sdk/selection");
 
   open(URL, {private: true, title: "PWPB Textarea Listener"}).
     then(selectTextarea).
@@ -335,17 +335,17 @@ exports["test PWPB Textarea Selection"] 
         assert.strictEqual(sel.html, null,
           "iterable selection.html with Textarea Selection works.");
       }
 
       assert.equal(selectionCount, 1,
         "One iterable selection");
 
       return closeWindow(window);
-    }).then(loader.unload).then(done).then(null, assert.fail);
+    }).then(loader.unload).then(done).catch(assert.fail);
 };
 
 exports["test PWPB Set HTML in Multiple DOM Selection"] = function(assert, done) {
   let loader = Loader(module);
   let selection = loader.require("sdk/selection");
 
   open(URL, {private: true, title: "PWPB Set HTML in Multiple DOM Selection"}).
     then(selectAllDivs).
@@ -375,17 +375,17 @@ exports["test PWPB Set HTML in Multiple 
 
         selectionCount++;
       }
 
       assert.equal(selectionCount, 2,
         "Two iterable selections");
 
       return closeWindow(window);
-    }).then(loader.unload).then(done).then(null, assert.fail);
+    }).then(loader.unload).then(done).catch(assert.fail);
 };
 
 exports["test PWPB Set Text in Textarea Selection"] = function(assert, done) {
   let loader = Loader(module);
   let selection = loader.require("sdk/selection");
 
   open(URL, {private: true, title: "test PWPB Set Text in Textarea Selection"}).
     then(selectTextarea).
@@ -412,17 +412,17 @@ exports["test PWPB Set Text in Textarea 
         assert.strictEqual(sel.html, null,
           "iterable selection.html with Textarea Selection works.");
       }
 
       assert.equal(selectionCount, 1,
         "One iterable selection");
 
       return closeWindow(window);
-    }).then(loader.unload).then(done).then(null, assert.fail);
+    }).then(loader.unload).then(done).catch(assert.fail);
 };
 
 // If the platform doesn't support the PBPW, we're replacing PBPW tests
 if (!require("sdk/private-browsing/utils").isWindowPBSupported) {
   module.exports = {
     "test PBPW Unsupported": function Unsupported (assert) {
       assert.pass("Private Window Per Browsing is not supported on this platform.");
     }
--- a/addon-sdk/source/test/addons/private-browsing-supported/test-sidebar.js
+++ b/addon-sdk/source/test/addons/private-browsing-supported/test-sidebar.js
@@ -35,17 +35,17 @@ exports.testSideBarIsInNewPrivateWindows
       assert.ok(isPrivate(window), 'the new window is private');
       assert.ok(!!ele, 'sidebar element was added');
 
       sidebar.destroy();
       assert.ok(!window.document.getElementById(makeID(testName)), 'sidebar id DNE');
       assert.ok(!startWindow.document.getElementById(makeID(testName)), 'sidebar id DNE');
 
       return close(window);
-  }).then(done).then(null, assert.fail);
+  }).then(done).catch(assert.fail);
 }
 
 // Disabled in order to land other fixes, see bug 910647 for further details.
 /*
 exports.testSidebarIsOpenInNewPrivateWindow = function(assert, done) {
   const { Sidebar } = require('sdk/ui/sidebar');
   let testName = 'testSidebarIsOpenInNewPrivateWindow';
   let window = getMostRecentBrowserWindow();
@@ -137,18 +137,18 @@ exports.testDestroyEdgeCaseBugWithPrivat
           assert.equal(isSidebarShowing(window), false, 'the sidebar is not showing');
 
           done();
         }
       })
 
       sidebar.show();
       assert.pass('showing the sidebar');
-    }).then(null, assert.fail);
-  }).then(null, assert.fail);
+    }).catch(assert.fail);
+  }).catch(assert.fail);
 }
 
 exports.testShowInPrivateWindow = function(assert, done) {
   const { Sidebar } = require('sdk/ui/sidebar');
   let testName = 'testShowInPrivateWindow';
   let window1 = getMostRecentBrowserWindow();
   let url = 'data:text/html;charset=utf-8,'+testName;
 
@@ -183,22 +183,22 @@ exports.testShowInPrivateWindow = functi
                   'the menuitem on the new window dne');
 
         // test old window state
         assert.equal(isSidebarShowing(window1), false, 'the old window sidebar is not showing');
         assert.equal(window1.document.getElementById(menuitemID),
                      null,
                      'the menuitem on the old window dne');
 
-        close(window).then(done).then(null, assert.fail);
+        close(window).then(done).catch(assert.fail);
       },
       function bad() {
         assert.fail('a successful show should not happen here..');
       });
-  }).then(null, assert.fail);
+  }).catch(assert.fail);
 }
 
 // If the module doesn't support the app we're being run in, require() will
 // throw.  In that case, remove all tests above from exports, and add one dummy
 // test that passes.
 try {
   require('sdk/ui/sidebar');
 }
--- a/addon-sdk/source/test/addons/private-browsing-supported/test-window-tabs.js
+++ b/addon-sdk/source/test/addons/private-browsing-supported/test-window-tabs.js
@@ -13,63 +13,63 @@ exports.testOpenTabWithPrivateActiveWind
 
   windowPromise(window, 'load').then(focus).then(function (window) {
     assert.ok(isPrivate(window), 'new window is private');
 
     tabs.open({
       url: 'about:blank',
       onOpen: function(tab) {
         assert.ok(isPrivate(tab), 'new tab is private');
-        close(window).then(done).then(null, assert.fail);
+        close(window).then(done).catch(assert.fail);
       }
     })
-  }).then(null, assert.fail);
+  }).catch(assert.fail);
 }
 
 exports.testOpenTabWithNonPrivateActiveWindowNoIsPrivateOption = function(assert, done) {
   let window = getMostRecentBrowserWindow().OpenBrowserWindow({ private: false });
 
   windowPromise(window, 'load').then(focus).then(function (window) {
     assert.equal(isPrivate(window), false, 'new window is not private');
 
     tabs.open({
       url: 'about:blank',
       onOpen: function(tab) {
         assert.equal(isPrivate(tab), false, 'new tab is not private');
-        close(window).then(done).then(null, assert.fail);
+        close(window).then(done).catch(assert.fail);
       }
     })
-  }).then(null, assert.fail);
+  }).catch(assert.fail);
 }
 
 exports.testOpenTabWithPrivateActiveWindowWithIsPrivateOptionTrue = function(assert, done) {
   let window = getMostRecentBrowserWindow().OpenBrowserWindow({ private: true });
 
   windowPromise(window, 'load').then(focus).then(function (window) {
     assert.ok(isPrivate(window), 'new window is private');
 
     tabs.open({
       url: 'about:blank',
       isPrivate: true,
       onOpen: function(tab) {
         assert.ok(isPrivate(tab), 'new tab is private');
-        close(window).then(done).then(null, assert.fail);
+        close(window).then(done).catch(assert.fail);
       }
     })
-  }).then(null, assert.fail);
+  }).catch(assert.fail);
 }
 
 exports.testOpenTabWithNonPrivateActiveWindowWithIsPrivateOptionFalse = function(assert, done) {
   let window = getMostRecentBrowserWindow().OpenBrowserWindow({ private: false });
 
   windowPromise(window, 'load').then(focus).then(function (window) {
     assert.equal(isPrivate(window), false, 'new window is not private');
 
     tabs.open({
       url: 'about:blank',
       isPrivate: false,
       onOpen: function(tab) {
         assert.equal(isPrivate(tab), false, 'new tab is not private');
-        close(window).then(done).then(null, assert.fail);
+        close(window).then(done).catch(assert.fail);
       }
     })
-  }).then(null, assert.fail);
+  }).catch(assert.fail);
 }
--- a/addon-sdk/source/test/addons/private-browsing-supported/test-windows.js
+++ b/addon-sdk/source/test/addons/private-browsing-supported/test-windows.js
@@ -69,17 +69,17 @@ exports.testWindowTrackerIgnoresPrivateW
       assert.pass('private window was closed');
 
       return makeEmptyBrowserWindow().then(function(window) {
         myNonPrivateWindowId = getInnerId(window);
         assert.notEqual(myPrivateWindowId, myNonPrivateWindowId, 'non private window was opened');
         return close(window);
       });
     });
-  }).then(null, assert.fail);
+  }).catch(assert.fail);
 };
 
 // Test setting activeWIndow and onFocus for private windows
 exports.testSettingActiveWindowDoesNotIgnorePrivateWindow = function(assert, done) {
   let browserWindow = WM.getMostRecentWindow("navigator:browser");
   let testSteps;
 
   assert.equal(winUtils.activeBrowserWindow, browserWindow,
@@ -141,17 +141,17 @@ exports.testSettingActiveWindowDoesNotIg
         continueAfterFocus(winUtils.activeWindow = browserWindow);
       },
       function() {
         assert.strictEqual(winUtils.activeBrowserWindow, browserWindow,
                           "Correct active browser window when pb mode is supported [4]");
         assert.strictEqual(winUtils.activeWindow, browserWindow,
                           "Correct active window when pb mode is supported [4]");
 
-        close(window).then(done).then(null, assert.fail);
+        close(window).then(done).catch(assert.fail);
       }
     ];
 
     function nextTest() {
       let args = arguments;
       if (testSteps.length) {
         require('sdk/timers').setTimeout(function() {
           (testSteps.shift()).apply(null, args);
@@ -231,10 +231,10 @@ exports.testWindowIteratorPrivateDefault
     assert.equal(isPrivate(winUtils.activeWindow), isWindowPBSupported);
     assert.equal(isPrivate(getMostRecentWindow()), isWindowPBSupported);
     assert.equal(isPrivate(browserWindows.activeWindow), isWindowPBSupported);
 
     assert.equal(browserWindows.length, 2, '2 windows open');
     assert.equal(windows(null, { includePrivate: true }).length, 2);
 
     return close(window);
-  }).then(done).then(null, assert.fail);
+  }).then(done).catch(assert.fail);
 };
--- a/addon-sdk/source/test/tabs/test-firefox-tabs.js
+++ b/addon-sdk/source/test/tabs/test-firefox-tabs.js
@@ -70,17 +70,17 @@ exports.testBrowserWindowCreationOnActiv
   tabs.once('activate', function onActivate(eventTab) {
     assert.ok(windows.activeWindow, "Is able to fetch activeWindow");
     gotActivate = true;
   });
 
   open().then(function(window) {
     assert.ok(gotActivate, "Received activate event");
     return close(window);
-  }).then(done).then(null, assert.fail);
+  }).then(done).catch(assert.fail);
 }
 
 // TEST: tab unloader
 exports.testAutomaticDestroyEventOpen = function(assert, done) {
   let called = false;
   let loader = Loader(module);
   let tabs2 = loader.require("sdk/tabs");
   tabs2.on('open', _ => called = true);
@@ -171,17 +171,17 @@ exports.testTabPropertiesInNewWindow = f
   });
 
   let tabs = loader.require('sdk/tabs');
   let { viewFor } = loader.require('sdk/view/core');
 
   let count = 0;
   function onReadyOrLoad (tab) {
     if (count++) {
-      close(getOwnerWindow(viewFor(tab))).then(done).then(null, assert.fail);
+      close(getOwnerWindow(viewFor(tab))).then(done).catch(assert.fail);
     }
   }
 
   let url = "data:text/html;charset=utf-8,<html><head><title>foo</title></head><body>foo</body></html>";
   tabs.open({
     inNewWindow: true,
     url: url,
     onReady: function(tab) {
@@ -264,17 +264,17 @@ exports.testTabContentTypeAndReload = fu
       url: url,
       onReady: function(tab) {
         if (tab.url === url) {
           assert.equal(tab.contentType, "text/html");
           tab.url = urlXML;
         }
         else {
           assert.equal(tab.contentType, "text/xml");
-          close(window).then(done).then(null, assert.fail);
+          close(window).then(done).catch(assert.fail);
         }
       }
     });
   });
 };
 
 // TEST: tabs iterator and length property
 exports.testTabsIteratorAndLength = function(assert, done) {
@@ -289,34 +289,34 @@ exports.testTabsIteratorAndLength = func
     tabs.open({
       url: url,
       onOpen: function(tab) {
         let count = 0;
         for (let t of tabs) count++;
         assert.equal(count, startCount + 3, "iterated tab count matches");
         assert.equal(startCount + 3, tabs.length, "iterated tab count matches length property");
 
-        close(window).then(done).then(null, assert.fail);
+        close(window).then(done).catch(assert.fail);
       }
     });
   });
 };
 
 // TEST: tab.url setter
 exports.testTabLocation = function(assert, done) {
   open().then(focus).then(function(window) {
     let url1 = "data:text/html;charset=utf-8,foo";
     let url2 = "data:text/html;charset=utf-8,bar";
 
     tabs.on('ready', function onReady(tab) {
       if (tab.url != url2)
         return;
       tabs.removeListener('ready', onReady);
       assert.pass("tab.load() loaded the correct url");
-      close(window).then(done).then(null, assert.fail);
+      close(window).then(done).catch(assert.fail);
     });
 
     tabs.open({
       url: url1,
       onOpen: function(tab) {
         tab.url = url2
       }
     });
@@ -360,20 +360,20 @@ exports.testTabMove = function(assert, d
     let url = "data:text/html;charset=utf-8,foo";
 
     tabs.open({
       url: url,
       onOpen: function(tab) {
         assert.equal(tab.index, 1, "tab index before move matches");
         tab.index = 0;
         assert.equal(tab.index, 0, "tab index after move matches");
-        close(window).then(done).then(null, assert.fail);
+        close(window).then(done).catch(assert.fail);
       }
     });
-  }).then(null, assert.fail);
+  }).catch(assert.fail);
 };
 
 exports.testIgnoreClosing = function*(assert) {
   let url = "data:text/html;charset=utf-8,foobar";
   let originalWindow = getMostRecentBrowserWindow();
 
   let window = yield open().then(focus);
 
@@ -476,17 +476,17 @@ exports.testOpenInNewWindow = function(a
 
       onFocus(newWindow).then(function() {
         assert.equal(getMostRecentBrowserWindow(), newWindow, "new window is active");
         assert.equal(tab.url, url, "URL of the new tab matches");
         assert.equal(newWindow.content.location, url, "URL of new tab in new window matches");
         assert.equal(tabs.activeTab.url, url, "URL of activeTab matches");
 
         return close(newWindow).then(done);
-      }).then(null, assert.fail);
+      }).catch(assert.fail);
     }
   });
 
 }
 
 // Test tab.open inNewWindow + onOpen combination
 exports.testOpenInNewWindowOnOpen = function(assert, done) {
   let startWindowCount = windows().length;
@@ -497,17 +497,17 @@ exports.testOpenInNewWindowOnOpen = func
     inNewWindow: true,
     onOpen: function(tab) {
       let newWindow = getOwnerWindow(viewFor(tab));
 
       onFocus(newWindow).then(function() {
         assert.equal(windows().length, startWindowCount + 1, "a new window was opened");
         assert.equal(getMostRecentBrowserWindow(), newWindow, "new window is active");
 
-        close(newWindow).then(done).then(null, assert.fail);
+        close(newWindow).then(done).catch(assert.fail);
       });
     }
   });
 };
 
 // TEST: onOpen event handler
 exports.testTabsEvent_onOpen = function(assert, done) {
   open().then(focus).then(window => {
@@ -520,21 +520,21 @@ exports.testTabsEvent_onOpen = function(
     };
     tabs.on('open', listener1);
 
     // add listener via collection add
     tabs.on('open', function listener2(tab) {
       assert.equal(++eventCount, 2, "both listeners notified");
       tabs.removeListener('open', listener1);
       tabs.removeListener('open', listener2);
-      close(window).then(done).then(null, assert.fail);
+      close(window).then(done).catch(assert.fail);
     });
 
     tabs.open(url);
-  }).then(null, assert.fail);
+  }).catch(assert.fail);
 };
 
 // TEST: onClose event handler
 exports.testTabsEvent_onClose = function*(assert) {
   let window = yield open().then(focus);
   let url = "data:text/html;charset=utf-8,onclose";
   let eventCount = 0;
 
@@ -616,17 +616,17 @@ exports.testTabsEvent_onCloseWindow = fu
       onClose: endTest
     });
 
     tabs.open({
       url: "data:text/html;charset=utf-8,tab4",
       onOpen: testCasePossiblyLoaded,
       onClose: endTest
     });
-  }).then(null, assert.fail);
+  }).catch(assert.fail);
 }
 
 // TEST: onReady event handler
 exports.testTabsEvent_onReady = function(assert, done) {
   open().then(focus).then(window => {
     let url = "data:text/html;charset=utf-8,onready";
     let eventCount = 0;
 
@@ -640,17 +640,17 @@ exports.testTabsEvent_onReady = function
     tabs.on('ready', function listener2(tab) {
       assert.equal(++eventCount, 2, "both listeners notified");
       tabs.removeListener('ready', listener1);
       tabs.removeListener('ready', listener2);
       close(window).then(done);
     });
 
     tabs.open(url);
-  }).then(null, assert.fail);
+  }).catch(assert.fail);
 };
 
 // TEST: onActivate event handler
 exports.testTabsEvent_onActivate = function(assert, done) {
   open().then(focus).then(window => {
     let url = "data:text/html;charset=utf-8,onactivate";
     let eventCount = 0;
 
@@ -660,21 +660,21 @@ exports.testTabsEvent_onActivate = funct
     };
     tabs.on('activate', listener1);
 
     // add listener via collection add
     tabs.on('activate', function listener2(tab) {
       assert.equal(++eventCount, 2, "both listeners notified");
       tabs.removeListener('activate', listener1);
       tabs.removeListener('activate', listener2);
-      close(window).then(done).then(null, assert.fail);
+      close(window).then(done).catch(assert.fail);
     });
 
     tabs.open(url);
-  }).then(null, assert.fail);
+  }).catch(assert.fail);
 };
 
 // onDeactivate event handler
 exports.testTabsEvent_onDeactivate = function*(assert) {
   let window = yield open().then(focus);
 
   let url = "data:text/html;charset=utf-8,ondeactivate";
   let eventCount = 0;
@@ -721,21 +721,21 @@ exports.testTabsEvent_pinning = function
       tabs.removeListener('pinned', onPinned);
       assert.ok(tab.isPinned, "notified tab is pinned");
       tab.unpin();
     });
 
     tabs.on('unpinned', function onUnpinned(tab) {
       tabs.removeListener('unpinned', onUnpinned);
       assert.ok(!tab.isPinned, "notified tab is not pinned");
-      close(window).then(done).then(null, assert.fail);
+      close(window).then(done).catch(assert.fail);
     });
 
     tabs.open(url);
-  }).then(null, assert.fail);
+  }).catch(assert.fail);
 };
 
 // TEST: per-tab event handlers
 exports.testPerTabEvents = function*(assert) {
   let window = yield open().then(focus);
   let eventCount = 0;
 
   let tab = yield new Promise(resolve => {
@@ -835,19 +835,19 @@ exports.testAttachOnMultipleDocuments = 
     });
 
     function checkEnd() {
       if (detachEventCount != 2)
         return;
 
       assert.pass("Got all detach events");
 
-      close(window).then(done).then(null, assert.fail);
+      close(window).then(done).catch(assert.fail);
     }
-  }).then(null, assert.fail);
+  }).catch(assert.fail);
 }
 
 
 exports.testAttachWrappers = function (assert, done) {
   // Check that content script has access to wrapped values by default
   open().then(focus).then(window => {
     let document = "data:text/html;charset=utf-8,<script>var globalJSVar = true; " +
                    "                       document.getElementById = 3;</script>";
@@ -861,22 +861,22 @@ exports.testAttachWrappers = function (a
                          '  self.postMessage(!("globalJSVar" in window));' +
                          '  self.postMessage(typeof window.globalJSVar == "undefined");' +
                          '} catch(e) {' +
                          '  self.postMessage(e.message);' +
                          '}',
           onMessage: function (msg) {
             assert.equal(msg, true, "Worker has wrapped objects ("+count+")");
             if (count++ == 1)
-              close(window).then(done).then(null, assert.fail);
+              close(window).then(done).catch(assert.fail);
           }
         });
       }
     });
-  }).then(null, assert.fail);
+  }).catch(assert.fail);
 }
 
 /*
 // We do not offer unwrapped access to DOM since bug 601295 landed
 // See 660780 to track progress of unwrap feature
 exports.testAttachUnwrapped = function (assert, done) {
   // Check that content script has access to unwrapped values through unsafeWindow
   openBrowserWindow(function(window, browser) {
@@ -926,34 +926,34 @@ exports['test window focus changes activ
 
           function whenReady(tab) {
             assert.pass("activate was called on windows focus change.");
             assert.equal(tab.url, url1, 'the activated tab url is correct');
 
             return close(win2).then(function() {
               assert.pass('window 2 was closed');
               return close(win1);
-            }).then(done).then(null, assert.fail);
+            }).then(done).catch(assert.fail);
           }
         });
 
         win1.focus();
       });
     }, "data:text/html;charset=utf-8,test window focus changes active tab</br><h1>Window #2");
   }, url1);
 };
 
 exports['test ready event on new window tab'] = function(assert, done) {
   let uri = encodeURI("data:text/html;charset=utf-8,Waiting for ready event!");
 
   require("sdk/tabs").on("ready", function onReady(tab) {
     if (tab.url === uri) {
       require("sdk/tabs").removeListener("ready", onReady);
       assert.pass("ready event was emitted");
-      close(window).then(done).then(null, assert.fail);
+      close(window).then(done).catch(assert.fail);
     }
   });
 
   let window = openBrowserWindow(function(){}, uri);
 };
 
 exports['test unique tab ids'] = function(assert, done) {
   var windows = require('sdk/windows').browserWindows;
--- a/addon-sdk/source/test/test-addon-window.js
+++ b/addon-sdk/source/test/test-addon-window.js
@@ -11,12 +11,12 @@ exports.testReady = function(assert, don
   let windowIsReady = false;
 
   ready.then(function() {
     assert.equal(windowIsReady, false, 'ready promise was resolved only once');
     windowIsReady = true;
 
     loader.unload();
     done();
-  }).then(null, assert.fail);
+  }).catch(assert.fail);
 }
 
 require('sdk/test').run(exports);
--- a/addon-sdk/source/test/test-child_process.js
+++ b/addon-sdk/source/test/test-child_process.js
@@ -61,17 +61,17 @@ exports.testExecOptionsEnvironment = fun
       env: { CHILD_PROCESS_ENV_TEST: 'my-value-test' }
     }, function (err, stdout, stderr) {
       assert.equal(stderr, '', 'stderr is empty');
       assert.ok(!err, 'received `cwd` option');
       assert.ok(/my-value-test/.test(stdout),
         'receives environment option');
       done();
     });
-  }).then(null, assert.fail);
+  }).catch(assert.fail);
 };
 
 exports.testExecOptionsTimeout = function (assert, done) {
   let count = 0;
   getScript('wait').then(script => {
     let child = exec(script, { timeout: 100 }, (err, stdout, stderr) => {
       assert.equal(err.killed, true, 'error has `killed` property as true');
       assert.equal(err.code, null, 'error has `code` as null');
@@ -99,29 +99,29 @@ exports.testExecOptionsTimeout = functio
     child.on('exit', exitHandler);
     child.on('close', closeHandler);
 
     function complete () {
       child.off('exit', exitHandler);
       child.off('close', closeHandler);
       done();
     }
-  }).then(null, assert.fail);
+  }).catch(assert.fail);
 };
 
 exports.testExecFileCallbackSuccess = function (assert, done) {
   getScript('args').then(script => {
     execFile(script, ['--myargs', '-j', '-s'], { cwd: PROFILE_DIR }, function (err, stdout, stderr) {
       assert.ok(!err, 'no errors found');
       assert.equal(stderr, '', 'stderr is empty');
       // Trim output since different systems have different new line output
       assert.equal(stdout.trim(), '--myargs -j -s'.trim(), 'passes in correct arguments');
       done();
     });
-  }).then(null, assert.fail);
+  }).catch(assert.fail);
 };
 
 exports.testExecFileCallbackError = function (assert, done) {
   execFile('not-real-command', { cwd: PROFILE_DIR }, function (err, stdout, stderr) {
     assert.ok(/Executable not found/.test(err.message),
       `error '${err.message}' contains error message`);
     assert.ok(err.lineNumber >= 0, 'error contains lineNumber');
     assert.ok(/resource:\/\//.test(err.fileName), 'error contains fileName');
@@ -138,17 +138,17 @@ exports.testExecFileOptionsEnvironment =
       env: { CHILD_PROCESS_ENV_TEST: 'my-value-test' }
     }, function (err, stdout, stderr) {
       assert.equal(stderr, '', 'stderr is empty');
       assert.ok(!err, 'received `cwd` option');
       assert.ok(/my-value-test/.test(stdout),
         'receives environment option');
       done();
     });
-  }).then(null, assert.fail);
+  }).catch(assert.fail);
 };
 
 exports.testExecFileOptionsTimeout = function (assert, done) {
   let count = 0;
   getScript('wait').then(script => {
     let child = execFile(script, { timeout: 100 }, (err, stdout, stderr) => {
       assert.equal(err.killed, true, 'error has `killed` property as true');
       assert.equal(err.code, null, 'error has `code` as null');
@@ -176,17 +176,17 @@ exports.testExecFileOptionsTimeout = fun
     child.on('exit', exitHandler);
     child.on('close', closeHandler);
 
     function complete () {
       child.off('exit', exitHandler);
       child.off('close', closeHandler);
       done();
     }
-  }).then(null, assert.fail);
+  }).catch(assert.fail);
 };
 
 /**
  * Not necessary to test for both `exec` and `execFile`, but
  * it is necessary to test both when the buffer is larger
  * and smaller than buffer size used by the subprocess library (1024)
  */
 exports.testExecFileOptionsMaxBufferLargeStdOut = function (assert, done) {
@@ -199,17 +199,17 @@ exports.testExecFileOptionsMaxBufferLarg
       assert.ok(/stdout maxBuffer exceeded/.test(err.toString()),
         'error contains stdout maxBuffer exceeded message');
       assert.ok(stdout.length >= 50, 'stdout has full buffer');
       assert.equal(stderr, '', 'stderr is empty');
       if (++count === 3) complete();
     });
     stdoutChild.on('exit', exitHandler);
     stdoutChild.on('close', closeHandler);
-  }).then(null, assert.fail);
+  }).catch(assert.fail);
 
   function exitHandler (code, signal) {
     assert.equal(code, null, 'Exit code is null in exit handler');
     assert.equal(signal, 'SIGTERM', 'Signal is SIGTERM in exit handler');
     if (++count === 3) complete();
   }
 
   function closeHandler (code, signal) {
@@ -234,17 +234,17 @@ exports.testExecFileOptionsMaxBufferLarg
       assert.ok(/stderr maxBuffer exceeded/.test(err.toString()),
         'error contains stderr maxBuffer exceeded message');
       assert.ok(stderr.length >= 50, 'stderr has full buffer');
       assert.equal(stdout, '', 'stdout is empty');
       if (++count === 3) complete();
     });
     stderrChild.on('exit', exitHandler);
     stderrChild.on('close', closeHandler);
-  }).then(null, assert.fail);
+  }).catch(assert.fail);
 
   function exitHandler (code, signal) {
     assert.equal(code, null, 'Exit code is null in exit handler');
     assert.equal(signal, 'SIGTERM', 'Signal is SIGTERM in exit handler');
     if (++count === 3) complete();
   }
 
   function closeHandler (code, signal) {
@@ -275,17 +275,17 @@ exports.testExecFileOptionsMaxBufferSmal
       assert.ok(/stdout maxBuffer exceeded/.test(err.toString()),
         'error contains stdout maxBuffer exceeded message');
       assert.ok(stdout.length >= 50, 'stdout has full buffer');
       assert.equal(stderr, '', 'stderr is empty');
       if (++count === 3) complete();
     });
     stdoutChild.on('exit', exitHandler);
     stdoutChild.on('close', closeHandler);
-  }).then(null, assert.fail);
+  }).catch(assert.fail);
 
   function exitHandler (code, signal) {
     // Sometimes the buffer limit is hit before the process closes successfully
     // on both OSX/Windows
     if (code === null) {
       assert.equal(code, null, 'Exit code is null in exit handler');
       assert.equal(signal, 'SIGTERM', 'Signal is SIGTERM in exit handler');
     }
@@ -326,17 +326,17 @@ exports.testExecFileOptionsMaxBufferSmal
       assert.ok(/stderr maxBuffer exceeded/.test(err.toString()),
         'error contains stderr maxBuffer exceeded message');
       assert.ok(stderr.length >= 50, 'stderr has full buffer');
       assert.equal(stdout, '', 'stdout is empty');
       if (++count === 3) complete();
     });
     stderrChild.on('exit', exitHandler);
     stderrChild.on('close', closeHandler);
-  }).then(null, assert.fail);
+  }).catch(assert.fail);
 
   function exitHandler (code, signal) {
     // Sometimes the buffer limit is hit before the process closes successfully
     // on both OSX/Windows
     if (code === null) {
       assert.equal(code, null, 'Exit code is null in exit handler');
       assert.equal(signal, 'SIGTERM', 'Signal is SIGTERM in exit handler');
     }
@@ -372,17 +372,17 @@ exports.testChildExecFileKillSignal = fu
   getScript('wait').then(script => {
     execFile(script, {
       killSignal: 'beepbeep',
       timeout: 10
     }, function (err, stdout, stderr) {
       assert.equal(err.signal, 'beepbeep', 'correctly used custom killSignal');
       done();
     });
-  }).then(null, assert.fail);
+  }).catch(assert.fail);
 };
 
 exports.testChildProperties = function (assert, done) {
   getScript('check-env').then(script => {
     let child = spawn(script, {
       env: { CHILD_PROCESS_ENV_TEST: 'my-value-test' }
     });
 
@@ -503,17 +503,17 @@ exports.testSpawnOptions = function (ass
     cwdChild = spawn(checkPwd, { cwd: PROFILE_DIR });
 
     // Do these need to be unbound?
     envChild.stdout.on('data', data => envStdout += data);
     cwdChild.stdout.on('data', data => cwdStdout += data);
 
     envChild.on('close', envClose);
     cwdChild.on('close', cwdClose);
-  }).then(null, assert.fail);
+  }).catch(assert.fail);
 
   function envClose () {
     assert.equal(envStdout.trim(), 'my-value-test', 'spawn correctly passed in ENV');
     if (++count === 2) complete();
   }
 
   function cwdClose () {
     // Check for PROFILE_DIR in the output because
--- a/addon-sdk/source/test/test-native-loader.js
+++ b/addon-sdk/source/test/test-native-loader.js
@@ -232,17 +232,17 @@ for (let variant of variants) {
         manifest: manifest,
         isNative: true
       });
 
       let program = main(loader);
       testLoader(program, assert);
       unload(loader);
       done();
-    }).then(null, (reason) => console.error(reason));
+    }).catch((reason) => console.error(reason));
   };
 
   exports[`test require#resolve with relative, dependencies (${variant.description})`] = function(assert, done) {
     getJSON('/fixtures/native-addon-test/package.json').then(manifest => {
       let rootURI = variant.getRootURI('native-addon-test');
       let loader = Loader({
         paths: makePaths(rootURI),
         rootURI: rootURI,
@@ -260,17 +260,17 @@ for (let variant of variants) {
       assert.equal(program.require.resolve("modules/Promise.jsm"), "resource://gre/modules/Promise.jsm", "works with path lookups");
 
       // TODO bug 1050422, handle loading non JS/JSM file paths
       // assert.equal(program.require.resolve("test-assets/styles.css"), fixtureRoot + "node_modules/test-assets/styles.css",
       // "works with different file extension lookups in dependencies");
 
       unload(loader);
       done();
-    }).then(null, (reason) => console.error(reason));
+    }).catch((reason) => console.error(reason));
   };
 }
 
 before(exports, () => {
   for (let fixture of fixtures) {
     let url = `jar:${root}/fixtures/${fixture}.xpi!/`;
 
     resProto.setSubstitution(fixture, NetUtil.newURI(url));
@@ -304,17 +304,17 @@ exports['test JSM loading'] = function (
       program.isLoadedAbsolute(20),
       program.isLoadedJSAbsolute(30)
     ]).then(([path, absolute, jsabsolute]) => {
       assert.equal(path, 10, 'JSM files resolved from path work');
       assert.equal(absolute, 20, 'JSM files resolved from full resource:// work');
       assert.equal(jsabsolute, 30, 'JS files resolved from full resource:// work');
     }).then(done, console.error);
 
-  }).then(null, console.error);
+  }).catch(console.error);
 };
 
 function testLoader (program, assert) {
   // Test 'main' entries
   // no relative custom main `lib/index.js`
   assert.equal(program.customMainModule, 'custom entry file',
     'a node_module dependency correctly uses its `main` entry in manifest');
   // relative custom main `./lib/index.js`
@@ -383,12 +383,12 @@ function loadAddon (uri, map) {
       rootURI: rootURI,
       manifest: manifest,
       isNative: true,
       modules: {
         '@test/options': testOptions
       }
     });
     let program = main(loader);
-  }).then(null, console.error);
+  }).catch(console.error);
 }
 
 require('sdk/test').run(exports);
--- a/addon-sdk/source/test/test-panel.js
+++ b/addon-sdk/source/test/test-panel.js
@@ -1016,17 +1016,17 @@ exports['test panel CSS'] = function(ass
       assert.equal(div.offsetHeight, 120,
         "Panel contentStyleFile worked");
 
       assert.equal(window.getComputedStyle(div).borderTopStyle, "dashed",
         "Panel contentStyleFile with relative path worked");
 
         loader.unload();
         done();
-      }).then(null, assert.fail);
+      }).catch(assert.fail);
     }
   });
 
   panel.show();
 };
 
 exports['test panel contentScriptFile'] = function(assert, done) {
   const { merge } = require("sdk/util/object");
--- a/addon-sdk/source/test/test-promise.js
+++ b/addon-sdk/source/test/test-promise.js
@@ -112,22 +112,22 @@ exports['test error recovery with promis
     let deferred = defer();
     deferred.resolve('recovery');
     return deferred.promise;
   }).then(function(actual) {
     assert.equal(actual, 'recovery', 'recorvered via promise');
     let deferred = defer();
     deferred.reject('error');
     return deferred.promise;
-  }).then(null, function(actual) {
+  }).catch(function(actual) {
     assert.equal(actual, 'error', 'rejected via promise');
     let deferred = defer();
     deferred.reject('end');
     return deferred.promise;
-  }).then(null, function(actual) {
+  }).catch(function(actual) {
     assert.equal(actual, 'end', 'rejeced via promise');
     done();
   });
 
   deferred.reject('reason');
 };
 
 exports['test propagation'] = function(assert, done) {
@@ -145,27 +145,27 @@ exports['test propagation'] = function(a
 
 exports['test chaining'] = function(assert, done) {
   let boom = Error('boom'), brax = Error('braxXXx');
   let deferred = defer();
 
   deferred.promise.then().then().then(function(actual) {
     assert.equal(actual, 2, 'value propagates unchanged');
     return actual + 2;
-  }).then(null, function(reason) {
+  }).catch(function(reason) {
     assert.fail('should not reject');
   }).then(function(actual) {
     assert.equal(actual, 4, 'value propagates through if not handled');
     throw boom;
   }).then(function(actual) {
     assert.fail('exception must reject promise');
-  }).then().then(null, function(actual) {
+  }).then().catch(function(actual) {
     assert.equal(actual, boom, 'reason propagates unchanged');
     throw brax;
-  }).then().then(null, function(actual) {
+  }).then().catch(function(actual) {
     assert.equal(actual, brax, 'reason changed becase of exception');
     return 'recovery';
   }).then(function(actual) {
     assert.equal(actual, 'recovery', 'recovered from error');
     done();
   });
 
   deferred.resolve(2);
@@ -236,17 +236,17 @@ exports['test promised error handling'] 
 exports['test errors in promise resolution handlers are propagated'] = function(assert, done) {
   var expected = Error('Boom');
   var { promise, resolve } = defer();
 
   promise.then(function() {
     throw expected;
   }).then(function() {
     return undefined;
-  }).then(null, function(actual) {
+  }).catch(function(actual) {
     assert.equal(actual, expected, 'rejected as expected');
   }).then(done, assert.fail);
 
   resolve({});
 };
 
 exports['test return promise form promised'] = function(assert, done) {
   let f = promised(function() {
@@ -440,17 +440,17 @@ function testEnvironment ({all, resolve,
   all([resolve(5), resolve(10), 925]).then(val => {
     assert.equal(val[0], 5, 'promise#all works ' + type);
     assert.equal(val[1], 10, 'promise#all works ' + type);
     assert.equal(val[2], 925, 'promise#all works ' + type);
     return resolve(1000);
   }).then(value => {
     assert.equal(value, 1000, 'promise#resolve works ' + type);
     return reject('testing reject');
-  }).then(null, reason => {
+  }).catch(reason => {
     assert.equal(reason, 'testing reject', 'promise#reject works ' + type);
     let deferred = defer();
     setTimeout(() => deferred.resolve('\\m/'), 10);
     return deferred.promise;
   }).then(value => {
     assert.equal(value, '\\m/', 'promise#defer works ' + type);
     return promised(x => x * x)(5);
   }).then(value => {
--- a/addon-sdk/source/test/test-ui-toggle-button.js
+++ b/addon-sdk/source/test/test-ui-toggle-button.js
@@ -1200,17 +1200,17 @@ exports['test button checked'] = functio
           'clicked:foo', 'changed:foo:false', 'clicked:foo', 'changed:foo:true'
         ],
         'button change events works');
 
       close(window).
         then(loader.unload).
         then(done, assert.fail);
     })
-  }).then(null, assert.fail);
+  }).catch(assert.fail);
 }
 
 exports['test button is checked on window level'] = function(assert, done) {
   let loader = Loader(module);
   let { ToggleButton } = loader.require('sdk/ui');
   let { browserWindows } = loader.require('sdk/windows');
   let tabs = loader.require('sdk/tabs');
 
@@ -1262,17 +1262,17 @@ exports['test button is checked on windo
           'window state, checked is `false`.');
 
         tab.close(()=> {
           close(window).
             then(loader.unload).
             then(done, assert.fail);
         })
       }).
-      then(null, assert.fail);
+      catch(assert.fail);
     }
   });
 
 };
 
 exports['test button click do not messing up states'] = function(assert) {
   let loader = Loader(module);
   let { ToggleButton } = loader.require('sdk/ui');
--- a/addon-sdk/source/test/test-window-utils-private-browsing.js
+++ b/addon-sdk/source/test/test-window-utils-private-browsing.js
@@ -74,17 +74,17 @@ exports.testWindowTrackerIgnoresPrivateW
 
     return close(window).then(function() {
       return makeEmptyBrowserWindow().then(function(window) {
         myNonPrivateWindow = window;
         assert.pass('opened new window');
         return close(window);
       });
     });
-  }).then(null, assert.fail);
+  }).catch(assert.fail);
 };
 
 // Test setting activeWIndow and onFocus for private windows
 exports.testSettingActiveWindowDoesNotIgnorePrivateWindow = function(assert, done) {
   let browserWindow = WM.getMostRecentWindow("navigator:browser");
 
   assert.equal(windowUtils.activeBrowserWindow, browserWindow,
                "Browser window is the active browser window.");
@@ -137,17 +137,17 @@ exports.testSettingActiveWindowDoesNotIg
     }).then(_ => {
       assert.strictEqual(windowUtils.activeBrowserWindow, browserWindow,
                          "Correct active browser window when pb mode is supported [4]");
       assert.strictEqual(windowUtils.activeWindow, browserWindow,
                          "Correct active window when pb mode is supported [4]");
 
       return close(window);
     })
-  }).then(done).then(null, assert.fail);
+  }).then(done).catch(assert.fail);
 };
 
 exports.testActiveWindowDoesNotIgnorePrivateWindow = function(assert, done) {
   // make a new private window
   makeEmptyBrowserWindow({
     private: true
   }).then(function(window) {
     // PWPB case
@@ -177,17 +177,17 @@ exports.testActiveWindowDoesNotIgnorePri
                    "active window is not private");
       assert.equal(isPrivate(windowUtils.activeBrowserWindow), false,
                    "active browser window is not private");
       assert.equal(isWindowPrivate(window), false, "window is not private");
       assert.equal(isPrivate(window), false, "window is not private");
     }
 
     return close(window);
-  }).then(done).then(null, assert.fail);
+  }).then(done).catch(assert.fail);
 }
 
 exports.testWindowIteratorIgnoresPrivateWindows = function(assert, done) {
   // make a new private window
   makeEmptyBrowserWindow({
     private: true
   }).then(function(window) {
     // PWPB case
@@ -199,12 +199,12 @@ exports.testWindowIteratorIgnoresPrivate
     // Global case
     else {
       assert.equal(isWindowPrivate(window), false, "window is not private");
       assert.ok(toArray(windowUtils.windowIterator()).indexOf(window) > -1,
                 "window is in windowIterator()");
     }
 
     return close(window);
-  }).then(done).then(null, assert.fail);
+  }).then(done).catch(assert.fail);
 };
 
 require("sdk/test").run(exports);
--- a/browser/base/content/aboutaccounts/aboutaccounts.js
+++ b/browser/base/content/aboutaccounts/aboutaccounts.js
@@ -440,31 +440,31 @@ function migrateToDevEdition(urlParams) 
     let accountData = JSON.parse(text).accountData;
     updateDisplayedEmail(accountData);
     return fxAccounts.setSignedInUser(accountData);
   }).then(() => {
     return fxAccounts.promiseAccountsForceSigninURI().then(url => {
       show("remote");
       wrapper.init(url, urlParams);
     });
-  }).then(null, error => {
+  }).catch(error => {
     log("Failed to migrate FX Account: " + error);
     show("stage", "intro");
     // load the remote frame in the background
     fxAccounts.promiseAccountsSignUpURI().then(uri => {
       wrapper.init(uri, urlParams)
     }).catch(e => {
       console.log("Failed to load signup page", e);
       setErrorPage("configError");
     });
   }).then(() => {
     // Reset the pref after migration.
     Services.prefs.setBoolPref("identity.fxaccounts.migrateToDevEdition", false);
     return true;
-  }).then(null, err => {
+  }).catch(err => {
     Cu.reportError("Failed to reset the migrateToDevEdition pref: " + err);
     return false;
   });
 }
 
 // Helper function that returns the path of the default profile on disk. Will be
 // overridden in tests.
 function getDefaultProfilePath() {
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1669,17 +1669,17 @@ var gBrowserInit = {
       Cu.reportError("Could not end startup crash tracking: " + ex);
     }
 
     // Delay this a minute into the idle time because there's no rush.
     requestIdleCallback(() => {
       this.gmpInstallManager = new GMPInstallManager();
       // We don't really care about the results, if someone is interested they
       // can check the log.
-      this.gmpInstallManager.simpleCheckAndInstall().then(null, () => {});
+      this.gmpInstallManager.simpleCheckAndInstall().catch(() => {});
     }, {timeout: 1000 * 60});
 
     SessionStore.promiseInitialized.then(() => {
       // Bail out if the window has been closed in the meantime.
       if (window.closed) {
         return;
       }
 
--- a/browser/base/content/sanitizeDialog.js
+++ b/browser/base/content/sanitizeDialog.js
@@ -98,19 +98,19 @@ var gSanitizePromptDialog = {
     let docElt = document.documentElement;
     let acceptButton = docElt.getButton("accept");
     acceptButton.disabled = true;
     acceptButton.setAttribute("label",
                               this.bundleBrowser.getString("sanitizeButtonClearing"));
     docElt.getButton("cancel").disabled = true;
 
     try {
-      s.sanitize().then(null, Components.utils.reportError)
+      s.sanitize().catch(Components.utils.reportError)
                   .then(() => window.close())
-                  .then(null, Components.utils.reportError);
+                  .catch(Components.utils.reportError);
       return false;
     } catch (er) {
       Components.utils.reportError("Exception during sanitize: " + er);
       return true; // We *do* want to close immediately on error.
     }
   },
 
   /**
--- a/browser/components/customizableui/CustomizeMode.jsm
+++ b/browser/components/customizableui/CustomizeMode.jsm
@@ -421,17 +421,17 @@ CustomizeMode.prototype = {
           this.panelUIContents.setAttribute("showoutline", "true");
         }
         delete this._enableOutlinesTimeout;
       }, 0);
 
       if (!this._wantToBeInCustomizeMode) {
         this.exit();
       }
-    })().then(null, e => {
+    })().catch(e => {
       log.error("Error entering customize mode", e);
       // We should ensure this has been called, and calling it again doesn't hurt:
       window.PanelUI.endBatchUpdate();
       this._handler.isEnteringCustomizeMode = false;
       // Exit customize mode to ensure proper clean-up when entering failed.
       this.exit();
     });
   },
@@ -591,17 +591,17 @@ CustomizeMode.prototype = {
       this._transitioning = false;
       this._handler.isExitingCustomizeMode = false;
       CustomizableUI.dispatchToolboxEvent("aftercustomization", {}, window);
       CustomizableUI.notifyEndCustomizing(window);
 
       if (this._wantToBeInCustomizeMode) {
         this.enter();
       }
-    })().then(null, e => {
+    })().catch(e => {
       log.error("Error exiting customize mode", e);
       if (!gPhotonStructure) {
         // We should ensure this has been called, and calling it again doesn't hurt:
         window.PanelUI.endBatchUpdate();
       }
       this._handler.isExitingCustomizeMode = false;
     });
   },
@@ -861,17 +861,17 @@ CustomizeMode.prototype = {
         } else if (provider == CustomizableUI.PROVIDER_SPECIAL) {
           this.visiblePalette.removeChild(paletteChild);
         }
 
         paletteChild = nextChild;
       }
       this.visiblePalette.hidden = false;
       this.window.gNavToolbox.palette = this._stowedPalette;
-    })().then(null, log.error);
+    })().catch(log.error);
   },
 
   isCustomizableItem(aNode) {
     return aNode.localName == "toolbarbutton" ||
            aNode.localName == "toolbaritem" ||
            aNode.localName == "toolbarseparator" ||
            aNode.localName == "toolbarspring" ||
            aNode.localName == "toolbarspacer";
@@ -1067,17 +1067,17 @@ CustomizeMode.prototype = {
     let target = CustomizableUI.getCustomizeTargetForArea(aArea, this.window);
     if (!target || this.areas.has(target)) {
       return null;
     }
 
     this._addDragHandlers(target);
     for (let child of target.children) {
       if (this.isCustomizableItem(child) && !this.isWrappedToolbarItem(child)) {
-        await this.deferredWrapToolbarItem(child, CustomizableUI.getPlaceForItem(child)).then(null, log.error);
+        await this.deferredWrapToolbarItem(child, CustomizableUI.getPlaceForItem(child)).catch(log.error);
       }
     }
     this.areas.add(target);
     return target;
   },
 
   _wrapToolbarItemSync(aArea) {
     let target = CustomizableUI.getCustomizeTargetForArea(aArea, this.window);
@@ -1144,17 +1144,17 @@ CustomizeMode.prototype = {
         for (let toolbarItem of target.children) {
           if (this.isWrappedToolbarItem(toolbarItem)) {
             await this.deferredUnwrapToolbarItem(toolbarItem);
           }
         }
         this._removeDragHandlers(target);
       }
       this.areas.clear();
-    })().then(null, log.error);
+    })().catch(log.error);
   },
 
   _removeExtraToolbarsIfEmpty() {
     let toolbox = this.window.gNavToolbox;
     for (let child of toolbox.children) {
       if (child.hasAttribute("customindex")) {
         let placements = CustomizableUI.getWidgetIdsInArea(child.id);
         if (!placements.length) {
@@ -1200,17 +1200,17 @@ CustomizeMode.prototype = {
       this._updateResetButton();
       this._updateUndoResetButton();
       this._updateEmptyPaletteNotice();
       this._showPanelCustomizationPlaceholders();
       this.resetting = false;
       if (!this._wantToBeInCustomizeMode) {
         this.exit();
       }
-    })().then(null, log.error);
+    })().catch(log.error);
   },
 
   undoReset() {
     this.resetting = true;
 
     return (async () => {
       this._removePanelCustomizationPlaceholders();
       await this.depopulatePalette();
@@ -1224,17 +1224,17 @@ CustomizeMode.prototype = {
       this.populatePalette();
 
       this.persistCurrentSets(true);
 
       this._updateResetButton();
       this._updateUndoResetButton();
       this._updateEmptyPaletteNotice();
       this.resetting = false;
-    })().then(null, log.error);
+    })().catch(log.error);
   },
 
   _onToolbarVisibilityChange(aEvent) {
     let toolbar = aEvent.target;
     if (aEvent.detail.visible && toolbar.getAttribute("customizable") == "true") {
       toolbar.setAttribute("customizing", "true");
     } else {
       toolbar.removeAttribute("customizing");
--- a/browser/components/customizableui/content/panelUI.js
+++ b/browser/components/customizableui/content/panelUI.js
@@ -439,17 +439,17 @@ const PanelUI = {
           CustomizableUI.registerMenuPanel(this.contents, CustomizableUI.AREA_PANEL);
         } finally {
           this.endBatchUpdate();
         }
       }
       this._updateQuitTooltip();
       this.panel.hidden = false;
       this._isReady = true;
-    })().then(null, Cu.reportError);
+    })().catch(Cu.reportError);
 
     return this._readyPromise;
   },
 
   /**
    * Switch the panel to the main view if it's not already
    * in that view.
    */
--- a/browser/components/downloads/DownloadsCommon.jsm
+++ b/browser/components/downloads/DownloadsCommon.jsm
@@ -462,17 +462,17 @@ this.DownloadsCommon = {
         aFile.launch();
       } catch (ex) {
         // If launch fails, try sending it through the system's external "file:"
         // URL handler.
         Cc["@mozilla.org/uriloader/external-protocol-service;1"]
           .getService(Ci.nsIExternalProtocolService)
           .loadUrl(NetUtil.newURI(aFile));
       }
-    }).then(null, Cu.reportError);
+    }).catch(Cu.reportError);
   },
 
   /**
    * Show a downloaded file in the system file manager.
    *
    * @param aFile
    *        a downloaded file.
    */
@@ -669,17 +669,17 @@ function DownloadsDataCtor(aPrivate) {
 DownloadsDataCtor.prototype = {
   /**
    * Starts receiving events for current downloads.
    */
   initializeDataLink() {
     if (!this._dataLinkInitialized) {
       let promiseList = Downloads.getList(this._isPrivate ? Downloads.PRIVATE
                                                           : Downloads.PUBLIC);
-      promiseList.then(list => list.addView(this)).then(null, Cu.reportError);
+      promiseList.then(list => list.addView(this)).catch(Cu.reportError);
       this._dataLinkInitialized = true;
     }
   },
   _dataLinkInitialized: false,
 
   /**
    * Iterator for all the available Download objects. This is empty until the
    * data has been loaded using the JavaScript API for downloads.
@@ -703,17 +703,17 @@ DownloadsDataCtor.prototype = {
 
   /**
    * Asks the back-end to remove finished downloads from the list.
    */
   removeFinished() {
     let promiseList = Downloads.getList(this._isPrivate ? Downloads.PRIVATE
                                                         : Downloads.PUBLIC);
     promiseList.then(list => list.removeFinished())
-               .then(null, Cu.reportError);
+               .catch(Cu.reportError);
     let indicatorData = this._isPrivate ? PrivateDownloadsIndicatorData
                                         : DownloadsIndicatorData;
     indicatorData.attention = DownloadsCommon.ATTENTION_NONE;
   },
 
   // Integration with the asynchronous Downloads back-end
 
   onDownloadAdded(download) {
--- a/browser/components/downloads/DownloadsTaskbar.jsm
+++ b/browser/components/downloads/DownloadsTaskbar.jsm
@@ -105,17 +105,17 @@ this.DownloadsTaskbar = {
       Downloads.getSummary(Downloads.ALL).then(summary => {
         // In case the method is re-entered, we simply ignore redundant
         // invocations of the callback, instead of keeping separate state.
         if (this._summary) {
           return undefined;
         }
         this._summary = summary;
         return this._summary.addView(this);
-      }).then(null, Cu.reportError);
+      }).catch(Cu.reportError);
     }
   },
 
   /**
    * On Windows, attaches the taskbar indicator to the specified browser window.
    */
   _attachIndicator(aWindow) {
     // Activate the indicator on the specified window.
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -1176,17 +1176,17 @@ BrowserGlue.prototype = {
       if (willPrompt) {
         Services.tm.dispatchToMainThread(function() {
           DefaultBrowserCheck.prompt(RecentWindow.getMostRecentBrowserWindow());
         });
       }
     }
 
     // Let's load the contextual identities.
-    Services.tm.mainThread.idleDispatch(() => {
+    Services.tm.idleDispatchToMainThread(() => {
       ContextualIdentityService.load();
     });
 
     this._sanitizer.onStartup();
     E10SAccessibilityCheck.onWindowsRestored();
   },
 
   _createExtraDefaultProfile() {
@@ -1211,17 +1211,17 @@ BrowserGlue.prototype = {
       }
       if (newProfile) {
         // We don't want a default profile with Developer Edition settings, an
         // empty profile directory will do. The profile service of the other
         // Firefox will populate it with its own stuff.
         let newProfilePath = newProfile.rootDir.path;
         OS.File.removeDir(newProfilePath).then(() => {
           return OS.File.makeDir(newProfilePath);
-        }).then(null, e => {
+        }).catch(e => {
           Cu.reportError("Could not empty profile 'default': " + e);
         });
       }
     }
   },
 
   _onQuitRequest: function BG__onQuitRequest(aCancelQuit, aQuitType) {
     // If user has already dismissed quit request, then do nothing
--- a/browser/components/places/content/browserPlacesViews.js
+++ b/browser/components/places/content/browserPlacesViews.js
@@ -1637,17 +1637,17 @@ PlacesToolbar.prototype = {
   },
 
   _onDrop: function PT__onDrop(aEvent) {
     PlacesControllerDragHelper.currentDropTarget = aEvent.target;
 
     let dropPoint = this._getDropPoint(aEvent);
     if (dropPoint && dropPoint.ip) {
       PlacesControllerDragHelper.onDrop(dropPoint.ip, aEvent.dataTransfer)
-                                .then(null, Components.utils.reportError);
+                                .catch(Components.utils.reportError);
       aEvent.preventDefault();
     }
 
     this._cleanupDragDetails();
     aEvent.stopPropagation();
   },
 
   _onDragExit: function PT__onDragExit(aEvent) {
--- a/browser/components/places/content/controller.js
+++ b/browser/components/places/content/controller.js
@@ -212,40 +212,40 @@ PlacesController.prototype = {
 
   doCommand: function PC_doCommand(aCommand) {
     switch (aCommand) {
     case "cmd_undo":
       if (!PlacesUIUtils.useAsyncTransactions) {
         PlacesUtils.transactionManager.undoTransaction();
         return;
       }
-      PlacesTransactions.undo().then(null, Components.utils.reportError);
+      PlacesTransactions.undo().catch(Components.utils.reportError);
       break;
     case "cmd_redo":
       if (!PlacesUIUtils.useAsyncTransactions) {
         PlacesUtils.transactionManager.redoTransaction();
         return;
       }
-      PlacesTransactions.redo().then(null, Components.utils.reportError);
+      PlacesTransactions.redo().catch(Components.utils.reportError);
       break;
     case "cmd_cut":
     case "placesCmd_cut":
       this.cut();
       break;
     case "cmd_copy":
     case "placesCmd_copy":
       this.copy();
       break;
     case "cmd_paste":
     case "placesCmd_paste":
-      this.paste().then(null, Components.utils.reportError);
+      this.paste().catch(Components.utils.reportError);
       break;
     case "cmd_delete":
     case "placesCmd_delete":
-      this.remove("Remove Selection").then(null, Components.utils.reportError);
+      this.remove("Remove Selection").catch(Components.utils.reportError);
       break;
     case "placesCmd_deleteDataHost":
       var host;
       if (PlacesUtils.nodeIsHost(this._view.selectedNode)) {
         var queries = this._view.selectedNode.getQueries();
         host = queries[0].domain;
       } else
         host = NetUtil.newURI(this._view.selectedNode.uri).host;
@@ -281,17 +281,17 @@ PlacesController.prototype = {
       break;
     case "placesCmd_moveBookmarks":
       this.moveSelectedBookmarks();
       break;
     case "placesCmd_reload":
       this.reloadSelectedLivemark();
       break;
     case "placesCmd_sortBy:name":
-      this.sortFolderByName().then(null, Components.utils.reportError);
+      this.sortFolderByName().catch(Components.utils.reportError);
       break;
     case "placesCmd_createBookmark":
       let node = this._view.selectedNode;
       PlacesUIUtils.showBookmarkDialog({ action: "add",
                                          type: "bookmark",
                                          hiddenRows: [ "description",
                                                         "keyword",
                                                         "location",
--- a/browser/components/places/content/menu.xml
+++ b/browser/components/places/content/menu.xml
@@ -358,17 +358,17 @@
       ]]></handler>
 
       <handler event="drop"><![CDATA[
         PlacesControllerDragHelper.currentDropTarget = event.target;
 
         let dropPoint = this._getDropPoint(event);
         if (dropPoint && dropPoint.ip) {
           PlacesControllerDragHelper.onDrop(dropPoint.ip, event.dataTransfer)
-                                    .then(null, Components.utils.reportError);
+                                    .catch(Components.utils.reportError);
           event.preventDefault();
         }
 
         this._cleanupDragDetails();
         event.stopPropagation();
       ]]></handler>
 
       <handler event="dragover"><![CDATA[
--- a/browser/components/places/content/moveBookmarks.js
+++ b/browser/components/places/content/moveBookmarks.js
@@ -49,17 +49,17 @@ var gMoveBookmarksDialog = {
       let newParentGuid = await PlacesUtils.promiseItemGuid(selectedFolderId);
       for (let node of this._nodes) {
         // Nothing to do if the node is already under the selected folder.
         if (node.parent.itemId == selectedFolderId)
           continue;
         await PlacesTransactions.Move({ guid: node.bookmarkGuid,
                                         newParentGuid }).transact();
       }
-    }).then(null, Components.utils.reportError);
+    }).catch(Components.utils.reportError);
   },
 
   newFolder: function MBD_newFolder() {
     // The command is disabled when the tree is not focused
     this.foldersTree.focus();
     goDoCommand("placesCmd_new:folder");
   }
 };
--- a/browser/components/places/content/places.js
+++ b/browser/components/places/content/places.js
@@ -369,17 +369,17 @@ var PlacesOrganizer = {
    * Open a file-picker and import the selected file into the bookmarks store
    */
   importFromFile: function PO_importFromFile() {
     let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
     let fpCallback = function fpCallback_done(aResult) {
       if (aResult != Ci.nsIFilePicker.returnCancel && fp.fileURL) {
         Components.utils.import("resource://gre/modules/BookmarkHTMLUtils.jsm");
         BookmarkHTMLUtils.importFromURL(fp.fileURL.spec, false)
-                         .then(null, Components.utils.reportError);
+                         .catch(Components.utils.reportError);
       }
     };
 
     fp.init(window, PlacesUIUtils.getString("SelectImport"),
             Ci.nsIFilePicker.modeOpen);
     fp.appendFilters(Ci.nsIFilePicker.filterHTML);
     fp.open(fpCallback);
   },
@@ -388,17 +388,17 @@ var PlacesOrganizer = {
    * Allows simple exporting of bookmarks.
    */
   exportBookmarks: function PO_exportBookmarks() {
     let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
     let fpCallback = function fpCallback_done(aResult) {
       if (aResult != Ci.nsIFilePicker.returnCancel) {
         Components.utils.import("resource://gre/modules/BookmarkHTMLUtils.jsm");
         BookmarkHTMLUtils.exportToFile(fp.file.path)
-                         .then(null, Components.utils.reportError);
+                         .catch(Components.utils.reportError);
       }
     };
 
     fp.init(window, PlacesUIUtils.getString("EnterExport"),
             Ci.nsIFilePicker.modeSave);
     fp.appendFilters(Ci.nsIFilePicker.filterHTML);
     fp.defaultString = "bookmarks.html";
     fp.open(fpCallback);
--- a/browser/components/places/content/treeView.js
+++ b/browser/components/places/content/treeView.js
@@ -1352,17 +1352,17 @@ PlacesTreeView.prototype = {
 
   drop: function PTV_drop(aRow, aOrientation, aDataTransfer) {
     // We are responsible for translating the |index| and |orientation|
     // parameters into a container id and index within the container,
     // since this information is specific to the tree view.
     let ip = this._getInsertionPoint(aRow, aOrientation);
     if (ip) {
       PlacesControllerDragHelper.onDrop(ip, aDataTransfer)
-                                .then(null, Components.utils.reportError);
+                                .catch(Components.utils.reportError);
     }
 
     PlacesControllerDragHelper.currentDropTarget = null;
   },
 
   getParentIndex: function PTV_getParentIndex(aRow) {
     let [, parentRow] = this._getParentByChildRow(aRow);
     return parentRow;
--- a/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir_c.js
+++ b/browser/components/privatebrowsing/test/browser/browser_privatebrowsing_downloadLastDir_c.js
@@ -73,17 +73,17 @@ function test() {
          "LastDir should be the expected last dir");
       // gDownloadLastDir should be usable outside of private windows
       is(gDownloadLastDir.file.path, aGlobalLastDir.path,
          "gDownloadLastDir should be the expected global last dir");
 
       gDownloadLastDir.cleanupPrivateFile();
       aWin.close();
       aCallback();
-    }).then(null, function() { ok(false); });
+    }).catch(function() { ok(false); });
   }
 
   testOnWindow(false, function(win, downloadDir) {
     testDownloadDir(win, downloadDir, file1, tmpDir, dir1, dir1, function() {
       testOnWindow(true, function(win1, downloadDir1) {
         testDownloadDir(win1, downloadDir1, file2, dir1, dir1, dir2, function() {
           testOnWindow(false, function(win2, downloadDir2) {
             testDownloadDir(win2, downloadDir2, file3, dir1, dir3, dir3, finish);
--- a/browser/components/tests/startupRecorder.js
+++ b/browser/components/tests/startupRecorder.js
@@ -63,19 +63,20 @@ startupRecorder.prototype = {
       for (let t of topics)
         Services.obs.addObserver(this, t);
       return;
     }
 
     Services.obs.removeObserver(this, topic);
 
     if (topic == "sessionstore-windows-restored") {
-      // We use idleDispatch here to record the set of loaded scripts after we
-      // are fully done with startup and ready to react to user events.
-      Services.tm.mainThread.idleDispatch(
+      // We use idleDispatchToMainThread here to record the set of
+      // loaded scripts after we are fully done with startup and ready
+      // to react to user events.
+      Services.tm.idleDispatchToMainThread(
         this.record.bind(this, "before handling user events"));
     } else {
       const topicsToNames = {
         "profile-do-change": "before profile selection",
         "toplevel-window-ready": "before opening first browser window",
       };
       topicsToNames[firstPaintNotification] = "before first paint";
       this.record(topicsToNames[topic]);
--- a/browser/components/uitour/UITour.jsm
+++ b/browser/components/uitour/UITour.jsm
@@ -648,17 +648,17 @@ this.UITour = {
             let onPopupShown = () => {
               searchbar.textbox.popup.removeEventListener("popupshown", onPopupShown);
               this.sendPageCallback(messageManager, data.callbackID);
             };
 
             searchbar.textbox.popup.addEventListener("popupshown", onPopupShown);
             searchbar.openSuggestionsPanel();
           }
-        }).then(null, Cu.reportError);
+        }).catch(Cu.reportError);
         break;
       }
 
       case "ping": {
         if (typeof data.callbackID == "string")
           this.sendPageCallback(messageManager, data.callbackID);
         break;
       }
--- a/browser/components/uitour/test/browser_UITour.js
+++ b/browser/components/uitour/test/browser_UITour.js
@@ -154,17 +154,17 @@ var tests = [
         gContentAPI.showHighlight("appMenu");
         waitForElementToBeVisible(highlight, function() {
           isnot(PanelUI.panel.state, "closed",
                 "Panel should remain open since UITour didn't open it in the first place");
           gContentAPI.hideMenu("appMenu");
           done();
         }, "Highlight should move to the appMenu button");
       }, "Highlight should be shown after showHighlight() for fixed panel items");
-    }).then(null, Components.utils.reportError);
+    }).catch(Components.utils.reportError);
   },
   function test_highlight_effect(done) {
     function waitForHighlightWithEffect(highlightEl, effect, next, error) {
       return waitForCondition(() => highlightEl.getAttribute("active") == effect,
                               next,
                               error);
     }
     function checkDefaultEffect() {
--- a/browser/components/uitour/test/browser_UITour2.js
+++ b/browser/components/uitour/test/browser_UITour2.js
@@ -58,17 +58,17 @@ var tests = [
                 ok(!PanelUI.panel.hasAttribute("noautohide"), "@noautohide on the menu panel should have been cleaned up on close");
                 done();
               });
               gContentAPI.hideMenu("appMenu");
             }, "Info should move to the appMenu button");
           });
         }, "Info should be shown after showInfo() for fixed menu panel items");
       });
-    }).then(null, Components.utils.reportError);
+    }).catch(Components.utils.reportError);
   },
   taskify(async function test_bookmarks_menu() {
     let bookmarksMenuButton = document.getElementById("bookmarks-menu-button");
 
     is(bookmarksMenuButton.open, false, "Menu should initially be closed");
     gContentAPI.showMenu("bookmarks");
 
     await waitForConditionPromise(() => {
--- a/browser/experiments/test/addons/experiment-racybranch/bootstrap.js
+++ b/browser/experiments/test/addons/experiment-racybranch/bootstrap.js
@@ -21,14 +21,14 @@ function realstartup() {
   let experiment = experiments._getActiveExperiment();
   if (experiment.branch) {
     Cu.reportError("Found pre-existing branch: " + experiment.branch);
     return;
   }
 
   let branch = "racy-set";
   experiments.setExperimentBranch(experiment.id, branch)
-    .then(null, Cu.reportError);
+    .catch(Cu.reportError);
 }
 
 function shutdown() { }
 function install() { }
 function uninstall() { }
--- a/browser/modules/AboutHome.jsm
+++ b/browser/modules/AboutHome.jsm
@@ -182,14 +182,14 @@ var AboutHome = {
       }
 
       if (target && target.messageManager) {
         target.messageManager.sendAsyncMessage("AboutHome:Update", data);
       } else {
         let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
         mm.broadcastAsyncMessage("AboutHome:Update", data);
       }
-    }).then(null, function onError(x) {
+    }).catch(function onError(x) {
       Cu.reportError("Error in AboutHome.sendAboutHomeData: " + x);
     });
   },
 
 };
--- a/browser/themes/linux/browser.css
+++ b/browser/themes/linux/browser.css
@@ -688,43 +688,47 @@ html|span.ac-tag {
 
 html|span.ac-emphasize-text-title,
 html|span.ac-emphasize-text-tag,
 html|span.ac-emphasize-text-url {
   font-weight: 600;
 }
 
 .ac-type-icon[type=bookmark] {
-  list-style-image: url("chrome://browser/skin/urlbar-star.svg#star");
+  list-style-image: url("chrome://browser/skin/bookmark.svg");
+  -moz-context-properties: fill;
+  fill: #b2b2b2;
 }
 
 .ac-type-icon[type=bookmark][selected][current] {
-  list-style-image: url("chrome://browser/skin/urlbar-star.svg#star-inverted");
+  fill: white;
 }
 
 .ac-type-icon[type=keyword],
 .ac-site-icon[type=searchengine] {
   list-style-image: url(chrome://global/skin/icons/autocomplete-search.svg);
   -moz-context-properties: fill;
   fill: GrayText;
 }
 
 .ac-type-icon[type=keyword][selected],
 .ac-site-icon[type=searchengine][selected] {
   fill: highlighttext;
 }
 
 .ac-type-icon[type=switchtab],
 .ac-type-icon[type=remotetab] {
-  list-style-image: url("chrome://browser/skin/urlbar-tab.svg#tab");
+  list-style-image: url("chrome://browser/skin/urlbar-tab.svg");
+  -moz-context-properties: fill;
+  fill: #b2b2b2;
 }
 
 .ac-type-icon[type=switchtab][selected],
 .ac-type-icon[type=remotetab][selected] {
-  list-style-image: url("chrome://browser/skin/urlbar-tab.svg#tab-inverted");
+  fill: white;
 }
 
 .autocomplete-treebody::-moz-tree-cell-text(treecolAutoCompleteComment) {
   color: GrayText;
 }
 
 .autocomplete-treebody::-moz-tree-cell-text(suggesthint, treecolAutoCompleteComment),
 .autocomplete-treebody::-moz-tree-cell-text(suggestfirst, treecolAutoCompleteComment) {
--- a/browser/themes/osx/browser.css
+++ b/browser/themes/osx/browser.css
@@ -846,43 +846,47 @@ html|span.ac-tag {
 
 html|span.ac-emphasize-text-title,
 html|span.ac-emphasize-text-tag,
 html|span.ac-emphasize-text-url {
   font-weight: 600;
 }
 
 .ac-type-icon[type=bookmark] {
-  list-style-image: url("chrome://browser/skin/urlbar-star.svg#star");
+  list-style-image: url("chrome://browser/skin/bookmark.svg");
+  -moz-context-properties: fill;
+  fill: #b2b2b2;
 }
 
 .ac-type-icon[type=bookmark][selected][current] {
-  list-style-image: url("chrome://browser/skin/urlbar-star.svg#star-inverted");
+  fill: white;
 }
 
 .ac-type-icon[type=keyword],
 .ac-site-icon[type=searchengine] {
   list-style-image: url(chrome://global/skin/icons/autocomplete-search.svg);
   -moz-context-properties: fill;
   fill: GrayText;
 }
 
 .ac-type-icon[type=keyword][selected],
 .ac-site-icon[type=searchengine][selected] {
   fill: highlighttext;
 }
 
 .ac-type-icon[type=switchtab],
 .ac-type-icon[type=remotetab] {
-  list-style-image: url("chrome://browser/skin/urlbar-tab.svg#tab");
+  list-style-image: url("chrome://browser/skin/urlbar-tab.svg");
+  -moz-context-properties: fill;
+  fill: #b2b2b2;
 }
 
 .ac-type-icon[type=switchtab][selected],
 .ac-type-icon[type=remotetab][selected] {
-  list-style-image: url("chrome://browser/skin/urlbar-tab.svg#tab-inverted");
+  fill: white;
 }
 
 .autocomplete-treebody::-moz-tree-cell-text(treecolAutoCompleteComment) {
   color: GrayText;
 }
 
 .autocomplete-treebody::-moz-tree-cell-text(suggesthint, treecolAutoCompleteComment),
 .autocomplete-treebody::-moz-tree-cell-text(suggestfirst, treecolAutoCompleteComment)
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -213,10 +213,9 @@
   skin/classic/browser/privatebrowsing/aboutPrivateBrowsing.css (../shared/privatebrowsing/aboutPrivateBrowsing.css)
   skin/classic/browser/privatebrowsing/favicon.svg             (../shared/privatebrowsing/favicon.svg)
   skin/classic/browser/privatebrowsing/private-browsing.svg    (../shared/privatebrowsing/private-browsing.svg)
   skin/classic/browser/privatebrowsing/tracking-protection-off.svg (../shared/privatebrowsing/tracking-protection-off.svg)
   skin/classic/browser/privatebrowsing/tracking-protection.svg (../shared/privatebrowsing/tracking-protection.svg)
   skin/classic/browser/compacttheme/loading-inverted.png (../shared/compacttheme/loading-inverted.png)
   skin/classic/browser/compacttheme/loading-inverted@2x.png (../shared/compacttheme/loading-inverted@2x.png)
   skin/classic/browser/compacttheme/urlbar-history-dropmarker.svg (../shared/compacttheme/urlbar-history-dropmarker.svg)
-  skin/classic/browser/urlbar-star.svg                         (../shared/urlbar-star.svg)
   skin/classic/browser/urlbar-tab.svg                          (../shared/urlbar-tab.svg)
--- a/browser/themes/shared/sync-desktopIcon.svg
+++ b/browser/themes/shared/sync-desktopIcon.svg
@@ -1,22 +1,8 @@
 <!-- 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/. -->
-<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
-  <style>
-    g:not(:target) { display: none; }
-
-    .glyph { fill: #4d4d4d; }
-    .glyph.translucent { fill-opacity: .15; }
-
-    .inverted .glyph { fill: #fff; }
-    .inverted .glyph.translucent { fill-opacity: .15; }
-  </style>
-  <g id="icon">
-    <path class="glyph" d="M15,14H1a1,1,0,0,1-1-1V12.526H16V13A1,1,0,0,1,15,14ZM1,4A1,1,0,0,1,2,3H14a1,1,0,0,1,1,1v8H1V4Zm1,7H14V4H2v7Z"/>
-    <rect class="glyph translucent" x="2" y="4" width="12" height="7"/>
-  </g>
-  <g id="icon-inverted" class="inverted">
-    <path class="glyph" d="M15,14H1a1,1,0,0,1-1-1V12.526H16V13A1,1,0,0,1,15,14ZM1,4A1,1,0,0,1,2,3H14a1,1,0,0,1,1,1v8H1V4Zm1,7H14V4H2v7Z"/>
-    <rect class="glyph translucent" x="2" y="4" width="12" height="7"/>
-  </g>
+<svg xmlns="http://www.w3.org/2000/svg"
+     width="16" height="16" viewBox="0 0 16 16">
+  <path fill="context-fill" d="M15,14H1a1,1,0,0,1-1-1V12.526H16V13A1,1,0,0,1,15,14ZM1,4A1,1,0,0,1,2,3H14a1,1,0,0,1,1,1v8H1V4Zm1,7H14V4H2v7Z"/>
+  <rect fill="context-fill" fill-opacity="0.15" x="2" y="4" width="12" height="7"/>
 </svg>
--- a/browser/themes/shared/sync-mobileIcon.svg
+++ b/browser/themes/shared/sync-mobileIcon.svg
@@ -1,22 +1,8 @@
 <!-- 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/. -->
-<svg width="16" height="16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg">
-  <style>
-    g:not(:target) { display: none; }
-
-    .glyph { fill: #4d4d4d; }
-    .glyph.translucent { fill-opacity: .15; }
-
-    .inverted .glyph { fill: #fff; }
-    .inverted .glyph.translucent { fill-opacity: .15; }
-  </style>
-  <g id="icon">
-    <path class="glyph" d="M12,16H4a1,1,0,0,1-1-1V1A1,1,0,0,1,4,0h8a1,1,0,0,1,1,1V15A1,1,0,0,1,12,16Zm-4-.684a0.785,0.785,0,1,0-.785-0.785A0.785,0.785,0,0,0,8,15.316ZM12,2H4V13h8V2Z"/>
-    <rect class="glyph translucent" x="4" y="2" width="8" height="11"/>
-  </g>
-  <g id="icon-inverted" class="inverted">
-    <path class="glyph" d="M12,16H4a1,1,0,0,1-1-1V1A1,1,0,0,1,4,0h8a1,1,0,0,1,1,1V15A1,1,0,0,1,12,16Zm-4-.684a0.785,0.785,0,1,0-.785-0.785A0.785,0.785,0,0,0,8,15.316ZM12,2H4V13h8V2Z"/>
-    <rect class="glyph translucent" x="4" y="2" width="8" height="11"/>
-  </g>
+<svg xmlns="http://www.w3.org/2000/svg"
+     width="16" height="16" viewBox="0 0 16 16">
+  <path fill="context-fill" d="M12,16H4a1,1,0,0,1-1-1V1A1,1,0,0,1,4,0h8a1,1,0,0,1,1,1V15A1,1,0,0,1,12,16Zm-4-.684a0.785,0.785,0,1,0-.785-0.785A0.785,0.785,0,0,0,8,15.316ZM12,2H4V13h8V2Z"/>
+  <rect fill="context-fill" fill-opacity="0.15" x="4" y="2" width="8" height="11"/>
 </svg>
--- a/browser/themes/shared/syncedtabs/sidebar.inc.css
+++ b/browser/themes/shared/syncedtabs/sidebar.inc.css
@@ -85,29 +85,33 @@ body {
   padding-inline-start: 35px;
 }
 
 .item.tab > .item-title-container {
   padding-inline-start: 20px;
 }
 
 .item.client.device-image-desktop > .item-title-container > .item-icon-container {
-  background-image: url("chrome://browser/skin/sync-desktopIcon.svg#icon");
+  background-image: url("chrome://browser/skin/sync-desktopIcon.svg");
+  -moz-context-properties: fill;
+  fill: #4d4d4d;
 }
 
 .item.client.device-image-desktop.selected:focus > .item-title-container > .item-icon-container {
-  background-image: url("chrome://browser/skin/sync-desktopIcon.svg#icon-inverted");
+  fill: white;
 }
 
 .item.client.device-image-mobile > .item-title-container > .item-icon-container {
-  background-image: url("chrome://browser/skin/sync-mobileIcon.svg#icon");
+  background-image: url("chrome://browser/skin/sync-mobileIcon.svg");
+  -moz-context-properties: fill;
+  fill: #4d4d4d;
 }
 
 .item.client.device-image-mobile.selected:focus > .item-title-container > .item-icon-container {
-  background-image: url("chrome://browser/skin/sync-mobileIcon.svg#icon-inverted");
+  fill: white;
 }
 
 .item.tab > .item-title-container > .item-icon-container {
   background-image: url("chrome://mozapps/skin/places/defaultFavicon.svg");
 }
 
 .item-icon-container {
   min-width: 16px;
deleted file mode 100644
--- a/browser/themes/shared/urlbar-star.svg
+++ /dev/null
@@ -1,20 +0,0 @@
-<?xml version="1.0"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
-  <style>
-    path:not(:target) {
-      display: none;
-    }
-    path {
-      fill: #b2b2b2;
-    }
-    path[id$="-inverted"] {
-      fill: #fff;
-    }
-  </style>
-
-	<path id="star" d="M8.7,0.5l2,4.3l4.6,0.7c0.6,0.1,0.9,0.9,0.4,1.4l-3.3,3.4l0.8,4.8c0.1,0.7-0.6,1.2-1.1,0.9L8,13.7l-4.1,2.3 c-0.6,0.3-1.2-0.2-1.1-0.9l0.8-4.8L0.2,6.9C-0.2,6.4,0,5.6,0.7,5.5l4.6-0.7l2-4.3C7.6-0.1,8.4-0.1,8.7,0.5z"/>
-	<path id="star-inverted" d="M8.7,0.5l2,4.3l4.6,0.7c0.6,0.1,0.9,0.9,0.4,1.4l-3.3,3.4l0.8,4.8c0.1,0.7-0.6,1.2-1.1,0.9L8,13.7l-4.1,2.3 c-0.6,0.3-1.2-0.2-1.1-0.9l0.8-4.8L0.2,6.9C-0.2,6.4,0,5.6,0.7,5.5l4.6-0.7l2-4.3C7.6-0.1,8.4-0.1,8.7,0.5z"/>
-</svg>
--- a/browser/themes/shared/urlbar-tab.svg
+++ b/browser/themes/shared/urlbar-tab.svg
@@ -1,21 +1,7 @@
-<?xml version="1.0"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="16" height="16" viewBox="0 0 16 16">
-  <style>
-    path:not(:target) {
-      display: none;
-    }
-    path {
-      fill: #b2b2b2;
-    }
-    path[id$="-inverted"] {
-      fill: #fff;
-    }
-  </style>
-
-  <path id="tab" d="M14,9.5V6c0-1.7-1.3-3-3-3H5C3.3,3,2,4.3,2,6v3.5C2,10.3,1.3,11,0.5,11h0C0.2,11,0,11.2,0,11.5v1 C0,12.8,0.2,13,0.5,13h15c0.3,0,0.5-0.2,0.5-0.5v-1c0-0.3-0.2-0.5-0.5-0.5h0C14.7,11,14,10.3,14,9.5z"/>
-  <path id="tab-inverted" d="M14,9.5V6c0-1.7-1.3-3-3-3H5C3.3,3,2,4.3,2,6v3.5C2,10.3,1.3,11,0.5,11h0C0.2,11,0,11.2,0,11.5v1 C0,12.8,0.2,13,0.5,13h15c0.3,0,0.5-0.2,0.5-0.5v-1c0-0.3-0.2-0.5-0.5-0.5h0C14.7,11,14,10.3,14,9.5z"/>
-
+<svg xmlns="http://www.w3.org/2000/svg"
+     width="16" height="16" viewBox="0 0 16 16">
+  <path fill="context-fill" d="M14,9.5V6c0-1.7-1.3-3-3-3H5C3.3,3,2,4.3,2,6v3.5C2,10.3,1.3,11,0.5,11h0C0.2,11,0,11.2,0,11.5v1 C0,12.8,0.2,13,0.5,13h15c0.3,0,0.5-0.2,0.5-0.5v-1c0-0.3-0.2-0.5-0.5-0.5h0C14.7,11,14,10.3,14,9.5z"/>
 </svg>
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -1072,43 +1072,47 @@ treechildren.searchbar-treebody::-moz-tr
 
 .autocomplete-richlistitem[selected],
 treechildren.searchbar-treebody::-moz-tree-row(selected) {
   background-color: Highlight;
   color: HighlightText;
 }
 
 .ac-type-icon[type=bookmark] {
-  list-style-image: url("chrome://browser/skin/urlbar-star.svg#star");
+  list-style-image: url("chrome://browser/skin/bookmark.svg");
+  -moz-context-properties: fill;
+  fill: #b2b2b2;
 }
 
 .ac-type-icon[type=bookmark][selected][current] {
-  list-style-image: url("chrome://browser/skin/urlbar-star.svg#star-inverted");
+  fill: white;
 }
 
 .ac-type-icon[type=keyword],
 .ac-site-icon[type=searchengine] {
   list-style-image: url(chrome://global/skin/icons/autocomplete-search.svg);
   -moz-context-properties: fill;
   fill: GrayText;
 }
 
 .ac-type-icon[type=keyword][selected],
 .ac-site-icon[type=searchengine][selected] {
   fill: highlighttext;
 }
 
 .ac-type-icon[type=switchtab],
 .ac-type-icon[type=remotetab] {
-  list-style-image: url("chrome://browser/skin/urlbar-tab.svg#tab");
+  list-style-image: url("chrome://browser/skin/urlbar-tab.svg");
+  -moz-context-properties: fill;
+  fill: #b2b2b2;
 }
 
 .ac-type-icon[type=switchtab][selected],
 .ac-type-icon[type=remotetab][selected] {
-  list-style-image: url("chrome://browser/skin/urlbar-tab.svg#tab-inverted");
+  fill: white;
 }
 
 .autocomplete-treebody::-moz-tree-cell-text(treecolAutoCompleteComment) {
   color: GrayText;
 }
 
 .autocomplete-treebody::-moz-tree-cell-text(suggesthint, treecolAutoCompleteComment),
 .autocomplete-treebody::-moz-tree-cell-text(suggestfirst, treecolAutoCompleteComment)
--- a/browser/themes/windows/customizableui/menu-arrow.svg
+++ b/browser/themes/windows/customizableui/menu-arrow.svg
@@ -1,26 +1,7 @@
-<?xml version="1.0" encoding="utf-8"?>
 <!-- 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/. -->
-<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" height="16" width="16" viewBox="0 0 16 16">
-  <style>
-    use:not(:target) {
-      display: none;
-    }
-    use {
-      fill: MenuText;
-    }
-    use[id$="-disabled"] {
-      fill: GrayText;
-    }
-    use[id$="-hover"] {
-      fill: HighlightText;
-    }
-  </style>
-  <defs>
-    <path id="arrow-shape" d="m 6,4 0,8 5,-4 z"/>
-  </defs>
-  <use id="arrow" xlink:href="#arrow-shape"/>
-  <use id="arrow-disabled" xlink:href="#arrow-shape"/>
-  <use id="arrow-hover" xlink:href="#arrow-shape"/>
+<svg xmlns="http://www.w3.org/2000/svg"
+     height="16" width="16" viewBox="0 0 16 16">
+  <path fill="context-fill" d="m 6,4 0,8 5,-4 z"/>
 </svg>
--- a/browser/themes/windows/customizableui/panelUI.css
+++ b/browser/themes/windows/customizableui/panelUI.css
@@ -106,28 +106,30 @@ toolbarbutton.social-provider-menuitem >
 }
 
 .subviewbutton:-moz-any([image],[targetURI],.cui-withicon, .restoreallitem, .bookmark-item)[checked="true"] > .toolbarbutton-icon {
   visibility: hidden;
 }
 
 menu.subviewbutton > .menu-right {
   -moz-appearance: none;
-  list-style-image: url(chrome://browser/skin/customizableui/menu-arrow.svg#arrow);
+  list-style-image: url(chrome://browser/skin/customizableui/menu-arrow.svg);
+  -moz-context-properties: fill;
+  fill: MenuText;
   /* Reset the rect we inherit from the button: */
   -moz-image-region: auto;
 }
 
 menu[disabled="true"].subviewbutton > .menu-right {
-  list-style-image: url(chrome://browser/skin/customizableui/menu-arrow.svg#arrow-disabled);
+  fill: GrayText;
 }
 
 @media (-moz-windows-default-theme: 0) {
   menu[_moz-menuactive].subviewbutton > .menu-right {
-    list-style-image: url(chrome://browser/skin/customizableui/menu-arrow.svg#arrow-hover);
+    fill: HighlightText;
   }
 }
 
 menu.subviewbutton > .menu-right:-moz-locale-dir(rtl) {
   transform: scaleX(-1);
 }
 
 /* Win8 and beyond. */
--- a/devtools/client/canvasdebugger/panel.js
+++ b/devtools/client/canvasdebugger/panel.js
@@ -45,17 +45,17 @@ CanvasDebuggerPanel.prototype = {
         this.panelWin.gFront = new CanvasFront(this.target.client, this.target.form);
         return this.panelWin.startupCanvasDebugger();
       })
       .then(() => {
         this.isReady = true;
         this.emit("ready");
         return this;
       })
-      .then(null, function onError(aReason) {
+      .catch(function onError(aReason) {
         DevToolsUtils.reportException("CanvasDebuggerPanel.prototype.open", aReason);
       });
   },
 
   // DevToolPanel API
 
   get target() {
     return this._toolbox.target;
--- a/devtools/client/canvasdebugger/test/head.js
+++ b/devtools/client/canvasdebugger/test/head.js
@@ -121,17 +121,17 @@ function ifTestingSupported() {
 
 function ifTestingUnsupported() {
   todo(false, "Skipping test because some required functionality isn't supported.");
   finish();
 }
 
 function test() {
   let generator = isTestingSupported() ? ifTestingSupported : ifTestingUnsupported;
-  Task.spawn(generator).then(null, handleError);
+  Task.spawn(generator).catch(handleError);
 }
 
 function createCanvas() {
   return document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
 }
 
 function isTestingSupported() {
   if (!gRequiresWebGL) {
--- a/devtools/client/commandline/test/helpers.js
+++ b/devtools/client/commandline/test/helpers.js
@@ -129,17 +129,17 @@ var { helpers, assert } = (function () {
     options.tab = tabbrowser.addTab();
     tabbrowser.selectedTab = options.tab;
     options.browser = tabbrowser.getBrowserForTab(options.tab);
     options.target = TargetFactory.forTab(options.tab);
 
     var loaded = helpers.listenOnce(options.browser, "load", true).then(function (ev) {
       var reply = callback.call(null, options);
 
-      return Promise.resolve(reply).then(null, function (error) {
+      return Promise.resolve(reply).catch(function (error) {
         ok(false, error);
       }).then(function () {
         tabbrowser.removeTab(options.tab);
 
         delete options.target;
         delete options.browser;
         delete options.tab;
 
@@ -325,17 +325,17 @@ var { helpers, assert } = (function () {
 
       return win.DeveloperToolbar.show(true).then(function () {
         var toolbar = win.DeveloperToolbar;
         innerOptions.automator = createDeveloperToolbarAutomator(toolbar);
         innerOptions.requisition = toolbar.requisition;
 
         var reply = callback.call(null, innerOptions);
 
-        return Promise.resolve(reply).then(null, function (error) {
+        return Promise.resolve(reply).catch(function (error) {
           ok(false, error);
           console.error(error);
         }).then(function () {
           win.DeveloperToolbar.hide().then(function () {
             delete innerOptions.automator;
           });
         });
       });
--- a/devtools/client/debugger/panel.js
+++ b/devtools/client/debugger/panel.js
@@ -64,17 +64,17 @@ DebuggerPanel.prototype = {
 
           let keyShortcut = this.translateToKeyShortcut(keycode, modifiers);
           this._toolbox.useKeyWithSplitConsole(keyShortcut, handler, "jsdebugger");
         }
         this.isReady = true;
         this.emit("ready");
         return this;
       })
-      .then(null, function onError(aReason) {
+      .catch(function onError(aReason) {
         DevToolsUtils.reportException("DebuggerPanel.prototype.open", aReason);
       });
   },
 
   /**
    * Translate a VK_ keycode, with modifiers, to a key shortcut that can be used with
    * shared/key-shortcut.
    *
--- a/devtools/client/debugger/test/mochitest/browser_dbg_addonactor.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_addonactor.js
@@ -26,17 +26,17 @@ function test() {
     installAddon()
       .then(attachAddonActorForId.bind(null, gClient, ADDON3_ID))
       .then(attachAddonThread)
       .then(testDebugger)
       .then(testSources)
       .then(() => gClient.close())
       .then(uninstallAddon)
       .then(finish)
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function installAddon() {
   return addTemporaryAddon(ADDON3_PATH).then(aAddon => {
     gAddon = aAddon;
--- a/devtools/client/debugger/test/mochitest/browser_dbg_auto-pretty-print-02.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_auto-pretty-print-02.js
@@ -55,17 +55,17 @@ function test() {
       yield selectFirstSource();
       testFirstSourceLabel();
       testPrettyPrintButtonOn();
 
       // Disable auto pretty printing so it does not affect the following tests.
       yield disableAutoPrettyPrint();
 
       closeDebuggerAndFinish(gPanel)
-        .then(null, aError => {
+        .catch(aError => {
           ok(false, "Got an error: " + DevToolsUtils.safeErrorString(aError));
         });
     });
 
     function selectSecondSource() {
       let finished = waitForDebuggerEvents(gPanel, gDebugger.EVENTS.SOURCE_SHOWN, 2);
       gSources.selectedIndex = 1;
       return finished;
--- a/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-01.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-01.js
@@ -19,17 +19,17 @@ function test() {
   initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
     gTab = aTab;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
 
     testBlackBoxSource()
       .then(testBlackBoxReload)
       .then(() => closeDebuggerAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function testBlackBoxSource() {
   const bbButton = getBlackBoxButton(gPanel);
   ok(!bbButton.checked, "Should not be black boxed by default");
--- a/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-02.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-02.js
@@ -23,17 +23,17 @@ function test() {
     gTab = aTab;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gFrames = gDebugger.DebuggerView.StackFrames;
 
     testBlackBoxSource()
       .then(testBlackBoxStack)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function testBlackBoxSource() {
   return toggleBlackBoxing(gPanel).then(source => {
     ok(source.isBlackBoxed, "The source should be black boxed now.");
--- a/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-03.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-03.js
@@ -25,17 +25,17 @@ function test() {
     gDebugger = gPanel.panelWin;
     gFrames = gDebugger.DebuggerView.StackFrames;
     gSources = gDebugger.DebuggerView.Sources;
 
     waitForSourceAndCaretAndScopes(gPanel, ".html", 21)
       .then(testBlackBoxStack)
       .then(testBlackBoxSource)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     callInTab(gTab, "runTest");
   });
 }
 
 function testBlackBoxStack() {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-04.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-04.js
@@ -24,17 +24,17 @@ function test() {
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gFrames = gDebugger.DebuggerView.StackFrames;
     gSources = gDebugger.DebuggerView.Sources;
 
     blackBoxSources()
       .then(testBlackBoxStack)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function blackBoxSources() {
   let finished = waitForThreadEvents(gPanel, "blackboxchange", 3);
 
--- a/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-05.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-05.js
@@ -25,17 +25,17 @@ function test() {
     gDeck = gDebugger.document.getElementById("editor-deck");
 
     testSourceEditorShown();
     toggleBlackBoxing(gPanel)
       .then(testBlackBoxMessageShown)
       .then(clickStopBlackBoxingButton)
       .then(testSourceEditorShownAgain)
       .then(() => closeDebuggerAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function testSourceEditorShown() {
   is(gDeck.selectedIndex, "0",
     "The first item in the deck should be selected (the source editor).");
--- a/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-06.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-06.js
@@ -22,17 +22,17 @@ function test() {
     gTab = aTab;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gSources = gDebugger.DebuggerView.Sources;
 
     waitForCaretAndScopes(gPanel, 21)
       .then(testBlackBox)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     callInTab(gTab, "runTest");
   });
 }
 
 function testBlackBox() {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-07.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_blackboxing-07.js
@@ -19,17 +19,17 @@ function test() {
   initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
     gTab = aTab;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
 
     testBlackBoxSource()
       .then(testBlackBoxReload)
       .then(() => closeDebuggerAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function testBlackBoxSource() {
   const bbButton = getBlackBoxButton(gPanel);
   ok(bbButton.checked, "Should be black boxed by default");
--- a/devtools/client/debugger/test/mochitest/browser_dbg_breadcrumbs-access.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_breadcrumbs-access.js
@@ -24,17 +24,17 @@ function test() {
     gSources = gDebugger.DebuggerView.Sources;
     gFrames = gDebugger.DebuggerView.StackFrames;
 
     waitForSourceAndCaretAndScopes(gPanel, "-02.js", 6)
       .then(checkNavigationWhileNotFocused)
       .then(focusCurrentStackFrame)
       .then(checkNavigationWhileFocused)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     callInTab(gTab, "firstCall");
   });
 
   function checkNavigationWhileNotFocused() {
     checkState({ frame: 1, source: 1, line: 6 });
--- a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-01.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-01.js
@@ -28,17 +28,17 @@ function test() {
       .then(setupGlobals)
       .then(pauseDebuggee)
       .then(testBreakOnAll)
       .then(testBreakOnDisabled)
       .then(testBreakOnNone)
       .then(testBreakOnClick)
       .then(() => gClient.close())
       .then(finish)
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function setupGlobals(aThreadClient) {
   gThreadClient = aThreadClient;
   gInput = content.document.querySelector("input");
--- a/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-02.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_break-on-dom-event-02.js
@@ -26,17 +26,17 @@ function test() {
 
     addTab(TAB_URL)
       .then(() => attachThreadActorForUrl(gClient, TAB_URL))
       .then(aThreadClient => gThreadClient = aThreadClient)
       .then(pauseDebuggee)
       .then(testBreakOnClick)
       .then(() => gClient.close())
       .then(finish)
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function pauseDebuggee() {
   let deferred = promise.defer();
 
--- a/devtools/client/debugger/test/mochitest/browser_dbg_chrome-debugging.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_chrome-debugging.js
@@ -30,17 +30,17 @@ function test() {
   gClient = new DebuggerClient(transport);
   gClient.connect().then(([aType, aTraits]) => {
     is(aType, "browser",
       "Root actor should identify itself as a browser.");
 
     promise.all([gAttached.promise, gNewGlobal.promise, gNewChromeSource.promise])
       .then(resumeAndCloseConnection)
       .then(finish)
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     testChromeActor();
   });
 }
 
 function testChromeActor() {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_clean-exit-window.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_clean-exit-window.js
@@ -17,17 +17,17 @@ function test() {
     .then(([aTab, aDebuggee, aPanel, aWindow]) => {
       gDebuggee = aDebuggee;
       gPanel = aPanel;
       gDebugger = gPanel.panelWin;
       gWindow = aWindow;
 
       return testCleanExit();
     })
-    .then(null, aError => {
+    .catch(aError => {
       ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
     });
 }
 
 function testCleanExit() {
   let deferred = promise.defer();
 
   ok(!!gWindow, "Second window created.");
--- a/devtools/client/debugger/test/mochitest/browser_dbg_closure-inspection.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_closure-inspection.js
@@ -16,17 +16,17 @@ function test() {
   };
   initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
     gTab = aTab;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
 
     testClosure()
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 
   function testClosure() {
     generateMouseClickInTab(gTab, "content.document.querySelector('button')");
 
     return waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_SCOPES).then(() => {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_debugger-statement.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_debugger-statement.js
@@ -28,17 +28,17 @@ function test() {
       .then((aTab) => {
         gTab = aTab;
         return attachTabActorForUrl(gClient, TAB_URL);
       })
       .then(testEarlyDebuggerStatement)
       .then(testDebuggerStatement)
       .then(() => gClient.close())
       .then(finish)
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function testEarlyDebuggerStatement([aGrip, aResponse]) {
   let deferred = promise.defer();
 
--- a/devtools/client/debugger/test/mochitest/browser_dbg_editor-contextmenu.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_editor-contextmenu.js
@@ -20,17 +20,17 @@ function test() {
   initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
     gTab = aTab;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gEditor = gDebugger.DebuggerView.editor;
     gSources = gDebugger.DebuggerView.Sources;
     gContextMenu = gDebugger.document.getElementById("sourceEditorContextMenu");
 
-    waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1).then(performTest).then(null, info);
+    waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1).then(performTest).catch(info);
     callInTab(gTab, "firstCall");
   });
 
   function performTest() {
     is(gDebugger.gThreadClient.state, "paused",
       "Should only be getting stack frames while paused.");
     is(gSources.itemCount, 2,
       "Found the expected number of sources.");
@@ -45,17 +45,17 @@ function test() {
     ok(gContextMenu,
       "The source editor's context menupopup is available.");
     ok(gEditor.getOption("readOnly"),
       "The source editor is read only.");
 
     gEditor.focus();
     gEditor.setSelection({ line: 1, ch: 0 }, { line: 1, ch: 10 });
 
-    once(gContextMenu, "popupshown").then(testContextMenu).then(null, info);
+    once(gContextMenu, "popupshown").then(testContextMenu).catch(info);
     gContextMenu.openPopup(gEditor.container, "overlap", 0, 0, true, false);
   }
 
   function testContextMenu() {
     let document = gDebugger.document;
 
     ok(document.getElementById("editMenuCommands"),
       "#editMenuCommands found.");
--- a/devtools/client/debugger/test/mochitest/browser_dbg_editor-mode.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_editor-mode.js
@@ -25,17 +25,17 @@ function test() {
     gEditor = gDebugger.DebuggerView.editor;
     gSources = gDebugger.DebuggerView.Sources;
 
     waitForSourceAndCaretAndScopes(gPanel, "code_test-editor-mode", 1)
       .then(testInitialSource)
       .then(testSwitch1)
       .then(testSwitch2)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     callInTab(gTab, "firstCall");
   });
 }
 
 function testInitialSource() {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-01.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-01.js
@@ -28,17 +28,17 @@ function test() {
       .then((aTab) => {
         gTab = aTab;
         return attachThreadActorForUrl(gClient, TAB_URL);
       })
       .then(pauseDebuggee)
       .then(testEventListeners)
       .then(() => gClient.close())
       .then(finish)
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function pauseDebuggee(aThreadClient) {
   let deferred = promise.defer();
 
--- a/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-02.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-02.js
@@ -29,17 +29,17 @@ function test() {
       .then((aTab) => {
         gTab = aTab;
         return attachThreadActorForUrl(gClient, TAB_URL);
       })
       .then(pauseDebuggee)
       .then(testEventListeners)
       .then(() => gClient.close())
       .then(finish)
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function pauseDebuggee(aThreadClient) {
   let deferred = promise.defer();
 
--- a/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-03.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_event-listeners-03.js
@@ -29,17 +29,17 @@ function test() {
       .then((aTab) => {
         gTab = aTab;
         return attachThreadActorForUrl(gClient, TAB_URL);
       })
       .then(pauseDebuggee)
       .then(testEventListeners)
       .then(() => gClient.close())
       .then(finish)
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function pauseDebuggee(aThreadClient) {
   let deferred = promise.defer();
 
--- a/devtools/client/debugger/test/mochitest/browser_dbg_iframes.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_iframes.js
@@ -25,17 +25,17 @@ function test() {
     gIframe = gDebuggee.frames[0];
     gEditor = gDebugger.DebuggerView.editor;
     gSources = gDebugger.DebuggerView.Sources;
     gFrames = gDebugger.DebuggerView.StackFrames;
 
     checkIframeSource();
     checkIframePause()
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 
   function checkIframeSource() {
     is(gDebugger.gThreadClient.paused, false,
       "Should be running after starting the test.");
 
--- a/devtools/client/debugger/test/mochitest/browser_dbg_interrupts.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_interrupts.js
@@ -29,17 +29,17 @@ function test() {
     gResumeKey = gDebugger.document.getElementById("resumeKey");
 
     gTarget.on("thread-paused", failOnPause);
     addBreakpoints()
       .then(() => { gTarget.off("thread-paused", failOnPause); })
       .then(testResumeButton)
       .then(testResumeKeyboard)
       .then(() => closeDebuggerAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 
   function failOnPause() {
     ok(false, "A pause was sent, but it shouldn't have been");
   }
 
--- a/devtools/client/debugger/test/mochitest/browser_dbg_jump-to-function-definition.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_jump-to-function-definition.js
@@ -21,17 +21,17 @@ function test() {
   initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
     gTab = aTab;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gSources = gDebugger.DebuggerView.Sources;
 
     jumpToFunctionDefinition()
       .then(() => closeDebuggerAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 
   function jumpToFunctionDefinition() {
     let callLocation = {line: 5, ch: 0};
     let editor = gDebugger.DebuggerView.editor;
     let coords = editor.getCoordsFromPosition(callLocation);
--- a/devtools/client/debugger/test/mochitest/browser_dbg_listaddons.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_listaddons.js
@@ -27,17 +27,17 @@ function test() {
 
     promise.resolve(null)
       .then(testFirstAddon)
       .then(testSecondAddon)
       .then(testRemoveFirstAddon)
       .then(testRemoveSecondAddon)
       .then(() => gClient.close())
       .then(finish)
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function testFirstAddon() {
   let addonListChanged = false;
   gClient.addOneTimeListener("addonListChanged", () => {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-01.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_listtabs-01.js
@@ -26,17 +26,17 @@ function test() {
 
     promise.resolve(null)
       .then(testFirstTab)
       .then(testSecondTab)
       .then(testRemoveTab)
       .then(testAttachRemovedTab)
       .then(() => gClient.close())
       .then(finish)
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function testFirstTab() {
   return addTab(TAB1_URL).then(aTab => {
     gTab1 = aTab;
--- a/devtools/client/debugger/test/mochitest/browser_dbg_multiple-windows.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_multiple-windows.js
@@ -30,17 +30,17 @@ function test() {
       .then(() => addTab(TAB1_URL))
       .then(testFirstTab)
       .then(() => addWindow(TAB2_URL))
       .then(testNewWindow)
       .then(testFocusFirst)
       .then(testRemoveTab)
       .then(() => gClient.close())
       .then(finish)
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function testFirstTab(aTab) {
   let deferred = promise.defer();
 
--- a/devtools/client/debugger/test/mochitest/browser_dbg_navigation.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_navigation.js
@@ -24,17 +24,17 @@ function test() {
     is(aType, "browser",
       "Root actor should identify itself as a browser.");
 
     addTab(TAB1_URL)
       .then(() => attachTabActorForUrl(gClient, TAB1_URL))
       .then(testNavigate)
       .then(testDetach)
       .then(finish)
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function testNavigate([aGrip, aResponse]) {
   let outstanding = [promise.defer(), promise.defer()];
 
--- a/devtools/client/debugger/test/mochitest/browser_dbg_no-page-sources.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_no-page-sources.js
@@ -21,17 +21,17 @@ function test() {
     gEditor = gDebugger.DebuggerView.editor;
     gSources = gDebugger.DebuggerView.Sources;
     const constants = gDebugger.require("./content/constants");
 
     reloadActiveTab(gPanel);
     waitForNavigation(gPanel)
       .then(testSourcesEmptyText)
       .then(() => closeDebuggerAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function testSourcesEmptyText() {
   is(gSources.itemCount, 0,
       "Found no entries in the sources widget.");
--- a/devtools/client/debugger/test/mochitest/browser_dbg_optimized-out-vars.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_optimized-out-vars.js
@@ -39,12 +39,12 @@ function test() {
 
     let argVar = outerScope.get("arg");
     is(argVar.target.querySelector(".name").getAttribute("value"), "arg",
       "Should have the right property name for |arg|.");
     is(argVar.target.querySelector(".value").getAttribute("value"), 44,
       "Should have the right property value for |arg|.");
 
     yield resumeDebuggerThenCloseAndFinish(panel);
-  }).then(null, aError => {
+  }).catch(aError => {
     ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
   });
 }
--- a/devtools/client/debugger/test/mochitest/browser_dbg_pause-exceptions-01.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_pause-exceptions-01.js
@@ -34,17 +34,17 @@ function test() {
 
     testPauseOnExceptionsDisabled()
       .then(enablePauseOnExceptions)
       .then(disableIgnoreCaughtExceptions)
       .then(testPauseOnExceptionsEnabled)
       .then(disablePauseOnExceptions)
       .then(enableIgnoreCaughtExceptions)
       .then(() => closeDebuggerAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function testPauseOnExceptionsDisabled() {
   let finished = waitForCaretAndScopes(gPanel, 26).then(() => {
     info("Testing disabled pause-on-exceptions.");
--- a/devtools/client/debugger/test/mochitest/browser_dbg_pause-exceptions-02.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_pause-exceptions-02.js
@@ -36,17 +36,17 @@ function test() {
       .then(() => reloadActiveTab(gPanel, gDebugger.EVENTS.SOURCE_SHOWN))
       .then(() => {
         generateMouseClickInTab(gTab, "content.document.querySelector('button')");
       })
       .then(testPauseOnExceptionsAfterReload)
       .then(disablePauseOnExceptions)
       .then(enableIgnoreCaughtExceptions)
       .then(() => closeDebuggerAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function testPauseOnExceptionsAfterReload() {
   let finished = waitForCaretAndScopes(gPanel, 19).then(() => {
     info("Testing enabled pause-on-exceptions.");
--- a/devtools/client/debugger/test/mochitest/browser_dbg_paused-keybindings.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_paused-keybindings.js
@@ -39,12 +39,12 @@ function test() {
     executeSoon(function () {
       EventUtils.synthesizeKey("l", { accelKey: true });
       EventUtils.synthesizeKey("1", {});
       EventUtils.synthesizeKey("5", {});
     });
     yield caretMove;
 
     yield resumeDebuggerThenCloseAndFinish(panel);
-  }).then(null, aError => {
+  }).catch(aError => {
     ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
   });
 }
--- a/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-11.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_pretty-print-11.js
@@ -31,17 +31,17 @@ function test() {
     finished.then(testSourceIsPretty)
       .then(() => {
         const finished = waitForCaretUpdated(gPanel, 7);
         const reloaded = reloadActiveTab(gPanel, gDebugger.EVENTS.SOURCE_SHOWN);
         return Promise.all([finished, reloaded]);
       })
       .then(testSourceIsPretty)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + DevToolsUtils.safeErrorString(aError));
       });
   });
 }
 
 function testSourceIsUgly() {
   ok(!gEditor.getText().includes("\n  "),
      "The source shouldn't be pretty printed yet.");
--- a/devtools/client/debugger/test/mochitest/browser_dbg_promises-allocation-stack.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_promises-allocation-stack.js
@@ -30,17 +30,17 @@ function test() {
     let { tabs } = yield listTabs(client);
     let targetTab = findTab(tabs, TAB_URL);
     yield attachTab(client, targetTab);
 
     yield testGetAllocationStack(client, targetTab, tab);
 
     yield close(client);
     yield closeDebuggerAndFinish(panel);
-  }).then(null, error => {
+  }).catch(error => {
     ok(false, "Got an error: " + error.message + "\n" + error.stack);
   });
 }
 
 function* testGetAllocationStack(client, form, tab) {
   let front = PromisesFront(client, form);
 
   yield front.attach();
--- a/devtools/client/debugger/test/mochitest/browser_dbg_promises-chrome-allocation-stack.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_promises-chrome-allocation-stack.js
@@ -33,23 +33,23 @@ function test() {
     let [, tabClient] = yield attachTab(client, chrome.form);
     yield tabClient.attachThread();
 
     yield testGetAllocationStack(client, chrome.form, () => {
       let p = new Promise(() => {});
       p.name = "p";
       let q = p.then();
       q.name = "q";
-      let r = p.then(null, () => {});
+      let r = p.catch(() => {});
       r.name = "r";
     });
 
     yield close(client);
     finish();
-  }).then(null, error => {
+  }).catch(error => {
     ok(false, "Got an error: " + error.message + "\n" + error.stack);
   });
 }
 
 function* testGetAllocationStack(client, form, makePromises) {
   let front = PromisesFront(client, form);
 
   yield front.attach();
--- a/devtools/client/debugger/test/mochitest/browser_dbg_promises-fulfillment-stack.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_promises-fulfillment-stack.js
@@ -48,17 +48,17 @@ function test() {
     let { tabs } = yield listTabs(client);
     let targetTab = findTab(tabs, TAB_URL);
     yield attachTab(client, targetTab);
 
     yield testGetFulfillmentStack(client, targetTab, tab);
 
     yield close(client);
     yield closeDebuggerAndFinish(panel);
-  }).then(null, error => {
+  }).catch(error => {
     ok(false, "Got an error: " + error.message + "\n" + error.stack);
   });
 }
 
 function* testGetFulfillmentStack(client, form, tab) {
   let front = PromisesFront(client, form);
 
   yield front.attach();
--- a/devtools/client/debugger/test/mochitest/browser_dbg_promises-rejection-stack.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_promises-rejection-stack.js
@@ -55,17 +55,17 @@ function test() {
     let { tabs } = yield listTabs(client);
     let targetTab = findTab(tabs, TAB_URL);
     yield attachTab(client, targetTab);
 
     yield testGetRejectionStack(client, targetTab, tab);
 
     yield close(client);
     yield closeDebuggerAndFinish(panel);
-  }).then(null, error => {
+  }).catch(error => {
     ok(false, "Got an error: " + error.message + "\n" + error.stack);
   });
 }
 
 function* testGetRejectionStack(client, form, tab) {
   let front = PromisesFront(client, form);
 
   yield front.attach();
--- a/devtools/client/debugger/test/mochitest/browser_dbg_reload-preferred-script-03.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_reload-preferred-script-03.js
@@ -27,17 +27,17 @@ function test() {
     gSources = gDebugger.DebuggerView.Sources;
 
     testSource(undefined, FIRST_URL);
     switchToSource(SECOND_URL)
       .then(() => testSource(SECOND_URL))
       .then(() => switchToSource(FIRST_URL))
       .then(() => testSource(FIRST_URL))
       .then(() => closeDebuggerAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function testSource(aPreferredUrl, aSelectedUrl = aPreferredUrl) {
   info("Currently preferred source: " + gSources.preferredValue);
   info("Currently selected source: " + gSources.selectedValue);
--- a/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-02.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-02.js
@@ -30,17 +30,17 @@ function test() {
       .then(() => verifySourceAndCaret("-01.js", 1, 1, [1, 1]))
       .then(combineWithLineSearch)
       .then(() => verifySourceAndCaret("-01.js", 2, 1, [53, 53]))
       .then(combineWithTokenSearch)
       .then(() => verifySourceAndCaret("-01.js", 2, 48, [96, 100]))
       .then(combineWithTokenColonSearch)
       .then(() => verifySourceAndCaret("-01.js", 2, 11, [56, 63]))
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     callInTab(gTab, "firstCall");
   });
 }
 
 function performSimpleSearch() {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-03.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-basic-03.js
@@ -32,17 +32,17 @@ function test() {
       .then(performFunctionSearch)
       .then(escapeAndHide)
       .then(escapeAndClear)
       .then(() => verifySourceAndCaret("-01.js", 4, 10))
       .then(performGlobalSearch)
       .then(escapeAndClear)
       .then(() => verifySourceAndCaret("-01.js", 4, 10))
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     callInTab(gTab, "firstCall");
   });
 }
 
 function performFileSearch() {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_search-global-01.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-global-01.js
@@ -27,17 +27,17 @@ function test() {
     gSearchView = gDebugger.DebuggerView.GlobalSearch;
     gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
 
     waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
       .then(firstSearch)
       .then(secondSearch)
       .then(clearSearch)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     callInTab(gTab, "firstCall");
   });
 }
 
 function firstSearch() {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_search-global-03.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-global-03.js
@@ -26,17 +26,17 @@ function test() {
     gSources = gDebugger.DebuggerView.Sources;
     gSearchView = gDebugger.DebuggerView.GlobalSearch;
     gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
 
     waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
       .then(firstSearch)
       .then(performTest)
       .then(() => closeDebuggerAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     callInTab(gTab, "firstCall");
   });
 }
 
 function firstSearch() {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_search-global-04.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-global-04.js
@@ -26,17 +26,17 @@ function test() {
     gSources = gDebugger.DebuggerView.Sources;
     gSearchView = gDebugger.DebuggerView.GlobalSearch;
     gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
 
     waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
       .then(firstSearch)
       .then(secondSearch)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     callInTab(gTab, "firstCall");
   });
 }
 
 function firstSearch() {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_search-global-05.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-global-05.js
@@ -29,17 +29,17 @@ function test() {
     gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
 
     waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
       .then(doSearch)
       .then(testExpandCollapse)
       .then(testClickLineToJump)
       .then(testClickMatchToJump)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     callInTab(gTab, "firstCall");
   });
 }
 
 function doSearch() {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_search-global-06.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-global-06.js
@@ -28,17 +28,17 @@ function test() {
     gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
 
     waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
       .then(doSearch)
       .then(testFocusLost)
       .then(doSearch)
       .then(testEscape)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     callInTab(gTab, "firstCall");
   });
 }
 
 function doSearch() {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_search-popup-jank.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-popup-jank.js
@@ -70,17 +70,17 @@ function test() {
       .then(superGenericFunctionSearch)
       .then(() => ensureSourceIs(aPanel, "code_test-editor-mode"))
       .then(() => ensureCaretAt(aPanel, 6))
       .then(() => pressKey("RETURN"))
       .then(() => ensureSourceIs(aPanel, "code_test-editor-mode"))
       .then(() => ensureCaretAt(aPanel, 4, 10))
 
       .then(() => closeDebuggerAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function waitForMatchFoundAndResultsShown(aName) {
   return promise.all([
     once(gDebugger, "popupshown"),
--- a/devtools/client/debugger/test/mochitest/browser_dbg_search-sources-01.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-sources-01.js
@@ -33,17 +33,17 @@ function test() {
       yield secondSearch();
       yield thirdSearch();
       yield fourthSearch();
       yield fifthSearch();
       yield sixthSearch();
       yield seventhSearch();
 
       return closeDebuggerAndFinish(gPanel)
-        .then(null, aError => {
+        .catch(aError => {
           ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
         });
     });
 
     function bogusSearch() {
       let finished = promise.all([
         ensureSourceIs(gPanel, "-01.js"),
         ensureCaretAt(gPanel, 1),
--- a/devtools/client/debugger/test/mochitest/browser_dbg_search-sources-02.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-sources-02.js
@@ -37,17 +37,17 @@ function test() {
       .then(goDown)
       .then(goDownAndWrap)
       .then(goUpAndWrap)
       .then(goUp)
       .then(returnAndSwitch)
       .then(firstSearch)
       .then(clickAndSwitch)
       .then(() => closeDebuggerAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function firstSearch() {
   let finished = promise.all([
     ensureSourceIs(gPanel, "-01.js"),
--- a/devtools/client/debugger/test/mochitest/browser_dbg_search-sources-03.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-sources-03.js
@@ -28,17 +28,17 @@ function test() {
       .then(verifySourcesPane)
       .then(kindaInterpretableSearch)
       .then(verifySourcesPane)
       .then(incrediblySpecificSearch)
       .then(verifySourcesPane)
       .then(returnAndHide)
       .then(verifySourcesPane)
       .then(() => closeDebuggerAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function waitForMatchFoundAndResultsShown() {
   return promise.all([
     once(gDebugger, "popupshown"),
--- a/devtools/client/debugger/test/mochitest/browser_dbg_search-symbols.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_search-symbols.js
@@ -35,17 +35,17 @@ function test() {
       .then(() => showSource("code_function-search-03.js"))
       .then(thirdJsSearch)
       .then(saveSearch)
       .then(filterSearch)
       .then(bogusSearch)
       .then(incrementalSearch)
       .then(emptySearch)
       .then(() => closeDebuggerAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function htmlSearch() {
   let deferred = promise.defer();
 
--- a/devtools/client/debugger/test/mochitest/browser_dbg_searchbox-help-popup-01.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_searchbox-help-popup-01.js
@@ -24,17 +24,17 @@ function test() {
     gDebugger = gPanel.panelWin;
     gSearchBox = gDebugger.DebuggerView.Filtering._searchbox;
     gSearchBoxPanel = gDebugger.DebuggerView.Filtering._searchboxHelpPanel;
 
     waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
       .then(showPopup)
       .then(hidePopup)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     callInTab(gTab, "firstCall");
   });
 }
 
 function showPopup() {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_searchbox-help-popup-02.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_searchbox-help-popup-02.js
@@ -30,17 +30,17 @@ function test() {
       ok(false, "Damn it, this shouldn't have happened.");
     });
 
     waitForSourceAndCaretAndScopes(gPanel, "-02.js", 1)
       .then(tryShowPopup)
       .then(focusEditor)
       .then(testFocusLost)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     callInTab(gTab, "firstCall");
   });
 }
 
 function tryShowPopup() {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-01.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-01.js
@@ -29,17 +29,17 @@ function test() {
     checkSourceMapsEnabled();
 
     checkInitialSource();
     testSetBreakpoint()
       .then(testSetBreakpointBlankLine)
       .then(testHitBreakpoint)
       .then(testStepping)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function checkSourceMapsEnabled() {
   is(Services.prefs.getBoolPref("devtools.debugger.source-maps-enabled"), true,
     "The source maps functionality should be enabled by default.");
--- a/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-02.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-02.js
@@ -28,17 +28,17 @@ function test() {
     gPrefs = gDebugger.Prefs;
     gOptions = gDebugger.DebuggerView.Options;
 
     testToggleGeneratedSource()
       .then(testSetBreakpoint)
       .then(testToggleOnPause)
       .then(testResume)
       .then(() => closeDebuggerAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function testToggleGeneratedSource() {
   let finished = waitForSourceShown(gPanel, ".js").then(() => {
     is(gPrefs.sourceMapsEnabled, false,
--- a/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-03.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-03.js
@@ -24,17 +24,17 @@ function test() {
     gDebugger = gPanel.panelWin;
     gEditor = gDebugger.DebuggerView.editor;
     gSources = gDebugger.DebuggerView.Sources;
     gFrames = gDebugger.DebuggerView.StackFrames;
 
     checkInitialSource()
     testSetBreakpoint()
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function checkInitialSource() {
   isnot(gSources.selectedItem.attachment.source.url.indexOf(".js"), -1,
     "The debugger should not show the minified js file.");
--- a/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-04.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_source-maps-04.js
@@ -38,17 +38,17 @@ function test() {
     enablePauseOnExceptions()
       .then(disableIgnoreCaughtExceptions)
       .then(testSetBreakpoint)
       .then(reloadPage)
       .then(testHitBreakpoint)
       .then(enableIgnoreCaughtExceptions)
       .then(disablePauseOnExceptions)
       .then(() => closeDebuggerAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function checkInitialSource() {
   isnot(gSources.selectedItem.attachment.source.url.indexOf("code_math_bogus_map.js"), -1,
     "The debugger should show the minified js file.");
--- a/devtools/client/debugger/test/mochitest/browser_dbg_sources-contextmenu-01.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_sources-contextmenu-01.js
@@ -21,17 +21,17 @@ function test() {
     gTab = aTab;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gSources = gDebugger.DebuggerView.Sources;
 
     openContextMenu()
       .then(testCopyMenuItem)
       .then(() => closeDebuggerAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 
   function clickCopyURL() {
     return new Promise((resolve, reject) => {
       let copyURLMenuItem = gDebugger.document.getElementById("debugger-sources-context-copyurl");
       if (!copyURLMenuItem) {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_sources-contextmenu-02.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_sources-contextmenu-02.js
@@ -23,17 +23,17 @@ function test() {
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gSources = gDebugger.DebuggerView.Sources;
 
     openContextMenu()
       .then(testNewTabMenuItem)
       .then(testNewTabURI)
       .then(() => closeDebuggerAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 
   function testNewTabURI(tabUri) {
     is(tabUri, SCRIPT_URI, "The tab contains the right script.");
     gBrowser.removeCurrentTab();
   }
--- a/devtools/client/debugger/test/mochitest/browser_dbg_sources-keybindings.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_sources-keybindings.js
@@ -20,17 +20,17 @@ function test() {
   initDebugger(TAB_URL, options).then(([aTab,, aPanel]) => {
     gTab = aTab;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gSources = gDebugger.DebuggerView.Sources;
 
     testCopyURLShortcut()
       .then(() => closeDebuggerAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 
   function testCopyURLShortcut() {
     return waitForClipboardPromise(sendCopyShortcut, SCRIPT_URI);
   }
 
--- a/devtools/client/debugger/test/mochitest/browser_dbg_stack-contextmenu-02.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_stack-contextmenu-02.js
@@ -21,17 +21,17 @@ function test() {
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gFrames = gDebugger.DebuggerView.StackFrames;
 
     waitForDebuggerEvents(gPanel, gDebugger.EVENTS.AFTER_FRAMES_REFILLED)
      .then(openContextMenu)
      .then(testCopyStackMenuItem)
      .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-     .then(null, aError => {
+     .catch(aError => {
        ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
      });
     callInTab(gTab, "simpleCall");
   });
 
   function clickCopyStack() {
     return new Promise((resolve, reject) => {
       let copyStackMenuItem = gDebugger.document.getElementById("copyStackMenuItem");
--- a/devtools/client/debugger/test/mochitest/browser_dbg_tabactor-01.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_tabactor-01.js
@@ -27,17 +27,17 @@ function test() {
       "Root actor should identify itself as a browser.");
 
     addTab(TAB_URL)
       .then(() => attachTabActorForUrl(gClient, TAB_URL))
       .then(testTabActor)
       .then(closeTab)
       .then(() => gClient.close())
       .then(finish)
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function testTabActor([aGrip, aResponse]) {
   let deferred = promise.defer();
 
--- a/devtools/client/debugger/test/mochitest/browser_dbg_tabactor-02.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_tabactor-02.js
@@ -27,17 +27,17 @@ function test() {
       "Root actor should identify itself as a browser.");
 
     addTab(TAB_URL)
       .then(() => attachTabActorForUrl(gClient, TAB_URL))
       .then(testTabActor)
       .then(closeTab)
       .then(() => gClient.close())
       .then(finish)
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function testTabActor([aGrip, aResponse]) {
   let deferred = promise.defer();
 
--- a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-accessibility.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-accessibility.js
@@ -12,17 +12,17 @@ var gVariablesView;
 
 function test() {
   initDebugger().then(([aTab,, aPanel]) => {
     gTab = aTab;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gVariablesView = gDebugger.DebuggerView.Variables;
 
-    performTest().then(null, aError => {
+    performTest().catch(aError => {
       ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
     });
   });
 }
 
 function performTest() {
   let arr = [
     42,
--- a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-getset-01.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-getset-01.js
@@ -111,17 +111,17 @@ function test() {
       }))
       .then(() => deleteWatchExpression("myVar.prop + 42"))
       .then(() => testEdit("self", "0910", {
         "myVar.prop": 910
       }))
       .then(() => deleteLastWatchExpression("myVar.prop"))
       .then(() => testWatchExpressionsRemoved())
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     generateMouseClickInTab(gTab, "content.document.querySelector('button')");
   });
 }
 
 function addWatchExpressions() {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-getset-02.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-getset-02.js
@@ -32,17 +32,17 @@ function test() {
 
     gVars.switch = function () {};
     gVars.delete = function () {};
 
     waitForCaretAndScopes(gPanel, 24)
       .then(() => addWatchExpression())
       .then(() => testEdit("\"xlerb\"", "xlerb"))
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     generateMouseClickInTab(gTab, "content.document.querySelector('button')");
   });
 }
 
 function addWatchExpression() {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-value-01.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-value-01.js
@@ -34,17 +34,17 @@ function test() {
       .then(() => testModification("Infinity", "Infinity"))
       .then(() => testModification("NaN", "NaN"))
       .then(() => testModification("new Function", "anonymous()"))
       .then(() => testModification("+0", "0"))
       .then(() => testModification("-0", "-0"))
       .then(() => testModification("Object.keys({})", "Array[0]"))
       .then(() => testModification("document.title", '"Debugger test page"'))
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     generateMouseClickInTab(gTab, "content.document.querySelector('button')");
   });
 }
 
 function initialChecks() {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-value-02.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-value-02.js
@@ -29,17 +29,17 @@ function test() {
       let expanded = once(gVars, "fetched");
       obj.expand();
       return expanded;
     }).then(() => initialCheck("\u2028", "\\u2028", "8"))
       .then(() => initialCheck("\u2029", "\\u2029", "9"))
       .then(() => testModification("\u2028", "\\u2028", "123", "123"))
       .then(() => testModification("\u2029", "\\u2029", "456", "456"))
       .then(() => resumeDebuggerThenCloseAndFinish(panel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 }
 
 function getObjectScope() {
   return gVars.getScopeAtIndex(0).get("obj");
 }
--- a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-watch.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-edit-watch.js
@@ -41,17 +41,17 @@ function test() {
       .then(() => testIntegrity4())
       .then(() => testModification("document.title", "\ \t\r\n", "\"43\"", "44"))
       .then(() => testIntegrity5())
       .then(() => testExprDeletion("this", "44"))
       .then(() => testIntegrity6())
       .then(() => testExprFinalDeletion("ermahgerd", "44"))
       .then(() => testIntegrity7())
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     addExpressions();
     callInTab(gTab, "ermahgerd");
   });
 }
 
--- a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-01.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-01.js
@@ -32,17 +32,17 @@ function test() {
     // The first 'with' scope should be expanded by default, but the
     // variables haven't been fetched yet. This is how 'with' scopes work.
     promise.all([
       waitForCaretAndScopes(gPanel, 22),
       waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES)
     ]).then(prepareVariablesAndProperties)
       .then(testVariablesAndPropertiesFiltering)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     generateMouseClickInTab(gTab, "content.document.querySelector('button')");
   });
 }
 
 function testVariablesAndPropertiesFiltering() {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-02.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-02.js
@@ -32,17 +32,17 @@ function test() {
     // The first 'with' scope should be expanded by default, but the
     // variables haven't been fetched yet. This is how 'with' scopes work.
     promise.all([
       waitForCaretAndScopes(gPanel, 22),
       waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES)
     ]).then(prepareVariablesAndProperties)
       .then(testVariablesAndPropertiesFiltering)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     generateMouseClickInTab(gTab, "content.document.querySelector('button')");
   });
 }
 
 function testVariablesAndPropertiesFiltering() {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-03.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-03.js
@@ -31,17 +31,17 @@ function test() {
     // The first 'with' scope should be expanded by default, but the
     // variables haven't been fetched yet. This is how 'with' scopes work.
     promise.all([
       waitForCaretAndScopes(gPanel, 22),
       waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES)
     ]).then(prepareVariablesAndProperties)
       .then(testVariablesAndPropertiesFiltering)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     generateMouseClickInTab(gTab, "content.document.querySelector('button')");
   });
 }
 
 function testVariablesAndPropertiesFiltering() {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-04.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-04.js
@@ -32,17 +32,17 @@ function test() {
     // The first 'with' scope should be expanded by default, but the
     // variables haven't been fetched yet. This is how 'with' scopes work.
     promise.all([
       waitForCaretAndScopes(gPanel, 22),
       waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES)
     ]).then(prepareVariablesAndProperties)
       .then(testVariablesAndPropertiesFiltering)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     generateMouseClickInTab(gTab, "content.document.querySelector('button')");
   });
 }
 
 function testVariablesAndPropertiesFiltering() {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-05.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-filter-05.js
@@ -31,17 +31,17 @@ function test() {
     // The first 'with' scope should be expanded by default, but the
     // variables haven't been fetched yet. This is how 'with' scopes work.
     promise.all([
       waitForCaretAndScopes(gPanel, 22),
       waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES)
     ]).then(prepareVariablesAndProperties)
       .then(testVariablesAndPropertiesFiltering)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     generateMouseClickInTab(gTab, "content.document.querySelector('button')");
   });
 }
 
 function testVariablesAndPropertiesFiltering() {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-parameters-01.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-parameters-01.js
@@ -26,17 +26,17 @@ function test() {
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gVariables = gDebugger.DebuggerView.Variables;
 
     waitForCaretAndScopes(gPanel, 24)
       .then(initialChecks)
       .then(testExpandVariables)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     generateMouseClickInTab(gTab, "content.document.querySelector('button')");
   });
 }
 
 function initialChecks() {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-parameters-02.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-parameters-02.js
@@ -31,17 +31,17 @@ function test() {
       .then(testScopeVariables)
       .then(testArgumentsProperties)
       .then(testSimpleObject)
       .then(testComplexObject)
       .then(testArgumentObject)
       .then(testInnerArgumentObject)
       .then(testGetterSetterObject)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     generateMouseClickInTab(gTab, "content.document.querySelector('button')");
   });
 }
 
 function testScopeVariables() {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-parameters-03.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-parameters-03.js
@@ -28,17 +28,17 @@ function test() {
     gVariables = gDebugger.DebuggerView.Variables;
 
     waitForCaretAndScopes(gPanel, 24)
       .then(expandGlobalScope)
       .then(testGlobalScope)
       .then(expandWindowVariable)
       .then(testWindowVariable)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     generateMouseClickInTab(gTab, "content.document.querySelector('button')");
   });
 }
 
 function expandGlobalScope() {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-with.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-frame-with.js
@@ -29,17 +29,17 @@ function test() {
       waitForCaretAndScopes(gPanel, 22),
       waitForDebuggerEvents(gPanel, gDebugger.EVENTS.FETCHED_VARIABLES)
     ]).then(testFirstWithScope)
       .then(expandSecondWithScope)
       .then(testSecondWithScope)
       .then(expandFunctionScope)
       .then(testFunctionScope)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     generateMouseClickInTab(gTab, "content.document.querySelector('button')");
   });
 }
 
 function testFirstWithScope() {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-large-array-buffer.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-large-array-buffer.js
@@ -28,17 +28,17 @@ function test() {
     gTab = aTab;
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gVariables = gDebugger.DebuggerView.Variables;
 
     waitForCaretAndScopes(gPanel, 28, 1)
       .then(() => performTests())
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, error => {
+      .catch(error => {
         ok(false, "Got an error: " + error.message + "\n" + error.stack);
       });
 
     generateMouseClickInTab(gTab, "content.document.querySelector('button')");
   });
 }
 
 const VARS_TO_TEST = [
--- a/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-webidl.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_variables-view-webidl.js
@@ -26,17 +26,17 @@ function test() {
     gPanel = aPanel;
     gDebugger = gPanel.panelWin;
     gVariables = gDebugger.DebuggerView.Variables;
 
     waitForCaretAndScopes(gPanel, 24)
       .then(expandGlobalScope)
       .then(performTest)
       .then(() => resumeDebuggerThenCloseAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
 
     generateMouseClickInTab(gTab, "content.document.querySelector('button')");
   });
 }
 
 function expandGlobalScope() {
--- a/devtools/client/debugger/test/mochitest/browser_dbg_watch-expressions-02.js
+++ b/devtools/client/debugger/test/mochitest/browser_dbg_watch-expressions-02.js
@@ -28,17 +28,17 @@ function test() {
     gVariables = gDebugger.DebuggerView.Variables;
 
     gDebugger.DebuggerView.toggleInstrumentsPane({ visible: true, animated: false });
 
     addExpressions();
     performTest()
       .then(finishTest)
       .then(() => closeDebuggerAndFinish(gPanel))
-      .then(null, aError => {
+      .catch(aError => {
         ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
       });
   });
 
   function addExpressions() {
     // https://bugzilla.mozilla.org/show_bug.cgi?id=1205353
     // BZ#1205353 - wrong result for string replace with backslash-dollar.
     gWatch.addExpression("'$$$'.replace(/\\$/, 'foo')");
--- a/devtools/client/debugger/test/mochitest/doc_promise-get-allocation-stack.html
+++ b/devtools/client/debugger/test/mochitest/doc_promise-get-allocation-stack.html
@@ -10,15 +10,15 @@
 
   <body>
     <script type="text/javascript">
       function makePromises() {
         var p = new Promise(() => {});
         p.name = "p";
         var q = p.then();
         q.name = "q";
-        var r = p.then(null, () => {});
+        var r = p.catch(() => {});
         r.name = "r";
       }
     </script>
   </body>
 
 </html>
--- a/devtools/client/debugger/test/mochitest/doc_promise.html
+++ b/devtools/client/debugger/test/mochitest/doc_promise.html
@@ -18,13 +18,13 @@
       var p = window.pending;
       var f = window.fulfilled;
       var r = window.rejected;
       debugger;
     };
 
     // Attach an error handler so that the logs don't have a warning about an
     // unhandled, rejected promise.
-    window.rejected.then(null, function () {});
+    window.rejected.catch(function () {});
     </script>
   </body>
 
 </html>
--- a/devtools/client/debugger/views/variable-bubble-view.js
+++ b/devtools/client/debugger/views/variable-bubble-view.js
@@ -151,17 +151,17 @@ VariableBubbleView.prototype = {
             objectActor: frameFinished.return
           });
         } else {
           let msg = "Evaluation has thrown for: " + identifierInfo.evalString;
           console.warn(msg);
           dumpn(msg);
         }
       })
-      .then(null, err => {
+      .catch(err => {
         let msg = "Couldn't evaluate: " + err.message;
         console.error(msg);
         dumpn(msg);
       });
   },
 
   /**
    * Shows an inspection popup for a specified object actor grip.
--- a/devtools/client/devtools-startup.js
+++ b/devtools/client/devtools-startup.js
@@ -71,17 +71,17 @@ DevToolsStartup.prototype = {
   handleConsoleFlag: function (cmdLine) {
     let window = Services.wm.getMostRecentWindow("devtools:webconsole");
     if (!window) {
       this.initDevTools();
 
       let { require } = Cu.import("resource://devtools/shared/Loader.jsm", {});
       let hudservice = require("devtools/client/webconsole/hudservice");
       let { console } = Cu.import("resource://gre/modules/Console.jsm", {});
-      hudservice.toggleBrowserConsole().then(null, console.error);
+      hudservice.toggleBrowserConsole().catch(console.error);
     } else {
       // the Browser Console was already open
       window.focus();
     }
 
     if (cmdLine.state == Ci.nsICommandLine.STATE_REMOTE_AUTO) {
       cmdLine.preventDefault = true;
     }
--- a/devtools/client/framework/test/browser_toolbox_custom_host.js
+++ b/devtools/client/framework/test/browser_toolbox_custom_host.js
@@ -15,17 +15,17 @@ function test() {
   iframe = document.createElement("iframe");
   document.documentElement.appendChild(iframe);
 
   addTab(TEST_URL).then(function (tab) {
     target = TargetFactory.forTab(tab);
     let options = {customIframe: iframe};
     gDevTools.showToolbox(target, null, Toolbox.HostType.CUSTOM, options)
              .then(testCustomHost, console.error)
-             .then(null, console.error);
+             .catch(console.error);
   });
 
   function onMessage(event) {
     if (typeof(event.data) !== "string") {
       return;
     }
     info("onMessage: " + event.data);
     let json = JSON.parse(event.data);
--- a/devtools/client/framework/test/browser_toolbox_raise.js
+++ b/devtools/client/framework/test/browser_toolbox_raise.js
@@ -10,32 +10,32 @@ var {Toolbox} = require("devtools/client
 var toolbox, tab1, tab2;
 
 function test() {
   addTab(TEST_URL).then(tab => {
     tab2 = BrowserTestUtils.addTab(gBrowser);
     let target = TargetFactory.forTab(tab);
     gDevTools.showToolbox(target)
              .then(testBottomHost, console.error)
-             .then(null, console.error);
+             .catch(console.error);
   });
 }
 
 function testBottomHost(aToolbox) {
   toolbox = aToolbox;
 
   // switch to another tab and test toolbox.raise()
   gBrowser.selectedTab = tab2;
   executeSoon(function () {
     is(gBrowser.selectedTab, tab2, "Correct tab is selected before calling raise");
     toolbox.raise();
     executeSoon(function () {
       is(gBrowser.selectedTab, tab1, "Correct tab was selected after calling raise");
 
-      toolbox.switchHost(Toolbox.HostType.WINDOW).then(testWindowHost).then(null, console.error);
+      toolbox.switchHost(Toolbox.HostType.WINDOW).then(testWindowHost).catch(console.error);
     });
   });
 }
 
 function testWindowHost() {
   // Make sure toolbox is not focused.
   window.addEventListener("focus", onFocus, true);
 
--- a/devtools/client/framework/test/browser_toolbox_sidebar.js
+++ b/devtools/client/framework/test/browser_toolbox_sidebar.js
@@ -84,17 +84,17 @@ function test() {
         allTabsReady(panel);
       });
 
       panel.sidebar.addTab("tab1", tab1URL, {selected: true});
       panel.sidebar.addTab("tab2", tab2URL);
       panel.sidebar.addTab("tab3", tab3URL);
 
       panel.sidebar.show();
-    }).then(null, console.error);
+    }).catch(console.error);
   });
 
   function allTabsReady(panel) {
     if (!tab1Selected || !readyTabs.tab1 || !readyTabs.tab2 || !readyTabs.tab3) {
       return;
     }
 
     ok(registeredTabs.tab1, "tab1 registered");
--- a/devtools/client/framework/test/browser_toolbox_sidebar_events.js
+++ b/devtools/client/framework/test/browser_toolbox_sidebar_events.js
@@ -64,17 +64,17 @@ function test() {
 
       panel.sidebar.once("hide", function (event, id) {
         collectedEvents.push(event);
       });
 
       panel.sidebar.once("tab1-selected", () => finishUp(panel));
       panel.sidebar.addTab("tab1", tab1URL, {selected: true});
       panel.sidebar.show();
-    }).then(null, console.error);
+    }).catch(console.error);
   });
 
   function finishUp(panel) {
     panel.sidebar.hide();
     panel.sidebar.destroy();
 
     let events = collectedEvents.join(":");
     is(events, "sidebar-created:show:hide:sidebar-destroyed",
--- a/devtools/client/framework/toolbox.js
+++ b/devtools/client/framework/toolbox.js
@@ -505,17 +505,17 @@ Toolbox.prototype = {
       // so we don't have to explicitly wait for this in tests; ideally, all tests
       // will handle this on their own, but each have their own tear down function.
       if (flags.testing) {
         yield performanceFrontConnection;
       }
 
       this.emit("ready");
       this._isOpenDeferred.resolve();
-    }.bind(this)).then(null, console.error.bind(console));
+    }.bind(this)).catch(console.error.bind(console));
   },
 
   /**
    * loading React modules when needed (to avoid performance penalties
    * during Firefox start up time).
    */
   get React() {
     return this.browserRequire("devtools/client/shared/vendor/react");
@@ -2474,17 +2474,17 @@ Toolbox.prototype = {
 
           // Force GC to prevent long GC pauses when running tests and to free up
           // memory in general when the toolbox is closed.
           if (flags.testing) {
             win.QueryInterface(Ci.nsIInterfaceRequestor)
               .getInterface(Ci.nsIDOMWindowUtils)
               .garbageCollect();
           }
-        }).then(null, console.error));
+        }).catch(console.error));
 
     let leakCheckObserver = ({wrappedJSObject: barrier}) => {
       // Make the leak detector wait until this toolbox is properly destroyed.
       barrier.client.addBlocker("DevTools: Wait until toolbox is destroyed",
                                 this._destroyer);
     };
 
     let topic = "shutdown-leaks-before-check";
--- a/devtools/client/inspector/computed/computed.js
+++ b/devtools/client/inspector/computed/computed.js
@@ -507,17 +507,17 @@ CssComputedView.prototype = {
 
             this.inspector.emit("computed-view-refreshed");
             deferred.resolve(undefined);
           }
         }
       );
       this._refreshProcess.schedule();
       return deferred.promise;
-    }).then(null, (err) => console.error(err));
+    }).catch((err) => console.error(err));
   },
 
   /**
    * Handle the shortcut events in the computed view.
    */
   _onShortcut: function (name, event) {
     if (!event.target.closest("#sidebar-panel-computedview")) {
       return;
@@ -671,17 +671,17 @@ CssComputedView.prototype = {
         CssComputedView.propertyNames.push(prop);
       }
     }
 
     CssComputedView.propertyNames.sort();
     CssComputedView.propertyNames.push.apply(CssComputedView.propertyNames,
       mozProps.sort());
 
-    this._createPropertyViews().then(null, e => {
+    this._createPropertyViews().catch(e => {
       if (!this._isDestroyed) {
         console.warn("The creation of property views was cancelled because " +
           "the computed-view was destroyed before it was done creating views");
       } else {
         console.error(e);
       }
     });
   },
@@ -1103,17 +1103,17 @@ PropertyView.prototype = {
           }
 
           this._matchedSelectorResponse = matched;
 
           return this._buildMatchedSelectors().then(() => {
             this.matchedExpander.setAttribute("open", "");
             this.tree.inspector.emit("computed-view-property-expanded");
           });
-        }).then(null, console.error);
+        }).catch(console.error);
     }
 
     this.matchedSelectorsContainer.innerHTML = "";
     this.matchedExpander.removeAttribute("open");
     this.tree.inspector.emit("computed-view-property-collapsed");
     return promise.resolve(undefined);
   },
 
--- a/devtools/client/inspector/fonts/fonts.js
+++ b/devtools/client/inspector/fonts/fonts.js
@@ -165,20 +165,20 @@ FontInspector.prototype = {
       includePreviews: true,
       previewText: this.getPreviewText(),
       previewFillStyle: getColor("body-color")
     };
 
     let fonts = [];
     if (showAllFonts) {
       fonts = yield this.pageStyle.getAllUsedFontFaces(options)
-                      .then(null, console.error);
+                      .catch(console.error);
     } else {
       fonts = yield this.pageStyle.getUsedFontFaces(node, options)
-                      .then(null, console.error);
+                      .catch(console.error);
     }
 
     if (!fonts || !fonts.length) {
       // No fonts to display. Clear the previously shown fonts.
       this.clear();
       return;
     }
 
--- a/devtools/client/inspector/inspector.js
+++ b/devtools/client/inspector/inspector.js
@@ -2149,15 +2149,15 @@ if (window.location.protocol === "chrome
     let target = yield targetFromURL(url);
     let fakeToolbox = yield buildFakeToolbox(
       target,
       (toolbox) => attachThread(toolbox),
       { React, ReactDOM, browserRequire }
     );
     let inspectorUI = new Inspector(fakeToolbox);
     inspectorUI.init();
-  }).then(null, e => {
+  }).catch(e => {
     window.alert("Unable to start the inspector:" + e.message + "\n" + e.stack);
   });
 }
 
 exports.Inspector = Inspector;
 exports.buildFakeToolbox = buildFakeToolbox;
--- a/devtools/client/inspector/markup/markup.js
+++ b/devtools/client/inspector/markup/markup.js
@@ -899,17 +899,17 @@ MarkupView.prototype = {
             this.navigate(this.getContainer(focusNode));
           }
         });
       }, () => {
         let isValidSibling = nextSibling && !nextSibling.isPseudoElement;
         nextSibling = isValidSibling ? nextSibling : null;
         this.walker.insertBefore(node, parent, nextSibling);
       });
-    }).then(null, console.error);
+    }).catch(console.error);
   },
 
   /**
    * If an editable item is focused, select its container.
    */
   _onFocus: function (event) {
     let parent = event.target;
     while (!parent.container) {
@@ -1167,17 +1167,17 @@ MarkupView.prototype = {
     return this._expandContainer(container).then(() => {
       let child = container.children.firstChild;
       let promises = [];
       while (child) {
         promises.push(this._expandAll(child.container));
         child = child.nextSibling;
       }
       return promise.all(promises);
-    }).then(null, console.error);
+    }).catch(console.error);
   },
 
   /**
    * Expand the entire tree beneath a node.
    *
    * @param  {DOMNode} node
    *         The node to expand, or null to start from the top.
    */
@@ -1210,17 +1210,17 @@ MarkupView.prototype = {
     if (isOuter) {
       walkerPromise = this.walker.outerHTML(node);
     } else {
       walkerPromise = this.walker.innerHTML(node);
     }
 
     return walkerPromise.then(longstr => {
       return longstr.string().then(html => {
-        longstr.release().then(null, console.error);
+        longstr.release().catch(console.error);
         return html;
       });
     });
   },
 
   /**
    * Retrieve the outerHTML for a remote node.
    *
@@ -1327,17 +1327,17 @@ MarkupView.prototype = {
     let container = this.getContainer(node);
     if (!container) {
       return promise.reject();
     }
 
     // Changing the outerHTML removes the node which outerHTML was changed.
     // Listen to this removal to reselect the right node afterwards.
     this.reselectOnRemoved(node, "outerhtml");
-    return this.walker.setOuterHTML(node, newValue).then(null, () => {
+    return this.walker.setOuterHTML(node, newValue).catch(() => {
       this.cancelReselectOnRemoved();
     });
   },
 
   /**
    * Replace the innerHTML of any node displayed in the inspector with
    * some other HTML code
    * @param  {Node} node
--- a/devtools/client/inspector/markup/views/element-editor.js
+++ b/devtools/client/inspector/markup/views/element-editor.js
@@ -606,17 +606,17 @@ ElementEditor.prototype = {
         newTagName.toLowerCase() === this.node.tagName.toLowerCase() ||
         !("editTagName" in this.markup.walker)) {
       return;
     }
 
     // Changing the tagName removes the node. Make sure the replacing node gets
     // selected afterwards.
     this.markup.reselectOnRemoved(this.node, "edittagname");
-    this.markup.walker.editTagName(this.node, newTagName).then(null, () => {
+    this.markup.walker.editTagName(this.node, newTagName).catch(() => {
       // Failed to edit the tag name, cancel the reselection.
       this.markup.cancelReselectOnRemoved();
     });
   },
 
   destroy: function () {
     for (let key in this.animationTimers) {
       clearTimeout(this.animationTimers[key]);
--- a/devtools/client/inspector/markup/views/text-editor.js
+++ b/devtools/client/inspector/markup/views/text-editor.js
@@ -39,17 +39,17 @@ function TextEditor(container, node, typ
     maxWidth: () => getAutocompleteMaxWidth(this.value, this.container.elt),
     trimOutput: false,
     done: (val, commit) => {
       if (!commit) {
         return;
       }
       this.node.getNodeValue().then(longstr => {
         longstr.string().then(oldValue => {
-          longstr.release().then(null, console.error);
+          longstr.release().catch(console.error);
 
           this.container.undo.do(() => {
             this.node.setNodeValue(val);
           }, () => {
             this.node.setNodeValue(oldValue);
           });
         });
       });
@@ -101,29 +101,29 @@ TextEditor.prototype = {
   },
 
   update: function () {
     let longstr = null;
     this.node.getNodeValue().then(ret => {
       longstr = ret;
       return longstr.string();
     }).then(str => {
-      longstr.release().then(null, console.error);
+      longstr.release().catch(console.error);
       this.value.textContent = str;
 
       let isWhitespace = !/[^\s]/.exec(str);
       this.value.classList.toggle("whitespace", isWhitespace);
 
       let chars = str.replace(/\n/g, "⏎")
                      .replace(/\t/g, "⇥")
                      .replace(/ /g, "◦");
       this.value.setAttribute("title", isWhitespace
         ? INSPECTOR_L10N.getFormatStr("markupView.whitespaceOnly", chars)
         : "");
-    }).then(null, console.error);
+    }).catch(console.error);
   },
 
   destroy: function () {},
 
   /**
    * Stub method for consistency with ElementEditor.
    */
   getInfoAtNode: function () {
--- a/devtools/client/inspector/rules/models/element-style.js
+++ b/devtools/client/inspector/rules/models/element-style.js
@@ -119,17 +119,17 @@ ElementStyle.prototype = {
       // We're done with the previous list of rules.
       for (let r of existingRules) {
         if (r && r.editor) {
           r.editor.destroy();
         }
       }
 
       return undefined;
-    }).then(null, e => {
+    }).catch(e => {
       // populate is often called after a setTimeout,
       // the connection may already be closed.
       if (this.destroyed) {
         return promise.resolve(undefined);
       }
       return promiseWarn(e);
     });
     this.populated = populated;
--- a/devtools/client/inspector/rules/rules.js
+++ b/devtools/client/inspector/rules/rules.js
@@ -773,17 +773,17 @@ CssRuleView.prototype = {
     // engine, we will set properties on a dummy element and observe
     // how their .style attribute reflects them as computed values.
     let dummyElementPromise = promise.resolve(this.styleDocument).then(document => {
       // ::before and ::after do not have a namespaceURI
       let namespaceURI = this.element.namespaceURI ||
           document.documentElement.namespaceURI;
       this._dummyElement = document.createElementNS(namespaceURI,
                                                    this.element.tagName);
-    }).then(null, promiseWarn);
+    }).catch(promiseWarn);
 
     let elementStyle = new ElementStyle(element, this, this.store,
       this.pageStyle, this.showUserAgentStyles);
     this._elementStyle = elementStyle;
 
     this._startSelectingElement();
 
     return dummyElementPromise.then(() => {
@@ -796,17 +796,17 @@ CssRuleView.prototype = {
         if (!refresh) {
           this.element.scrollTop = 0;
         }
         this._stopSelectingElement();
         this._elementStyle.onChanged = () => {
           this._changed();
         };
       }
-    }).then(null, e => {
+    }).catch(e => {
       if (this._elementStyle === elementStyle) {
         this._stopSelectingElement();
         this._clearRules();
       }
       console.error(e);
     });
   },
 
@@ -881,17 +881,17 @@ CssRuleView.prototype = {
       this._clearRules();
       let onEditorsReady = this._createEditors();
       this.refreshPseudoClassPanel();
 
       // Notify anyone that cares that we refreshed.
       return onEditorsReady.then(() => {
         this.emit("ruleview-refreshed");
       }, e => console.error(e));
-    }).then(null, promiseWarn);
+    }).catch(promiseWarn);
   },
 
   /**
    * Show the user that the rule view has no node selected.
    */
   _showEmpty: function () {
     if (this.styleDocument.getElementById("ruleview-no-results")) {
       return;
--- a/devtools/client/inspector/shared/highlighters-overlay.js
+++ b/devtools/client/inspector/shared/highlighters-overlay.js
@@ -326,17 +326,17 @@ HighlightersOverlay.prototype = {
     }
 
     // For some reason, the call to highlighter.hide doesn't always return a
     // promise. This causes some tests to fail when trying to install a
     // rejection handler on the result of the call. To avoid this, check
     // whether the result is truthy before installing the handler.
     let onHidden = this.highlighters[this.hoveredHighlighterShown].hide();
     if (onHidden) {
-      onHidden.then(null, e => console.error(e));
+      onHidden.catch(e => console.error(e));
     }
 
     this.hoveredHighlighterShown = null;
     this.emit("highlighter-hidden");
   },
 
   /**
    * Is the current hovered node a css transform property value in the
--- a/devtools/client/jsonview/components/json-panel.js
+++ b/devtools/client/jsonview/components/json-panel.js
@@ -3,29 +3,34 @@
 /* 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";
 
 define(function (require, exports, module) {
   const { DOM: dom, createFactory, createClass, PropTypes } = require("devtools/client/shared/vendor/react");
-  const TreeView = createFactory(require("devtools/client/shared/components/tree/tree-view"));
+  const TreeViewClass = require("devtools/client/shared/components/tree/tree-view");
+  const TreeView = createFactory(TreeViewClass);
 
   const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
   const { createFactories } = require("devtools/client/shared/react-utils");
   const { Rep } = REPS;
 
   const { SearchBox } = createFactories(require("./search-box"));
   const { Toolbar, ToolbarButton } = createFactories(require("./reps/toolbar"));
 
   const { div } = dom;
   const AUTO_EXPAND_MAX_SIZE = 100 * 1024;
   const AUTO_EXPAND_MAX_LEVEL = 7;
 
+  function isObject(value) {
+    return Object(value) === value;
+  }
+
   /**
    * This template represents the 'JSON' panel. The panel is
    * responsible for rendering an expandable tree that allows simple
    * inspection of JSON structure.
    */
   let JsonPanel = createClass({
     displayName: "JsonPanel",
 
@@ -60,43 +65,21 @@ define(function (require, exports, modul
       if (!this.props.searchFilter) {
         return true;
       }
 
       let json = object.name + JSON.stringify(object.value);
       return json.toLowerCase().indexOf(this.props.searchFilter.toLowerCase()) >= 0;
     },
 
-    getExpandedNodes: function (object, path = "", level = 0) {
-      if (typeof object != "object") {
-        return null;
-      }
-
-      if (level > AUTO_EXPAND_MAX_LEVEL) {
-        return null;
-      }
-
-      let expandedNodes = new Set();
-      for (let prop in object) {
-        let nodePath = path + "/" + prop;
-        expandedNodes.add(nodePath);
-
-        let nodes = this.getExpandedNodes(object[prop], nodePath, level + 1);
-        if (nodes) {
-          expandedNodes = new Set([...expandedNodes, ...nodes]);
-        }
-      }
-      return expandedNodes;
-    },
-
     renderValue: props => {
       let member = props.member;
 
-      // Hide object summary when object is expanded (bug 1244912).
-      if (typeof member.value == "object" && member.open) {
+      // Hide object summary when non-empty object is expanded (bug 1244912).
+      if (isObject(member.value) && member.hasChildren && member.open) {
         return null;
       }
 
       // Render the value (summary) using Reps library.
       return Rep(Object.assign({}, props, {
         cropLimit: 50,
       }));
     },
@@ -107,17 +90,20 @@ define(function (require, exports, modul
       let columns = [{
         id: "value",
         width: "100%"
       }];
 
       // Expand the document by default if its size isn't bigger than 100KB.
       let expandedNodes = new Set();
       if (this.props.jsonTextLength <= AUTO_EXPAND_MAX_SIZE) {
-        expandedNodes = this.getExpandedNodes(this.props.data);
+        expandedNodes = TreeViewClass.getExpandedNodes(
+          this.props.data,
+          {maxLevel: AUTO_EXPAND_MAX_LEVEL}
+        );
       }
 
       // Render tree component.
       return TreeView({
         object: this.props.data,
         mode: MODE.TINY,
         onFilter: this.onFilter,
         columns: columns,
@@ -126,17 +112,17 @@ define(function (require, exports, modul
       });
     },
 
     render: function () {
       let content;
       let data = this.props.data;
 
       try {
-        if (typeof data == "object") {
+        if (isObject(data)) {
           content = this.renderTree();
         } else {
           content = div({className: "jsonParseError"},
             data + ""
           );
         }
       } catch (err) {
         content = div({className: "jsonParseError"},
--- a/devtools/client/jsonview/test/browser.ini
+++ b/devtools/client/jsonview/test/browser.ini
@@ -22,19 +22,21 @@ support-files =
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_jsonview_copy_json.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
 [browser_jsonview_copy_rawdata.js]
 subsuite = clipboard
 skip-if = (os == 'linux' && bits == 32 && debug) # bug 1328915, disable linux32 debug devtools for timeouts
+[browser_jsonview_empty_object.js]
 [browser_jsonview_filter.js]
 [browser_jsonview_invalid_json.js]
 [browser_jsonview_manifest.js]
 [browser_jsonview_nojs.js]
 [browser_jsonview_nul.js]
 [browser_jsonview_save_json.js]
 support-files =
   !/toolkit/content/tests/browser/common/mockTransfer.js
+[browser_jsonview_slash.js]
 [browser_jsonview_utf8.js]
 [browser_jsonview_valid_json.js]
 [browser_json_refresh.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/jsonview/test/browser_jsonview_empty_object.js
@@ -0,0 +1,48 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+function testRootObject(objExpr, summary = objExpr) {
+  return function* () {
+    info("Test JSON with root empty object " + objExpr + " started");
+
+    let TEST_JSON_URL = "data:application/json," + objExpr;
+    yield addJsonViewTab(TEST_JSON_URL);
+
+    let objectText = yield getElementText(
+      ".jsonPanelBox .panelContent");
+    is(objectText, summary, "The root object " + objExpr + " is visible");
+  };
+}
+
+function testNestedObject(objExpr, summary = objExpr) {
+  return function* () {
+    info("Test JSON with nested empty object " + objExpr + " started");
+
+    let TEST_JSON_URL = "data:application/json,[" + objExpr + "]";
+    yield addJsonViewTab(TEST_JSON_URL);
+
+    let objectCellCount = yield getElementCount(
+      ".jsonPanelBox .treeTable .objectCell");
+    is(objectCellCount, 1, "There must be one object cell");
+
+    let objectCellText = yield getElementText(
+      ".jsonPanelBox .treeTable .objectCell");
+    is(objectCellText, summary, objExpr + " has a visible summary");
+
+    // Collapsed auto-expanded node.
+    yield clickJsonNode(".jsonPanelBox .treeTable .treeLabel");
+
+    let textAfter = yield getElementText(
+      ".jsonPanelBox .treeTable .objectCell");
+    is(textAfter, summary, objExpr + " still has a visible summary");
+  };
+}
+
+add_task(testRootObject("null"));
+add_task(testNestedObject("null"));
+add_task(testNestedObject("[]"));
+add_task(testNestedObject("{}", "Object"));
new file mode 100644
--- /dev/null
+++ b/devtools/client/jsonview/test/browser_jsonview_slash.js
@@ -0,0 +1,16 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+add_task(function* () {
+  info("Test JSON with NUL started.");
+
+  const TEST_JSON_URL = "data:application/json,{\"a/b\":[1,2],\"a\":{\"b\":[3,4]}}";
+  yield addJsonViewTab(TEST_JSON_URL);
+
+  let countBefore = yield getElementCount(".jsonPanelBox .treeTable .treeRow");
+  ok(countBefore == 7, "There must be seven rows");
+});
--- a/devtools/client/netmonitor/src/components/properties-view.js
+++ b/devtools/client/netmonitor/src/components/properties-view.js
@@ -15,17 +15,18 @@ const {
 
 const { REPS, MODE } = require("devtools/client/shared/components/reps/reps");
 const { Rep } = REPS;
 
 const { FILTER_SEARCH_DELAY } = require("../constants");
 
 // Components
 const SearchBox = createFactory(require("devtools/client/shared/components/search-box"));
-const TreeView = createFactory(require("devtools/client/shared/components/tree/tree-view"));
+const TreeViewClass = require("devtools/client/shared/components/tree/tree-view");
+const TreeView = createFactory(TreeViewClass);
 const TreeRow = createFactory(require("devtools/client/shared/components/tree/tree-row"));
 const SourceEditor = createFactory(require("./source-editor"));
 
 const { div, tr, td } = DOM;
 const AUTO_EXPAND_MAX_LEVEL = 7;
 const AUTO_EXPAND_MAX_NODES = 50;
 const EDITOR_CONFIG_ID = "EDITOR_CONFIG";
 
@@ -132,48 +133,16 @@ const PropertiesView = createClass({
   },
 
   updateFilterText(filterText) {
     this.setState({
       filterText,
     });
   },
 
-  getExpandedNodes: function (object, path = "", level = 0) {
-    if (typeof object != "object") {
-      return null;
-    }
-
-    if (level > AUTO_EXPAND_MAX_LEVEL) {
-      return null;
-    }
-
-    let expandedNodes = new Set();
-    for (let prop in object) {
-      if (expandedNodes.size > AUTO_EXPAND_MAX_NODES) {
-        // If we reached the limit of expandable nodes, bail out to avoid performance
-        // issues.
-        break;
-      }
-
-      let nodePath = path + "/" + prop;
-      expandedNodes.add(nodePath);
-
-      let nodes = this.getExpandedNodes(object[prop], nodePath, level + 1);
-      if (nodes) {
-        let newSize = expandedNodes.size + nodes.size;
-        if (newSize < AUTO_EXPAND_MAX_NODES) {
-          // Avoid having a subtree half expanded.
-          expandedNodes = new Set([...expandedNodes, ...nodes]);
-        }
-      }
-    }
-    return expandedNodes;
-  },
-
   render() {
     const {
       decorator,
       enableInput,
       expandableStrings,
       filterPlaceHolder,
       object,
       renderRow,
@@ -200,17 +169,20 @@ const PropertiesView = createClass({
               width: "100%",
             }],
             decorator: decorator || {
               getRowClass: (rowObject) => this.getRowClass(rowObject, sectionNames),
             },
             enableInput,
             expandableStrings,
             useQuotes: false,
-            expandedNodes: this.getExpandedNodes(object),
+            expandedNodes: TreeViewClass.getExpandedNodes(
+              object,
+              {maxLevel: AUTO_EXPAND_MAX_LEVEL, maxNodes: AUTO_EXPAND_MAX_NODES}
+            ),
             onFilter: (props) => this.onFilter(props, sectionNames),
             renderRow: renderRow || this.renderRowWithEditor,
             renderValue: renderValue || this.renderValueWithRep,
           }),
         ),
       )
     );
   }
--- a/devtools/client/netmonitor/src/components/security-panel.js
+++ b/devtools/client/netmonitor/src/components/security-panel.js
@@ -8,16 +8,17 @@ const {
   createFactory,
   DOM,
   PropTypes,
 } = require("devtools/client/shared/vendor/react");
 const { L10N } = require("../utils/l10n");
 const { getUrlHost } = require("../utils/request-utils");
 
 // Components
+const TreeViewClass = require("devtools/client/shared/components/tree/tree-view");
 const PropertiesView = createFactory(require("./properties-view"));
 
 const { div, input, span } = DOM;
 
 /*
  * Security panel component
  * If the site is being served over HTTPS, you get an extra tab labeled "Security".
  * This contains details about the secure connection used including the protocol,
@@ -90,17 +91,17 @@ function SecurityPanel({ request }) {
     };
   }
 
   return div({ className: "panel-container security-panel" },
     PropertiesView({
       object,
       renderValue: (props) => renderValue(props, securityInfo.weaknessReasons),
       enableFilter: false,
-      expandedNodes: getExpandedNodes(object),
+      expandedNodes: TreeViewClass.getExpandedNodes(object),
     })
   );
 }
 
 SecurityPanel.displayName = "SecurityPanel";
 
 SecurityPanel.propTypes = {
   request: PropTypes.object.isRequired,
@@ -134,27 +135,9 @@ function renderValue(props, weaknessReas
         className: "security-warning-icon",
         title: L10N.getStr("netmonitor.security.warning.cipher"),
       })
       :
       null
   );
 }
 
-function getExpandedNodes(object, path = "", level = 0) {
-  if (typeof object !== "object") {
-    return null;
-  }
-
-  let expandedNodes = new Set();
-  for (let prop in object) {
-    let nodePath = path + "/" + prop;
-    expandedNodes.add(nodePath);
-
-    let nodes = getExpandedNodes(object[prop], nodePath, level + 1);
-    if (nodes) {
-      expandedNodes = new Set([...expandedNodes, ...nodes]);
-    }
-  }
-  return expandedNodes;
-}
-
 module.exports = SecurityPanel;
--- a/devtools/client/netmonitor/src/har/har-exporter.js
+++ b/devtools/client/netmonitor/src/har/har-exporter.js
@@ -153,17 +153,17 @@ const HarExporter = {
       if (options.jsonp) {
         // This callback name is also used in HAR Viewer by default.
         // http://www.softwareishard.com/har/viewer/
         let callbackName = options.jsonpCallback || "onInputData";
         jsonString = callbackName + "(" + jsonString + ");";
       }
 
       return jsonString;
-    }).then(null, function onError(err) {
+    }).catch(function onError(err) {
       console.error(err);
     });
   },
 
   /**
    * Build HAR data object. This object contains all HTTP data
    * collected by the Network panel. The process is asynchronous
    * since it can involve additional RDP communication (e.g. resolving
--- a/devtools/client/scratchpad/scratchpad.js
+++ b/devtools/client/scratchpad/scratchpad.js
@@ -690,17 +690,17 @@ var Scratchpad = {
     const tabsize = Services.prefs.getIntPref(TAB_SIZE);
 
     return this.prettyPrintWorker.performTask("pretty-print", {
       url: "(scratchpad)",
       indent: tabsize,
       source: uglyText
     }).then(data => {
       this.editor.setText(data.code);
-    }).then(null, error => {
+    }).catch(error => {
       this.writeAsErrorComment({ exception: error });
       throw error;
     });
   },
 
   /**
    * Parse the text and return an AST. If we can't parse it, write an error
    * comment and return false.
@@ -1730,17 +1730,17 @@ var Scratchpad = {
       if (state)
         this.dirty = !state.saved;
 
       this.initialized = true;
       this._triggerObservers("Ready");
       this.populateRecentFilesMenu();
       PreferenceObserver.init();
       CloseObserver.init();
-    }).then(null, (err) => console.error(err));
+    }).catch((err) => console.error(err));
     this._setupCommandListeners();
     this._updateViewMenuItems();
     this._setupPopupShowingListeners();
 
     // Change the accesskey for the help menu as it can be specific on Windows
     // some localizations of Windows (ex:french, german) use "?"
     //  for the help button in the menubar but Gnome does not.
     if (Services.appinfo.OS == "WINNT") {
--- a/devtools/client/scratchpad/test/browser_scratchpad_pprint-02.js
+++ b/devtools/client/scratchpad/test/browser_scratchpad_pprint-02.js
@@ -23,17 +23,17 @@ function runTests(sw)
   const space = " ".repeat(6);
 
   const sp = sw.Scratchpad;
   sp.setText("function main() { console.log(5); }");
   sp.prettyPrint().then(() => {
     const prettyText = sp.getText();
     ok(prettyText.includes(space));
     finish();
-  }).then(null, error => {
+  }).catch(error => {
     ok(false, error);
   });
 }
 
 registerCleanupFunction(function () {
   Services.prefs.setIntPref("devtools.editor.tabsize", gTabsize);
   gTabsize = null;
 });
--- a/devtools/client/scratchpad/test/browser_scratchpad_pprint.js
+++ b/devtools/client/scratchpad/test/browser_scratchpad_pprint.js
@@ -17,12 +17,12 @@ function test()
 function runTests(sw)
 {
   const sp = sw.Scratchpad;
   sp.setText("function main() { console.log(5); }");
   sp.prettyPrint().then(() => {
     const prettyText = sp.getText();
     ok(prettyText.includes("\n"));
     finish();
-  }).then(null, error => {
+  }).catch(error => {
     ok(false, error);
   });
 }
--- a/devtools/client/shadereditor/panel.js
+++ b/devtools/client/shadereditor/panel.js
@@ -45,17 +45,17 @@ ShaderEditorPanel.prototype = {
         this.panelWin.gFront = new WebGLFront(this.target.client, this.target.form);
         return this.panelWin.startupShaderEditor();
       })
       .then(() => {
         this.isReady = true;
         this.emit("ready");
         return this;
       })
-      .then(null, function onError(aReason) {
+      .catch(function onError(aReason) {
         DevToolsUtils.reportException("ShaderEditorPanel.prototype.open", aReason);
       });
   },
 
   // DevToolPanel API
 
   get target() {
     return this._toolbox.target;
--- a/devtools/client/shadereditor/shadereditor.js
+++ b/devtools/client/shadereditor/shadereditor.js
@@ -308,17 +308,17 @@ var ShadersListView = Heritage.extend(Wi
         vs: vertexShaderText,
         fs: fragmentShaderText
       });
     }
 
     getShaders()
       .then(getSources)
       .then(showSources)
-      .then(null, e => console.error(e));
+      .catch(e => console.error(e));
   },
 
   /**
    * The check listener for the programs container.
    */
   _onProgramCheck: function ({ detail: { checked }, target }) {
     let sourceItem = this.getItemForElement(target);
     let attachment = sourceItem.attachment;
--- a/devtools/client/shadereditor/test/head.js
+++ b/devtools/client/shadereditor/test/head.js
@@ -113,17 +113,17 @@ function ifWebGLSupported() {
 
 function ifWebGLUnsupported() {
   todo(false, "Skipping test because WebGL isn't supported.");
   finish();
 }
 
 function test() {
   let generator = isWebGLSupported(document) ? ifWebGLSupported : ifWebGLUnsupported;
-  Task.spawn(generator).then(null, handleError);
+  Task.spawn(generator).catch(handleError);
 }
 
 function createCanvas() {
   return document.createElementNS("http://www.w3.org/1999/xhtml", "canvas");
 }
 
 function once(aTarget, aEventName, aUseCapture = false) {
   info("Waiting for event: '" + aEventName + "' on " + aTarget + ".");
--- a/devtools/client/shared/components/tree/tree-view.js
+++ b/devtools/client/shared/components/tree/tree-view.js
@@ -272,17 +272,17 @@ define(function (require, exports, modul
       if (!Array.isArray(children)) {
         return children;
       }
 
       children = this.onSort(parent, children) || children;
 
       return children.map(child => {
         let key = provider.getKey(child);
-        let nodePath = path + "/" + key;
+        let nodePath = TreeView.subPath(path, key);
         let type = provider.getType(child);
         let hasChildren = provider.hasChildren(child);
 
         // Value with no column specified is used for optimization.
         // The row is re-rendered only if this value changes.
         // Value for actual column is get when a cell is rendered.
         let value = provider.getValue(child);
 
@@ -414,16 +414,65 @@ define(function (require, exports, modul
           DOM.tbody({
             role: "presentation"
           }, rows)
         )
       );
     }
   });
 
+  TreeView.subPath = function (path, subKey) {
+    return path + "/" + String(subKey).replace(/[\\/]/g, "\\$&");
+  };
+
+  /**
+   * Creates a set with the paths of the nodes that should be expanded by default
+   * according to the passed options.
+   * @param {Object} The root node of the tree.
+   * @param {Object} [optional] An object with the following optional parameters:
+   *   - maxLevel: nodes nested deeper than this level won't be expanded.
+   *   - maxNodes: maximum number of nodes that can be expanded. The traversal is
+         breadth-first, so expanding nodes nearer to the root will be preferred.
+         Sibling nodes will either be all expanded or none expanded.
+   * }
+   */
+  TreeView.getExpandedNodes = function (rootObj,
+    { maxLevel = Infinity, maxNodes = Infinity } = {}
+  ) {
+    let expandedNodes = new Set();
+    let queue = [{
+      object: rootObj,
+      level: 1,
+      path: ""
+    }];
+    while (queue.length) {
+      let {object, level, path} = queue.shift();
+      if (Object(object) !== object) {
+        continue;
+      }
+      let keys = Object.keys(object);
+      if (expandedNodes.size + keys.length > maxNodes) {
+        // Avoid having children half expanded.
+        break;
+      }
+      for (let key of keys) {
+        let nodePath = TreeView.subPath(path, key);
+        expandedNodes.add(nodePath);
+        if (level < maxLevel) {
+          queue.push({
+            object: object[key],
+            level: level + 1,
+            path: nodePath
+          });
+        }
+      }
+    }
+    return expandedNodes;
+  };
+
   // Helpers
 
   /**
    * There should always be at least one column (the one with toggle buttons)
    * and this function ensures that it's true.
    */
   function ensureDefaultColumn(columns) {
     if (!columns) {
--- a/devtools/client/shared/developer-toolbar.js
+++ b/devtools/client/shared/developer-toolbar.js
@@ -1001,17 +1001,17 @@ OutputPanel.prototype._outputChanged = f
   this.remove();
 
   this.displayedOutput = ev.output;
 
   if (this.displayedOutput.completed) {
     this._update();
   } else {
     this.displayedOutput.promise.then(this._update, this._update)
-                                .then(null, console.error);
+                                .catch(console.error);
   }
 };
 
 /**
  * Called when displayed Output says it's changed or from outputChanged, which
  * happens when there is a new displayed Output.
  */
 OutputPanel.prototype._update = function () {
--- a/devtools/client/shared/redux/middleware/task.js
+++ b/devtools/client/shared/redux/middleware/task.js
@@ -14,17 +14,17 @@ const ERROR_TYPE = exports.ERROR_TYPE = 
  * asynchronously) and yield on each. If called with a promise, calls `dispatch`
  * on the results.
  */
 
 function task({ dispatch, getState }) {
   return next => action => {
     if (isGenerator(action)) {
       return Task.spawn(action.bind(null, dispatch, getState))
-        .then(null, handleError.bind(null, dispatch));
+        .catch(handleError.bind(null, dispatch));
     }
 
     /*
     if (isPromise(action)) {
       return action.then(dispatch, handleError.bind(null, dispatch));
     }
     */
 
--- a/devtools/client/sourceeditor/autocomplete.js
+++ b/devtools/client/sourceeditor/autocomplete.js
@@ -232,17 +232,17 @@ function autoComplete({ ed, cm }) {
     popup.setItems(suggestions);
 
     popup.once("popup-opened", () => {
       // This event is used in tests.
       ed.emit("after-suggest");
     });
     popup.openPopup(cursorElement, -1 * left, 0);
     autocompleteOpts.suggestionInsertedOnce = false;
-  }).then(null, e => console.error(e));
+  }).catch(e => console.error(e));
 }
 
 /**
  * Inserts a popup item into the current cursor location
  * in the editor.
  */
 function insertPopupItem(ed, popupItem) {
   let {preLabel, text} = popupItem;
--- a/devtools/client/styleeditor/StyleEditorUI.jsm
+++ b/devtools/client/styleeditor/StyleEditorUI.jsm
@@ -229,17 +229,17 @@ StyleEditorUI.prototype = {
    * @param {string} event
    *        Event name
    * @param {StyleSheet} styleSheet
    *        StyleSheet object for new sheet
    */
   _onNewDocument: function () {
     this._debuggee.getStyleSheets().then((styleSheets) => {
       return this._resetStyleSheetList(styleSheets);
-    }).then(null, e => console.error(e));
+    }).catch(e => console.error(e));
   },
 
   /**
    * Add editors for all the given stylesheets to the UI.
    *
    * @param  {array} styleSheets
    *         Array of StyleSheetFront
    */
@@ -629,17 +629,17 @@ StyleEditorUI.prototype = {
             let lineCount = editorText.split("\n").length;
             let ruleCount = showEditor.styleSheet.ruleCount;
             if (lineCount >= ruleCount) {
               showEditor.addUnusedRegions(reports);
             } else {
               this.emit("error", { key: "error-compressed", level: "info" });
             }
           }
-        }.bind(this)).then(null, e => console.error(e));
+        }.bind(this)).catch(e => console.error(e));
       }
     });
   },
 
   /**
    * Switch to the editor that has been marked to be selected.
    *
    * @return {Promise}
@@ -919,17 +919,17 @@ StyleEditorUI.prototype = {
         div.appendChild(link);
 
         list.appendChild(div);
       }
 
       sidebar.hidden = !showSidebar || !inSource;
 
       this.emit("media-list-changed", editor);
-    }.bind(this)).then(null, e => console.error(e));
+    }.bind(this)).catch(e => console.error(e));
   },
 
   /**
     * Called when a media condition is clicked
     * If a responsive mode link is clicked, it will launch it.
     *
     * @param {object} e
     *        Event object
--- a/devtools/client/styleeditor/StyleSheetEditor.jsm
+++ b/devtools/client/styleeditor/StyleSheetEditor.jsm
@@ -285,17 +285,17 @@ StyleSheetEditor.prototype = {
    * @return {Promise}
    *         A promise that'll resolve with the source text once the source
    *         has been loaded or reject on unexpected error.
    */
   fetchSource: function () {
     return this._getSourceTextAndPrettify().then((source) => {
       this.sourceLoaded = true;
       return source;
-    }).then(null, e => {
+    }).catch(e => {
       if (this._isDestroyed) {
         console.warn("Could not fetch the source for " +
                      this.styleSheet.href +
                      ", the editor was destroyed");
         console.error(e);
       } else {
         this.emit("error", { key: LOAD_ERROR, append: this.styleSheet.href });
         throw e;
@@ -513,17 +513,17 @@ StyleSheetEditor.prototype = {
     }
     this.focus();
   },
 
   /**
    * Toggled the disabled state of the underlying stylesheet.
    */
   toggleDisabled: function () {
-    this.styleSheet.toggleDisabled().then(null, e => console.error(e));
+    this.styleSheet.toggleDisabled().catch(e => console.error(e));
   },
 
   /**
    * Queue a throttled task to update the live style sheet.
    */
   updateStyleSheet: function () {
     if (this._updateTask) {
       // cancel previous queued task not executed within throttle delay
@@ -555,17 +555,17 @@ StyleSheetEditor.prototype = {
     this._updateTask = null;
 
     if (this.sourceEditor) {
       this._state.text = this.sourceEditor.getText();
     }
 
     this._isUpdating = true;
     this.styleSheet.update(this._state.text, this.transitionsEnabled)
-      .then(null, e => console.error(e));
+      .catch(e => console.error(e));
   },
 
   /**
    * Handle mousemove events, calling _highlightSelectorAt after a delay only
    * and reseting the delay everytime.
    */
   _onMouseMove: function (e) {
     this.highlighter.hide();
--- a/devtools/client/webaudioeditor/panel.js
+++ b/devtools/client/webaudioeditor/panel.js
@@ -39,17 +39,17 @@ WebAudioEditorPanel.prototype = {
         this.panelWin.gFront = new WebAudioFront(this.target.client, this.target.form);
         return this.panelWin.startupWebAudioEditor();
       })
       .then(() => {
         this.isReady = true;
         this.emit("ready");
         return this;
       })
-      .then(null, function onError(aReason) {
+      .catch(function onError(aReason) {
         console.error("WebAudioEditorPanel open failed. " +
                       aReason.error + ": " + aReason.message);
       });
   },
 
   // DevToolPanel API
 
   get target() {
--- a/devtools/client/webconsole/console-output.js
+++ b/devtools/client/webconsole/console-output.js
@@ -3069,17 +3069,17 @@ Widgets.ObjectRenderers.add({
     }
 
     this._text(">");
 
     // Register this widget in the owner message so that it gets destroyed when
     // the message is destroyed.
     this.message.widgets.add(this);
 
-    this.linkToInspector().then(null, e => console.error(e));
+    this.linkToInspector().catch(e => console.error(e));
   },
 
   /**
    * If the DOMNode being rendered can be highlit in the page, this function
    * will attach mouseover/out event listeners to do so, and the inspector icon
    * to open the node in the inspector.
    * @return a promise that resolves when the node has been linked to the
    * inspector, or rejects if it wasn't (either if no toolbox could be found to
@@ -3157,17 +3157,17 @@ Widgets.ObjectRenderers.add({
   /**
    * Unhighlight a previously highlit node
    * @see highlightDomNode
    * @return a promise that resolves when the highlighter has been hidden
    */
   unhighlightDomNode: function () {
     return this.linkToInspector().then(() => {
       return this.toolbox.highlighterUtils.unhighlight();
-    }).then(null, e => console.error(e));
+    }).catch(e => console.error(e));
   },
 
   /**
    * Open the DOMNode corresponding to the ObjectActor in the inspector panel
    * @return a promise that resolves when the inspector has been switched to
    * and the node has been selected, or rejects if the node cannot be selected
    * (detached from the DOM). Note that in any case, the inspector panel will
    * be switched to.
--- a/devtools/client/webconsole/test/browser_console_optimized_out_vars.js
+++ b/devtools/client/webconsole/test/browser_console_optimized_out_vars.js
@@ -45,17 +45,17 @@ function test() {
       webconsole: hud,
       messages: [{
         text: "optimized out",
         category: CATEGORY_OUTPUT,
       }]
     });
 
     finishTest();
-  }).then(null, aError => {
+  }).catch(aError => {
     ok(false, "Got an error: " + aError.message + "\n" + aError.stack);
   });
 }
 
 // Debugger helper functions stolen from devtools/client/debugger/test/head.js.
 
 function ensureThreadClientState(aPanel, aState) {
   let thread = aPanel.panelWin.gThreadClient;
--- a/devtools/client/webconsole/test/browser_webconsole_split.js
+++ b/devtools/client/webconsole/test/browser_webconsole_split.js
@@ -194,17 +194,17 @@ function test() {
   });
 
   function openPanel(toolId) {
     let deferred = promise.defer();
     let target = TargetFactory.forTab(gBrowser.selectedTab);
     gDevTools.showToolbox(target, toolId).then(function (box) {
       toolbox = box;
       deferred.resolve();
-    }).then(null, console.error);
+    }).catch(console.error);
     return deferred.promise;
   }
 
   function openAndCheckPanel(toolId) {
     return openPanel(toolId).then(() => {
       info("Checking toolbox for " + toolId);
       return checkToolboxUI(toolbox.getCurrentPanel());
     });
--- a/devtools/client/webide/content/webide.js
+++ b/devtools/client/webide/content/webide.js
@@ -913,17 +913,17 @@ var Cmds = {
     });
     return UI.busyUntil(disconnecting, "disconnecting from runtime");
   },
 
   takeScreenshot: function () {
     let url = AppManager.deviceFront.screenshotToDataURL();
     return UI.busyUntil(url.then(longstr => {
       return longstr.string().then(dataURL => {
-        longstr.release().then(null, console.error);
+        longstr.release().catch(console.error);
         UI.openInBrowser(dataURL);
       });
     }), "taking screenshot");
   },
 
   showRuntimeDetails: function () {
     UI.selectDeckPanel("runtimedetails");
   },
--- a/devtools/client/webide/modules/app-manager.js
+++ b/devtools/client/webide/modules/app-manager.js
@@ -478,17 +478,17 @@ var AppManager = exports.AppManager = {
         this.connection.on(Connection.Events.CONNECTED, onConnectedOrDisconnected);
         this.connection.on(Connection.Events.DISCONNECTED, onConnectedOrDisconnected);
         try {
           // Reset the connection's state to defaults
           this.connection.resetOptions();
           // Only watch for errors here.  Final resolution occurs above, once
           // we've reached the CONNECTED state.
           this.selectedRuntime.connect(this.connection)
-                              .then(null, e => reject(e));
+                              .catch(e => reject(e));
         } catch (e) {
           reject(e);
         }
       }, reject);
     });
 
     // Record connection result in telemetry
     let logResult = result => {
--- a/devtools/client/webide/test/test_device_preferences.html
+++ b/devtools/client/webide/test/test_device_preferences.html
@@ -72,16 +72,16 @@
 
           searchFields(deck, "debugger");
 
           DebuggerServer.destroy();
 
           yield closeWebIDE(win);
 
           SimpleTest.finish();
-        }).then(null, e => {
+        }).catch(e => {
           ok(false, "Exception: " + e);
           SimpleTest.finish();
         });
       }
     </script>
   </body>
 </html>
--- a/devtools/client/webide/test/test_device_runtime.html
+++ b/devtools/client/webide/test/test_device_runtime.html
@@ -66,16 +66,16 @@
 
           ok(!deck.selectedPanel, "No panel selected");
 
           DebuggerServer.destroy();
 
           yield closeWebIDE(win);
 
           SimpleTest.finish();
-        }).then(null, e => {
+        }).catch(e => {
           ok(false, "Exception: " + e);
           SimpleTest.finish();
         });
       }
     </script>
   </body>
 </html>
--- a/devtools/client/webide/test/test_duplicate_import.html
+++ b/devtools/client/webide/test/test_duplicate_import.html
@@ -61,17 +61,17 @@
           is(items[3].querySelector("span").textContent, "A name (in app directory)", "Panel text is correct");
           is(items[4].querySelector("span").textContent, "hosted manifest name property", "Panel text is correct");
 
           yield closeWebIDE(win);
 
           yield removeAllProjects();
 
           SimpleTest.finish();
-        }).then(null, e => {
+        }).catch(e => {
           ok(false, "Exception: " + e);
           SimpleTest.finish();
         });
       }
     </script>
   </body>
 </html>
 
--- a/devtools/client/webide/test/test_import.html
+++ b/devtools/client/webide/test/test_import.html
@@ -67,16 +67,16 @@
           is(items[3].querySelector("span").textContent, "A name (in app directory)", "Panel text is correct");
           is(items[4].querySelector("span").textContent, "hosted manifest name property", "Panel text is correct");
 
           yield closeWebIDE(win);
 
           yield removeAllProjects();
 
           SimpleTest.finish();
-        }).then(null, e => {
+        }).catch(e => {
           ok(false, "Exception: " + e);
           SimpleTest.finish();
         });
       }
     </script>
   </body>
 </html>
--- a/devtools/server/actors/script.js
+++ b/devtools/server/actors/script.js
@@ -733,17 +733,17 @@ const ThreadActor = ActorClassWithSpec(t
                     }
 
                     packet.frame.where = {
                       source: originalLocation.originalSourceActor.form(),
                       line: originalLocation.originalLine,
                       column: originalLocation.originalColumn
                     };
                     resolve(onPacket(packet))
-          .then(null, error => {
+          .catch(error => {
             reportError(error);
             return {
               error: "unknownError",
               message: error.message + "\n" + error.stack
             };
           })
           .then(pkt => {
             this.conn.send(pkt);
@@ -1066,17 +1066,17 @@ const ThreadActor = ActorClassWithSpec(t
     let needNest = true;
     let eventLoop;
     let returnVal;
 
     p.then((resolvedVal) => {
       needNest = false;
       returnVal = resolvedVal;
     })
-    .then(null, (error) => {
+    .catch((error) => {
       reportError(error, "Error inside unsafeSynchronize:");
     })
     .then(() => {
       if (eventLoop) {
         eventLoop.resolve();
       }
     });
 
--- a/devtools/server/actors/source.js
+++ b/devtools/server/actors/source.js
@@ -154,17 +154,17 @@ let SourceActor = ActorClassWithSpec(sou
     this._encodeAndSetSourceMapURL = this._encodeAndSetSourceMapURL.bind(this);
     this._getSourceText = this._getSourceText.bind(this);
 
     this._mapSourceToAddon();
 
     if (this.threadActor.sources.isPrettyPrinted(this.url)) {
       this._init = this.prettyPrint(
         this.threadActor.sources.prettyPrintIndent(this.url)
-      ).then(null, error => {
+      ).catch(error => {
         DevToolsUtils.reportException("SourceActor", error);
       });
     } else {
       this._init = null;
     }
   },
 
   get isSourceMapped() {
@@ -490,17 +490,17 @@ let SourceActor = ActorClassWithSpec(sou
           };
         }
         return {
           source: createValueGrip(content, this.threadActor.threadLifetimePool,
             this.threadActor.objectGrip),
           contentType: contentType
         };
       })
-      .then(null, error => {
+      .catch(error => {
         reportError(error, "Got an exception during SA_onSource: ");
         throw new Error("Could not load the source for " + this.url + ".\n" +
                         DevToolsUtils.safeErrorString(error));
       });
   },
 
   /**
    * Handler for the "prettyPrint" packet.
@@ -513,17 +513,17 @@ let SourceActor = ActorClassWithSpec(sou
       .then(this._encodeAndSetSourceMapURL)
       .then(() => {
         // We need to reset `_init` now because we have already done the work of
         // pretty printing, and don't want onSource to wait forever for
         // initialization to complete.
         this._init = null;
       })
       .then(this.onSource)
-      .then(null, error => {
+      .catch(error => {
         this.disablePrettyPrint();
         throw new Error(DevToolsUtils.safeErrorString(error));
       });
   },
 
   /**
    * Return a function that sends a request to the pretty print worker, waits on
    * the worker's response, and then returns the pretty printed code.
--- a/devtools/server/actors/utils/TabSources.js
+++ b/devtools/server/actors/utils/TabSources.js
@@ -460,17 +460,17 @@ TabSources.prototype = {
     }
 
     let fetching = fetch(absSourceMapURL, { loadFromCache: false })
       .then(({ content }) => {
         let map = new SourceMapConsumer(content);
         this._setSourceMapRoot(map, absSourceMapURL, sourceURL);
         return map;
       })
-      .then(null, error => {
+      .catch(error => {
         if (!DevToolsUtils.reportingDisabled) {
           DevToolsUtils.reportException("TabSources.prototype._fetchSourceMap", error);
         }
         return null;
       });
     this._sourceMapCache[absSourceMapURL] = fetching;
     return fetching;
   },
--- a/devtools/server/main.js
+++ b/devtools/server/main.js
@@ -1671,17 +1671,17 @@ DebuggerServerConnection.prototype = {
     let pendingResponse = this._actorResponses.get(from) || SyncPromise.resolve(null);
     let responsePromise = pendingResponse.then(() => {
       return responseOrPromise;
     }).then(response => {
       if (!response.from) {
         response.from = from;
       }
       this.transport.send(response);
-    }).then(null, (e) => {
+    }).catch((e) => {
       let errorPacket = this._unknownError(
         "error occurred while processing '" + type, e);
       errorPacket.from = from;
       this.transport.send(errorPacket);
     });
 
     this._actorResponses.set(from, responsePromise);
   },
--- a/devtools/server/tests/mochitest/inspector-helpers.js
+++ b/devtools/server/tests/mochitest/inspector-helpers.js
@@ -202,17 +202,17 @@ function checkAvailable(client, actorID)
     is(response.error, "unrecognizedPacketType",
        "node list actor should be contactable.");
     deferred.resolve(undefined);
   });
   return deferred.promise;
 }
 
 function promiseDone(currentPromise) {
-  currentPromise.then(null, err => {
+  currentPromise.catch(err => {
     ok(false, "Promise failed: " + err);
     if (err.stack) {
       dump(err.stack);
     }
     SimpleTest.finish();
   });
 }
 
@@ -294,17 +294,17 @@ function waitForMutation(walker, test, m
 }
 
 var _tests = [];
 function addTest(test) {
   _tests.push(test);
 }
 
 function addAsyncTest(generator) {
-  _tests.push(() => Task.spawn(generator).then(null, ok.bind(null, false)));
+  _tests.push(() => Task.spawn(generator).catch(ok.bind(null, false)));
 }
 
 function runNextTest() {
   if (_tests.length == 0) {
     SimpleTest.finish();
     return;
   }
   let fn = _tests.shift();
--- a/devtools/server/tests/unit/test_actor-registry-actor.js
+++ b/devtools/server/tests/unit/test_actor-registry-actor.js
@@ -37,17 +37,17 @@ function registerNewActor() {
     constructor: "HelloActor",
     type: { global: true }
   };
 
   gRegistryFront
     .registerActor("resource://test/hello-actor.js", options)
     .then(actorFront => (gActorFront = actorFront))
     .then(talkToNewActor)
-    .then(null, e => {
+    .catch(e => {
       DevToolsUtils.reportException("registerNewActor", e);
       do_check_true(false);
     });
 }
 
 function talkToNewActor() {
   gClient.listTabs(({ helloActor }) => {
     do_check_true(!!helloActor);
@@ -60,17 +60,17 @@ function talkToNewActor() {
     });
   });
 }
 
 function unregisterNewActor() {
   gActorFront
     .unregister()
     .then(testActorIsUnregistered)
-    .then(null, e => {
+    .catch(e => {
       DevToolsUtils.reportException("unregisterNewActor", e);
       do_check_true(false);
     });
 }
 
 function testActorIsUnregistered() {
   gClient.listTabs(({ helloActor }) => {
     do_check_true(!helloActor);
--- a/devtools/server/tests/unit/test_blackboxing-06.js
+++ b/devtools/server/tests/unit/test_blackboxing-06.js
@@ -23,17 +23,17 @@ function run_test() {
       gClient, "test-black-box",
       function (response, tabClient, threadClient) {
         gThreadClient = threadClient;
 
         promise.resolve(setup_code())
           .then(black_box_code)
           .then(run_code)
           .then(test_correct_location)
-          .then(null, function (error) {
+          .catch(function (error) {
             do_check_true(false, "Should not get an error, got " + error);
           })
           .then(function () {
             finishClient(gClient);
           });
       });
   });
   do_test_pending();
--- a/devtools/server/tests/unit/test_promise_state-03.js
+++ b/devtools/server/tests/unit/test_promise_state-03.js
@@ -37,17 +37,17 @@ function run_test() {
 }
 
 function evalCode(debuggee) {
   /* eslint-disable */
   Components.utils.evalInSandbox(
     "doTest();\n" +
     function doTest() {
       var resolved = Promise.reject(new Error("uh oh"));
-      resolved.then(null, () => {
+      resolved.catch(() => {
         var p = resolved;
         debugger;
       });
     },
     debuggee
   );
   /* eslint-enable */
 }
--- a/devtools/server/tests/unit/test_promises_client_getdependentpromises.js
+++ b/devtools/server/tests/unit/test_promises_client_getdependentpromises.js
@@ -18,17 +18,17 @@ add_task(function* () {
 
   ok(Promise.toString().includes("native code"), "Expect native DOM Promise.");
 
   yield testGetDependentPromises(client, chromeActors, () => {
     let p = new Promise(() => {});
     p.name = "p";
     let q = p.then();
     q.name = "q";
-    let r = p.then(null, () => {});
+    let r = p.catch(() => {});
     r.name = "r";
 
     return p;
   });
 
   let response = yield listTabs(client);
   let targetTab = findTab(response.tabs, "test-promises-dependentpromises");
   ok(targetTab, "Found our target tab.");
@@ -37,17 +37,17 @@ add_task(function* () {
   yield testGetDependentPromises(client, targetTab, () => {
     const debuggee =
       DebuggerServer.getTestGlobal("test-promises-dependentpromises");
 
     let p = new debuggee.Promise(() => {});
     p.name = "p";
     let q = p.then();
     q.name = "q";
-    let r = p.then(null, () => {});
+    let r = p.catch(() => {});
     r.name = "r";
 
     return p;
   });
 
   yield close(client);
 });
 
--- a/devtools/server/tests/unit/test_protocol_children.js
+++ b/devtools/server/tests/unit/test_protocol_children.js
@@ -592,14 +592,14 @@ function run_test() {
       do_check_eq(ret.length, 2);
       do_check_true(ret[0] === childFront);
       do_check_true(ret[1] !== childFront);
       do_check_true(ret[1] instanceof ChildFront);
     }).then(() => {
       client.close().then(() => {
         do_test_finished();
       });
-    }).then(null, err => {
+    }).catch(err => {
       do_report_unexpected_exception(err, "Failure executing test");
     });
   });
   do_test_pending();
 }
--- a/devtools/server/tests/unit/test_protocol_longstring.js
+++ b/devtools/server/tests/unit/test_protocol_longstring.js
@@ -230,14 +230,14 @@ function run_test() {
     }).then(() => {
       trace.expectSend({"type": "release", "to": "<actorid>"});
       trace.expectReceive({"from": "<actorid>"});
       expectRootChildren(0);
     }).then(() => {
       client.close().then(() => {
         do_test_finished();
       });
-    }).then(null, err => {
+    }).catch(err => {
       do_report_unexpected_exception(err, "Failure executing test");
     });
   });
   do_test_pending();
 }
--- a/devtools/server/tests/unit/test_protocol_simple.js
+++ b/devtools/server/tests/unit/test_protocol_simple.js
@@ -324,14 +324,14 @@ function run_test() {
         deferred.resolve();
       });
       rootClient.emitFalsyOptions();
       return deferred.promise;
     }).then(() => {
       client.close().then(() => {
         do_test_finished();
       });
-    }).then(null, err => {
+    }).catch(err => {
       do_report_unexpected_exception(err, "Failure executing test");
     });
   });
   do_test_pending();
 }
--- a/devtools/server/tests/unit/test_sourcemaps-10.js
+++ b/devtools/server/tests/unit/test_sourcemaps-10.js
@@ -20,17 +20,17 @@ function run_test() {
   gClient.connect().then(function () {
     attachTestTabAndResume(
       gClient, "test-source-map",
       function (response, tabClient, threadClient) {
         gThreadClient = threadClient;
         promise.resolve(define_code())
           .then(run_code)
           .then(test_frame_location)
-          .then(null, error => {
+          .catch(error => {
             dump(error + "\n");
             dump(error.stack);
             do_check_true(false);
           })
           .then(() => {
             finishClient(gClient);
           });
       });
--- a/devtools/server/tests/unit/test_sourcemaps-11.js
+++ b/devtools/server/tests/unit/test_sourcemaps-11.js
@@ -20,17 +20,17 @@ function run_test() {
   gClient.connect().then(function () {
     attachTestTabAndResume(
       gClient, "test-source-map",
       function (response, tabClient, threadClient) {
         gThreadClient = threadClient;
         promise.resolve(define_code())
           .then(run_code)
           .then(test_frames)
-          .then(null, error => {
+          .catch(error => {
             dump(error + "\n");
             dump(error.stack);
             do_check_true(false);
           })
           .then(() => {
             finishClient(gClient);
           });
       });
--- a/devtools/shared/apps/app-actor-front.js
+++ b/devtools/shared/apps/app-actor-front.js
@@ -615,17 +615,17 @@ AppActorFront.prototype = {
     // On demand, retrieve apps icons in order to be able
     // to synchronously retrieve it on `App` objects
     let promises = [];
     for (let [, app] of this._apps) {
       promises.push(app.getIcon());
     }
 
     return DevToolsUtils.settleAll(promises)
-                        .then(null, () => {});
+                        .catch(() => {});
   },
 
   _listenAppEvents: function (listener) {
     this._listeners.push(listener);
 
     if (this._listeners.length > 1) {
       return promise.resolve();
     }
--- a/devtools/shared/deprecated-sync-thenables.js
+++ b/devtools/shared/deprecated-sync-thenables.js
@@ -56,16 +56,19 @@ function defer() {
 
       if (observers) {
         observers.push({ resolve: resolve, reject: reject });
       } else {
         result.then(resolve, reject);
       }
 
       return deferred.promise;
+    },
+    catch: function (callback) {
+      return this.then(null, callback);
     }
   };
 
   var deferred = {
     promise: promise,
     resolve: function resolve(value) {
       if (!result) {
         result = isPromise(value) ? value : fulfilled(value);
--- a/devtools/shared/fronts/device.js
+++ b/devtools/shared/fronts/device.js
@@ -14,17 +14,17 @@ const DeviceFront = protocol.FrontClassW
     this.actorID = form.deviceActor;
     this.manage(this);
   },
 
   screenshotToBlob: function () {
     return this.screenshotToDataURL().then(longstr => {
       return longstr.string().then(dataURL => {
         let deferred = defer();
-        longstr.release().then(null, Cu.reportError);
+        longstr.release().catch(Cu.reportError);
         let req = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
             .createInstance(Ci.nsIXMLHttpRequest);
         req.open("GET", dataURL, true);
         req.responseType = "blob";
         req.onload = () => {
           deferred.resolve(req.response);
         };
         req.onerror = () => {
--- a/devtools/shared/gcli/templater.js
+++ b/devtools/shared/gcli/templater.js
@@ -169,17 +169,17 @@ function processNode(state, node, data) 
             if (value.indexOf("${") === 0 &&
                 value.charAt(value.length - 1) === "}") {
               replacement = envEval(state, value.slice(2, -1), data, value);
               if (replacement && typeof replacement.then === "function") {
                 node.setAttribute(name, "");
                 /* jshint loopfunc:true */
                 replacement.then(function (newValue) {
                   node.setAttribute(name, newValue);
-                }).then(null, console.error);
+                }).catch(console.error);
               } else {
                 if (state.options.blankNullUndefined && replacement == null) {
                   replacement = "";
                 }
                 node.setAttribute(name, replacement);
               }
             } else {
               node.setAttribute(name, processString(state, value, data));
@@ -448,17 +448,17 @@ function handleAsync(thing, siblingNode,
     // Placeholder element to be replaced once we have the real data
     let tempNode = siblingNode.ownerDocument.createElement("span");
     siblingNode.parentNode.insertBefore(tempNode, siblingNode);
     thing.then(function (delayed) {
       inserter(delayed, tempNode);
       if (tempNode.parentNode != null) {
         tempNode.remove();
       }
-    }).then(null, function (error) {
+    }).catch(function (error) {
       console.error(error.stack);
     });
   } else {
     inserter(thing, siblingNode);
   }
 }
 
 /**
--- a/devtools/shared/protocol.js
+++ b/devtools/shared/protocol.js
@@ -1107,17 +1107,17 @@ var generateRequestHandlers = function (
 
           conn.send(response);
         };
 
         this._queueResponse(p => {
           return p
             .then(() => ret)
             .then(sendReturn)
-            .then(null, this.writeError.bind(this));
+            .catch(this.writeError.bind(this));
         });
       } catch (e) {
         this._queueResponse(p => {
           return p.then(() => this.writeError(e));
         });
       }
     };
 
@@ -1246,17 +1246,17 @@ var Front = Class({
    */
   send: function (packet) {
     if (packet.to) {
       this.conn._transport.send(packet);
     } else {
       this.actor().then(actorID => {
         packet.to = actorID;
         this.conn._transport.send(packet);
-      }).then(null, e => console.error(e));
+      }).catch(e => console.error(e));
     }
   },
 
   /**
    * Send a two-way request on the connection.
    */
   request: function (packet) {
     let deferred = defer();
--- a/devtools/shared/tests/mochitest/test_eventemitter_basic.html
+++ b/devtools/shared/tests/mochitest/test_eventemitter_basic.html
@@ -28,17 +28,17 @@
       const { Task } = require("devtools/shared/task");
 
       SimpleTest.waitForExplicitFinish();
 
       testEmitter();
       testEmitter({});
 
       Task.spawn(testPromise)
-          .then(null, ok.bind(null, false))
+          .catch(ok.bind(null, false))
           .then(SimpleTest.finish);
 
       function testEmitter(aObject) {
         let emitter;
 
         if (aObject) {
           emitter = aObject;
           EventEmitter.decorate(emitter);
--- a/devtools/shared/tests/unit/test_async-utils.js
+++ b/devtools/shared/tests/unit/test_async-utils.js
@@ -21,17 +21,17 @@ function run_test() {
   Task.spawn(function* () {
     yield test_async_args(asyncOnce);
     yield test_async_return(asyncOnce);
     yield test_async_throw(asyncOnce);
 
     yield test_async_once();
     yield test_async_invoke();
     do_test_finished();
-  }).then(null, error => {
+  }).catch(error => {
     do_throw(error);
   });
 }
 
 // Test that arguments are correctly passed through to the async function.
 function test_async_args(async) {
   let obj = {
     method: async(function* (a, b) {
@@ -61,17 +61,17 @@ function test_async_return(async) {
 // Test that the throwing from an async function rejects the promise.
 function test_async_throw(async) {
   let obj = {
     method: async(function* () {
       throw new Error("boom");
     })
   };
 
-  return obj.method().then(null, error => {
+  return obj.method().catch(error => {
     do_check_true(error instanceof Error);
     do_check_eq(error.message, "boom");
   });
 }
 
 // Test that asyncOnce only runs the async function once per instance and
 // returns the same promise for that instance.
 function test_async_once() {
@@ -144,14 +144,14 @@ function test_async_invoke() {
     do_check_eq(results[0], "foo");
     do_check_eq(results[1], "bar");
 
     // Test throwing from the function.
     function thrower() {
       throw new Error("boom");
     }
 
-    yield promiseCall(thrower).then(null, error => {
+    yield promiseCall(thrower).catch(error => {
       do_check_true(error instanceof Error);
       do_check_eq(error.message, "boom");
     });
   });
 }
--- a/devtools/shared/transport/tests/unit/test_client_server_bulk.js
+++ b/devtools/shared/transport/tests/unit/test_client_server_bulk.js
@@ -172,17 +172,17 @@ var test_bulk_request_cs = Task.async(fu
     // Send bulk data to server
     request.on("bulk-send-ready", bulkSendReadyCallback);
 
     // Set up reply handling for this type
     replyHandlers[replyType](request).then(() => {
       client.close();
       transport.close();
     });
-  }).then(null, do_throw);
+  }).catch(do_throw);
 
   DebuggerServer.on("connectionchange", (event, type) => {
     if (type === "closed") {
       serverDeferred.resolve();
     }
   });
 
   return promise.all([
@@ -214,17 +214,17 @@ var test_json_request_cs = Task.async(fu
       type: actorType
     });
 
     // Set up reply handling for this type
     replyHandlers[replyType](request).then(() => {
       client.close();
       transport.close();
     });
-  }).then(null, do_throw);
+  }).catch(do_throw);
 
   DebuggerServer.on("connectionchange", (event, type) => {
     if (type === "closed") {
       serverDeferred.resolve();
     }
   });
 
   return promise.all([
--- a/dom/base/DocGroup.cpp
+++ b/dom/base/DocGroup.cpp
@@ -44,17 +44,17 @@ DocGroup::DocGroup(TabGroup* aTabGroup, 
   // This method does not add itself to mTabGroup->mDocGroups as the caller does it for us.
 }
 
 DocGroup::~DocGroup()
 {
   MOZ_ASSERT(mDocuments.IsEmpty());
   if (!NS_IsMainThread()) {
     nsIEventTarget* target = EventTargetFor(TaskCategory::Other);
-    NS_ProxyRelease(target, mReactionsStack.forget());
+    NS_ProxyRelease("DocGroup::mReactionsStack", target, mReactionsStack.forget());
   }
 
   mTabGroup->mDocGroups.RemoveEntry(mKey);
 }
 
 nsresult
 DocGroup::Dispatch(const char* aName,
                    TaskCategory aCategory,
--- a/dom/base/SelectionChangeListener.cpp
+++ b/dom/base/SelectionChangeListener.cpp
@@ -141,30 +141,30 @@ SelectionChangeListener::NotifySelection
     // If we didn't get a target before, we can instead fire the event at the document.
     if (!target) {
       nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDoc);
       target = doc.forget();
     }
 
     if (target) {
       RefPtr<AsyncEventDispatcher> asyncDispatcher =
-        new AsyncEventDispatcher(target, NS_LITERAL_STRING("selectionchange"), false);
+        new AsyncEventDispatcher(target, eSelectionChange, false);
       asyncDispatcher->PostDOMEvent();
     }
   } else {
     if (const nsFrameSelection* fs = sel->GetFrameSelection()) {
       if (nsCOMPtr<nsIContent> root = fs->GetLimiter()) {
         if (root->IsInNativeAnonymousSubtree()) {
           return NS_OK;
         }
       }
     }
 
     nsCOMPtr<nsIDocument> doc = do_QueryInterface(aDoc);
     if (doc) {
       RefPtr<AsyncEventDispatcher> asyncDispatcher =
-        new AsyncEventDispatcher(doc, NS_LITERAL_STRING("selectionchange"), false);
+        new AsyncEventDispatcher(doc, eSelectionChange, false);
       asyncDispatcher->PostDOMEvent();
     }
   }
 
   return NS_OK;
 }
--- a/dom/base/UseCounter.h
+++ b/dom/base/UseCounter.h
@@ -26,11 +26,17 @@ enum UseCounter : int16_t {
 #define DEPRECATED_OPERATION(op_) \
   eUseCounter_##op_,
 #include "nsDeprecatedOperationList.h"
 #undef DEPRECATED_OPERATION
 
   eUseCounter_Count
 };
 
+enum IncCounter : int16_t {
+  eIncCounter_UNKNOWN = -1,
+  eIncCounter_ScriptTag,
+  eIncCounter_Count
+};
+
 }
 
 #endif
--- a/dom/base/WebSocket.cpp
+++ b/dom/base/WebSocket.cpp
@@ -634,18 +634,18 @@ WebSocketImpl::Disconnect()
     // where to, exactly?
     rv.SuppressException();
   }
 
   // DontKeepAliveAnyMore() can release the object. So hold a reference to this
   // until the end of the method.
   RefPtr<WebSocketImpl> kungfuDeathGrip = this;
 
-  NS_ReleaseOnMainThread(mChannel.forget());
-  NS_ReleaseOnMainThread(mService.forget());
+  NS_ReleaseOnMainThread("WebSocketImpl::mChannel", mChannel.forget());
+  NS_ReleaseOnMainThread("WebSocketImpl::mService", mService.forget());
 
   mWebSocket->DontKeepAliveAnyMore();
   mWebSocket->mImpl = nullptr;
 
   if (mWorkerPrivate && mWorkerHolder) {
     UnregisterWorkerHolder();
   }
 
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -840,41 +840,62 @@ nsContentUtils::InitializeModifierString
   sShiftText = new nsString(shiftModifier);
   sMetaText = new nsString(metaModifier);
   sOSText = new nsString(osModifier);
   sAltText = new nsString(altModifier);
   sControlText = new nsString(controlModifier);
   sModifierSeparator = new nsString(modifierSeparator);
 }
 
+mozilla::EventClassID
+nsContentUtils::GetEventClassIDFromMessage(EventMessage aEventMessage)
+{
+  switch (aEventMessage) {
+#define MESSAGE_TO_EVENT(name_, message_, type_, struct_) \
+  case message_: return struct_;
+#include "mozilla/EventNameList.h"
+#undef MESSAGE_TO_EVENT
+  default:
+    MOZ_ASSERT_UNREACHABLE("Invalid event message?");
+    return eBasicEventClass;
+  }
+}
+
+static nsIAtom*
+GetEventTypeFromMessage(EventMessage aEventMessage)
+{
+  switch (aEventMessage) {
+#define MESSAGE_TO_EVENT(name_, message_, type_, struct_) \
+  case message_: return nsGkAtoms::on##name_;
+#include "mozilla/EventNameList.h"
+#undef MESSAGE_TO_EVENT
+  default:
+    return nullptr;
+  }
+}
+
 // Because of SVG/SMIL we have several atoms mapped to the same
 // id, but we can rely on MESSAGE_TO_EVENT to map id to only one atom.
 static bool
 ShouldAddEventToStringEventTable(const EventNameMapping& aMapping)
 {
-  switch(aMapping.mMessage) {
-#define MESSAGE_TO_EVENT(name_, message_, type_, struct_) \
-  case message_: return nsGkAtoms::on##name_ == aMapping.mAtom;
-#include "mozilla/EventNameList.h"
-#undef MESSAGE_TO_EVENT
-  default:
-    break;
-  }
-  return false;
+  MOZ_ASSERT(aMapping.mAtom);
+  return GetEventTypeFromMessage(aMapping.mMessage) == aMapping.mAtom;
 }
 
 bool
 nsContentUtils::InitializeEventTable() {
   NS_ASSERTION(!sAtomEventTable, "EventTable already initialized!");
   NS_ASSERTION(!sStringEventTable, "EventTable already initialized!");
 
   static const EventNameMapping eventArray[] = {
 #define EVENT(name_,  _message, _type, _class)          \
     { nsGkAtoms::on##name_, _type, _message, _class, false },
 #define WINDOW_ONLY_EVENT EVENT
+#define DOCUMENT_ONLY_EVENT EVENT
 #define NON_IDL_EVENT EVENT
 #include "mozilla/EventNameList.h"
 #undef WINDOW_ONLY_EVENT
 #undef NON_IDL_EVENT
 #undef EVENT
     { nullptr }
   };
 
@@ -4342,59 +4363,93 @@ nsresult GetEventAndTarget(nsIDocument* 
   return NS_OK;
 }
 
 // static
 nsresult
 nsContentUtils::DispatchTrustedEvent(nsIDocument* aDoc, nsISupports* aTarget,
                                      const nsAString& aEventName,
                                      bool aCanBubble, bool aCancelable,
-                                     bool *aDefaultAction)
+                                     bool* aDefaultAction)
 {
   return DispatchEvent(aDoc, aTarget, aEventName, aCanBubble, aCancelable,
                        true, aDefaultAction);
 }
 
 // static
 nsresult
 nsContentUtils::DispatchUntrustedEvent(nsIDocument* aDoc, nsISupports* aTarget,
                                        const nsAString& aEventName,
                                        bool aCanBubble, bool aCancelable,
-                                       bool *aDefaultAction)
+                                       bool* aDefaultAction)
 {
   return DispatchEvent(aDoc, aTarget, aEventName, aCanBubble, aCancelable,
                        false, aDefaultAction);
 }
 
 // static
 nsresult
 nsContentUtils::DispatchEvent(nsIDocument* aDoc, nsISupports* aTarget,
                               const nsAString& aEventName,
                               bool aCanBubble, bool aCancelable,
-                              bool aTrusted, bool *aDefaultAction,
+                              bool aTrusted, bool* aDefaultAction,
                               bool aOnlyChromeDispatch)
 {
   nsCOMPtr<nsIDOMEvent> event;
   nsCOMPtr<EventTarget> target;
   nsresult rv = GetEventAndTarget(aDoc, aTarget, aEventName, aCanBubble,
                                   aCancelable, aTrusted, getter_AddRefs(event),
                                   getter_AddRefs(target));
   NS_ENSURE_SUCCESS(rv, rv);
   event->WidgetEventPtr()->mFlags.mOnlyChromeDispatch = aOnlyChromeDispatch;
 
   bool dummy;
   return target->DispatchEvent(event, aDefaultAction ? aDefaultAction : &dummy);
 }
 
+// static
+nsresult
+nsContentUtils::DispatchEvent(nsIDocument* aDoc, nsISupports* aTarget,
+                              WidgetEvent& aEvent,
+                              EventMessage aEventMessage,
+                              bool aCanBubble, bool aCancelable,
+                              bool aTrusted, bool *aDefaultAction,
+                              bool aOnlyChromeDispatch)
+{
+  MOZ_ASSERT_IF(aOnlyChromeDispatch, aTrusted);
+
+  nsCOMPtr<EventTarget> target(do_QueryInterface(aTarget));
+
+  aEvent.mTime = PR_Now();
+
+  aEvent.mSpecifiedEventType = GetEventTypeFromMessage(aEventMessage);
+  aEvent.SetDefaultComposed();
+  aEvent.SetDefaultComposedInNativeAnonymousContent();
+
+  aEvent.mFlags.mBubbles = aCanBubble;
+  aEvent.mFlags.mCancelable = aCancelable;
+  aEvent.mFlags.mOnlyChromeDispatch = aOnlyChromeDispatch;
+
+  aEvent.mTarget = target;
+
+  nsEventStatus status = nsEventStatus_eIgnore;
+  nsresult rv = EventDispatcher::DispatchDOMEvent(target, &aEvent, nullptr,
+                                                  nullptr, &status);
+  if (aDefaultAction) {
+    *aDefaultAction = (status != nsEventStatus_eConsumeNoDefault);
+  }
+  return rv;
+}
+
 nsresult
 nsContentUtils::DispatchChromeEvent(nsIDocument *aDoc,
                                     nsISupports *aTarget,
                                     const nsAString& aEventName,
                                     bool aCanBubble, bool aCancelable,
-                                    bool *aDefaultAction)
+                                    bool* aDefaultAction)
 {
 
   nsCOMPtr<nsIDOMEvent> event;
   nsCOMPtr<EventTarget> target;
   nsresult rv = GetEventAndTarget(aDoc, aTarget, aEventName, aCanBubble,
                                   aCancelable, true, getter_AddRefs(event),
                                   getter_AddRefs(target));
   NS_ENSURE_SUCCESS(rv, rv);
--- a/dom/base/nsContentUtils.h
+++ b/dom/base/nsContentUtils.h
@@ -15,16 +15,17 @@
 
 #if defined(SOLARIS)
 #include <ieeefp.h>
 #endif
 
 #include "js/TypeDecls.h"
 #include "js/Value.h"
 #include "js/RootingAPI.h"
+#include "mozilla/BasicEvents.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/GuardObjects.h"
 #include "mozilla/TaskCategory.h"
 #include "mozilla/TimeStamp.h"
 #include "nsContentListDeclarations.h"
 #include "nsMathUtils.h"
 #include "nsTArrayForwardDeclare.h"
 #include "Units.h"
@@ -1233,16 +1234,43 @@ public:
   static nsresult DispatchTrustedEvent(nsIDocument* aDoc,
                                        nsISupports* aTarget,
                                        const nsAString& aEventName,
                                        bool aCanBubble,
                                        bool aCancelable,
                                        bool *aDefaultAction = nullptr);
 
   /**
+   * This method creates and dispatches a trusted event using an event message.
+   * @param aDoc           The document which will be used to create the event.
+   * @param aTarget        The target of the event, should be QIable to
+   *                       EventTarget.
+   * @param aEventMessage  The event message.
+   * @param aCanBubble     Whether the event can bubble.
+   * @param aCancelable    Is the event cancelable.
+   * @param aDefaultAction Set to true if default action should be taken,
+   *                       see nsIDOMEventTarget::DispatchEvent.
+   */
+  template <class WidgetEventType>
+  static nsresult DispatchTrustedEvent(nsIDocument* aDoc,
+                                       nsISupports* aTarget,
+                                       mozilla::EventMessage aEventMessage,
+                                       bool aCanBubble,
+                                       bool aCancelable,
+                                       bool *aDefaultAction = nullptr,
+                                       bool aOnlyChromeDispatch = false)
+  {
+    WidgetEventType event(true, aEventMessage);
+    MOZ_ASSERT(GetEventClassIDFromMessage(aEventMessage) == event.mClass);
+    return DispatchEvent(aDoc, aTarget, event, aEventMessage,
+                         aCanBubble, aCancelable, true,
+                         aDefaultAction, aOnlyChromeDispatch);
+  }
+
+  /**
    * This method creates and dispatches a untrusted event.
    * Works only with events which can be created by calling
    * nsIDOMDocument::CreateEvent() with parameter "Events".
    * @param aDoc           The document which will be used to create the event.
    * @param aTarget        The target of the event, should be QIable to
    *                       nsIDOMEventTarget.
    * @param aEventName     The name of the event.
    * @param aCanBubble     Whether the event can bubble.
@@ -1252,16 +1280,44 @@ public:
    */
   static nsresult DispatchUntrustedEvent(nsIDocument* aDoc,
                                          nsISupports* aTarget,
                                          const nsAString& aEventName,
                                          bool aCanBubble,
                                          bool aCancelable,
                                          bool *aDefaultAction = nullptr);
 
+
+  /**
+   * This method creates and dispatches a untrusted event using an event message.
+   * @param aDoc           The document which will be used to create the event.
+   * @param aTarget        The target of the event, should be QIable to
+   *                       EventTarget.
+   * @param aEventMessage  The event message.
+   * @param aCanBubble     Whether the event can bubble.
+   * @param aCancelable    Is the event cancelable.
+   * @param aDefaultAction Set to true if default action should be taken,
+   *                       see nsIDOMEventTarget::DispatchEvent.
+   */
+  template <class WidgetEventType>
+  static nsresult DispatchUntrustedEvent(nsIDocument* aDoc,
+                                         nsISupports* aTarget,
+                                         mozilla::EventMessage aEventMessage,
+                                         bool aCanBubble,
+                                         bool aCancelable,
+                                         bool *aDefaultAction = nullptr,
+                                         bool aOnlyChromeDispatch = false)
+  {
+    WidgetEventType event(false, aEventMessage);
+    MOZ_ASSERT(GetEventClassIDFromMessage(aEventMessage) == event.mClass);
+    return DispatchEvent(aDoc, aTarget, event, aEventMessage,
+                         aCanBubble, aCancelable, false,
+                         aDefaultAction, aOnlyChromeDispatch);
+  }
+
   /**
    * This method creates and dispatches a trusted event to the chrome
    * event handler (the parent object of the DOM Window in the event target
    * chain). Note, chrome event handler is used even if aTarget is a chrome
    * object. Use DispatchEventOnlyToChrome if the normal event dispatching is
    * wanted in case aTarget is a chrome object.
    * Works only with events which can be created by calling
    * nsIDOMDocument::CreateEvent() with parameter "Events".
@@ -3000,27 +3056,40 @@ private:
                                 nsISupports* aTarget,
                                 const nsAString& aEventName,
                                 bool aCanBubble,
                                 bool aCancelable,
                                 bool aTrusted,
                                 bool *aDefaultAction = nullptr,
                                 bool aOnlyChromeDispatch = false);
 
+  static nsresult DispatchEvent(nsIDocument* aDoc,
+                                nsISupports* aTarget,
+                                mozilla::WidgetEvent& aWidgetEvent,
+                                mozilla::EventMessage aEventMessage,
+                                bool aCanBubble,
+                                bool aCancelable,
+                                bool aTrusted,
+                                bool *aDefaultAction = nullptr,
+                                bool aOnlyChromeDispatch = false);
+
   static void InitializeModifierStrings();
 
   static void DropFragmentParsers();
 
   static bool MatchClassNames(mozilla::dom::Element* aElement,
                               int32_t aNamespaceID,
                               nsIAtom* aAtom, void* aData);
   static void DestroyClassNameArray(void* aData);
   static void* AllocClassMatchingInfo(nsINode* aRootNode,
                                       const nsString* aClasses);
 
+  static mozilla::EventClassID
+  GetEventClassIDFromMessage(mozilla::EventMessage aEventMessage);
+
   // Fills in aInfo with the tokens from the supplied autocomplete attribute.
   static AutocompleteAttrState InternalSerializeAutocompleteAttribute(const nsAttrValue* aAttrVal,
                                                                       mozilla::dom::AutocompleteInfo& aInfo,
                                                                       bool aGrantAllValidValue = false);
 
   static bool CallOnAllRemoteChildren(nsIMessageBroadcaster* aManager,
                                       CallOnRemoteChildFunction aCallback,
                                       void* aArg);
--- a/dom/base/nsDOMDataChannel.cpp
+++ b/dom/base/nsDOMDataChannel.cpp
@@ -573,30 +573,30 @@ nsDOMDataChannel::UpdateMustKeepAlive()
     case DataChannel::CLOSED:
     {
       shouldKeepAlive = false;
     }
   }
 
   if (mSelfRef && !shouldKeepAlive) {
     // release our self-reference (safely) by putting it in an event (always)
-    NS_ReleaseOnMainThread(mSelfRef.forget(), true);
+    NS_ReleaseOnMainThread("nsDOMDataChannel::mSelfRef", mSelfRef.forget(), true);
   } else if (!mSelfRef && shouldKeepAlive) {
     mSelfRef = this;
   }
 }
 
 void
 nsDOMDataChannel::DontKeepAliveAnyMore()
 {
   MOZ_ASSERT(NS_IsMainThread());
 
   if (mSelfRef) {
     // Since we're on MainThread, force an eventloop trip to avoid deleting ourselves.
-    NS_ReleaseOnMainThread(mSelfRef.forget(), true);
+    NS_ReleaseOnMainThread("nsDOMDataChannel::mSelfRef", mSelfRef.forget(), true);
   }
 
   mCheckMustKeepAlive = false;
 }
 
 void
 nsDOMDataChannel::EventListenerAdded(nsIAtom* aType)
 {
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -1369,19 +1369,23 @@ nsIDocument::nsIDocument()
     mStaticCloneCount(0),
     mWindow(nullptr),
     mBFCacheEntry(nullptr),
     mInSyncOperationCount(0),
     mBlockDOMContentLoaded(0),
     mUseCounters(0),
     mChildDocumentUseCounters(0),
     mNotifiedPageForUseCounter(0),
+    mIncCounters(),
     mUserHasInteracted(false)
 {
   SetIsInDocument();
+  for (auto& cnt : mIncCounters) {
+    cnt = 0;
+  }
 }
 
 nsDocument::nsDocument(const char* aContentType)
   : nsIDocument()
   , mSubDocuments(nullptr)
   , mFlashClassification(FlashClassification::Unclassified)
   , mHeaderData(nullptr)
   , mIsGoingAway(false)
@@ -12738,16 +12742,21 @@ nsDocument::ReportUseCounters(UseCounter
             printf(": %d\n", value);
           }
 
           Telemetry::Accumulate(id, 1);
         }
       }
     }
   }
+
+  if (IsContentDocument() || IsResourceDoc()) {
+    uint16_t num = mIncCounters[eIncCounter_ScriptTag];
+    Telemetry::Accumulate(Telemetry::DOM_SCRIPT_EVAL_PER_DOCUMENT, num);
+  }
 }
 
 void
 nsDocument::AddIntersectionObserver(DOMIntersectionObserver* aObserver)
 {
   MOZ_ASSERT(!mIntersectionObservers.Contains(aObserver),
              "Intersection observer already in the list");
   mIntersectionObservers.PutEntry(aObserver);
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -797,16 +797,17 @@ GK_ATOM(oncallschanged, "oncallschanged"
 GK_ATOM(oncancel, "oncancel")
 GK_ATOM(oncardstatechange, "oncardstatechange")
 GK_ATOM(oncfstatechange, "oncfstatechange")
 GK_ATOM(onchange, "onchange")
 GK_ATOM(oncharacteristicchanged, "oncharacteristicchanged")
 GK_ATOM(onchargingchange, "onchargingchange")
 GK_ATOM(onchargingtimechange, "onchargingtimechange")
 GK_ATOM(onchecking, "onchecking")
+GK_ATOM(onCheckboxStateChange, "onCheckboxStateChange")
 GK_ATOM(onclick, "onclick")
 GK_ATOM(onclirmodechange, "onclirmodechange")
 GK_ATOM(onclose, "onclose")
 GK_ATOM(oncommand, "oncommand")
 GK_ATOM(oncommandupdate, "oncommandupdate")
 GK_ATOM(oncomplete, "oncomplete")
 GK_ATOM(oncompositionend, "oncompositionend")
 GK_ATOM(oncompositionstart, "oncompositionstart")
@@ -965,16 +966,17 @@ GK_ATOM(onpopupshown, "onpopupshown")
 GK_ATOM(onpullphonebookreq, "onpullphonebookreq")
 GK_ATOM(onpullvcardentryreq, "onpullvcardentryreq")
 GK_ATOM(onpullvcardlistingreq, "onpullvcardlistingreq")
 GK_ATOM(onpush, "onpush")
 GK_ATOM(onpushsubscriptionchange, "onpushsubscriptionchange")
 GK_ATOM(onpschange, "onpschange")
 GK_ATOM(onptychange, "onptychange")
 GK_ATOM(onradiostatechange, "onradiostatechange")
+GK_ATOM(onRadioStateChange, "onRadioStateChange")
 GK_ATOM(onrdsdisabled, "onrdsdisabled")
 GK_ATOM(onrdsenabled, "onrdsenabled")
 GK_ATOM(onreaderror, "onreaderror")
 GK_ATOM(onreadsuccess, "onreadsuccess")
 GK_ATOM(onready, "onready")
 GK_ATOM(onreadystatechange, "onreadystatechange")
 GK_ATOM(onreceived, "onreceived")
 GK_ATOM(onremoteheld, "onremoteheld")
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2883,16 +2883,21 @@ public:
   void SetDocumentAndPageUseCounter(mozilla::UseCounter aUseCounter)
   {
     SetDocumentUseCounter(aUseCounter);
     SetPageUseCounter(aUseCounter);
   }
 
   void PropagateUseCounters(nsIDocument* aParentDocument);
 
+  void SetDocumentIncCounter(mozilla::IncCounter aIncCounter, uint32_t inc = 1)
+  {
+    mIncCounters[aIncCounter] += inc;
+  }
+
   void SetUserHasInteracted(bool aUserHasInteracted)
   {
     mUserHasInteracted = aUserHasInteracted;
   }
 
   bool UserHasInteracted()
   {
     return mUserHasInteracted;
@@ -3423,16 +3428,19 @@ protected:
   // Flags for use counters used directly by this document.
   std::bitset<mozilla::eUseCounter_Count> mUseCounters;
   // Flags for use counters used by any child documents of this document.
   std::bitset<mozilla::eUseCounter_Count> mChildDocumentUseCounters;
   // Flags for whether we've notified our top-level "page" of a use counter
   // for this child document.
   std::bitset<mozilla::eUseCounter_Count> mNotifiedPageForUseCounter;
 
+  // Count the number of times something is seen in a document.
+  mozilla::Array<uint16_t, mozilla::eIncCounter_Count> mIncCounters;
+
   // Whether the user has interacted with the document or not:
   bool mUserHasInteracted;
 
   mozilla::TimeStamp mPageUnloadingEventTimeStamp;
 
   RefPtr<mozilla::dom::DocGroup> mDocGroup;
 
   // The set of all the tracking script URLs.  URLs are added to this set by
--- a/dom/base/test/intersectionobserver_window.html
+++ b/dom/base/test/intersectionobserver_window.html
@@ -22,17 +22,19 @@
           var result = records.length === 1 &&
                        records[0].rootBounds.top === 0 &&
                        records[0].rootBounds.left === 0 &&
                        records[0].rootBounds.right === viewportWidth &&
                        records[0].rootBounds.width === viewportWidth &&
                        records[0].rootBounds.bottom === viewportHeight &&
                        records[0].rootBounds.height === viewportHeight;
           if (!result) {
-              result = [records[0].rootBounds.top,
+              result = [records.length,
+                        records[0].isIntersecting,
+                        records[0].rootBounds.top,
                         records[0].rootBounds.left,
                         records[0].rootBounds.right,
                         records[0].rootBounds.width,
                         records[0].rootBounds.bottom,
                         records[0].rootBounds.height,
                         viewportWidth,
                         viewportHeight].join(',');
           }
--- a/dom/cache/Context.cpp
+++ b/dom/cache/Context.cpp
@@ -772,17 +772,18 @@ Context::ThreadsafeHandle::~ThreadsafeHa
   // always holding a strong ref to the ThreadsafeHandle via the owning
   // runnable.  So we cannot run the ThreadsafeHandle destructor simultaneously.
   if (!mStrongRef || mOwningEventTarget->IsOnCurrentThread()) {
     return;
   }
 
   // Dispatch is guaranteed to succeed here because we block shutdown until
   // all Contexts have been destroyed.
-  NS_ProxyRelease(mOwningEventTarget, mStrongRef.forget());
+  NS_ProxyRelease(
+    "Context::ThreadsafeHandle::mStrongRef", mOwningEventTarget, mStrongRef.forget());
 }
 
 void
 Context::ThreadsafeHandle::AllowToCloseOnOwningThread()
 {
   MOZ_ASSERT(mOwningEventTarget->IsOnCurrentThread());
 
   // A Context "closes" when its ref count drops to zero.  Dropping this
--- a/dom/cache/ManagerId.cpp
+++ b/dom/cache/ManagerId.cpp
@@ -60,14 +60,15 @@ ManagerId::~ManagerId()
   if (NS_IsMainThread()) {
     return;
   }
 
   // Otherwise we need to proxy to main thread to do the release
 
   // The PBackground worker thread shouldn't be running after the main thread
   // is stopped.  So main thread is guaranteed to exist here.
-  NS_ReleaseOnMainThread(mPrincipal.forget());
+  NS_ReleaseOnMainThread(
+    "ManagerId::mPrincipal", mPrincipal.forget());
 }
 
 } // namespace cache
 } // namespace dom
 } // namespace mozilla
--- a/dom/console/Console.cpp
+++ b/dom/console/Console.cpp
@@ -881,18 +881,20 @@ Console::Shutdown()
   if (NS_IsMainThread()) {
     nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
     if (obs) {
       obs->RemoveObserver(this, "inner-window-destroyed");
       obs->RemoveObserver(this, "memory-pressure");
     }
   }
 
-  NS_ReleaseOnMainThread(mStorage.forget());
-  NS_ReleaseOnMainThread(mSandbox.forget());
+  NS_ReleaseOnMainThread(
+    "Console::mStorage", mStorage.forget());
+  NS_ReleaseOnMainThread(
+    "Console::mSandbox", mSandbox.forget());
 
   mTimerRegistry.Clear();
   mCounterRegistry.Clear();
 
   mCallDataStorage.Clear();
   mCallDataStoragePending.Clear();
 
   mStatus = eShuttingDown;
--- a/dom/crypto/WebCryptoTask.cpp
+++ b/dom/crypto/WebCryptoTask.cpp
@@ -3740,14 +3740,16 @@ WebCryptoTask::~WebCryptoTask()
   MOZ_ASSERT(mReleasedNSSResources);
 
   nsNSSShutDownPreventionLock lock;
   if (!isAlreadyShutDown()) {
     shutdown(ShutdownCalledFrom::Object);
   }
 
   if (mWorkerHolder) {
-    NS_ProxyRelease(mOriginalEventTarget, mWorkerHolder.forget());
+    NS_ProxyRelease(
+      "WebCryptoTask::mWorkerHolder",
+      mOriginalEventTarget, mWorkerHolder.forget());
   }
 }
 
 } // namespace dom
 } // namespace mozilla
--- a/dom/crypto/test/test_WebCrypto.html
+++ b/dom/crypto/test/test_WebCrypto.html
@@ -486,20 +486,17 @@ TestArray.addTest(
     function encrypt(x, iv) {
       return crypto.subtle.encrypt(
         { name: "AES-CBC", iv: iv },
         x, tv.aes_cbc_enc.data);
     }
 
     function doEncrypt(x) {
       return encrypt(x, new Uint8Array(15))
-        .then(
-          null,
-          function () { return encrypt(new Uint8Array(17)); }
-        );
+        .catch(function () { return encrypt(new Uint8Array(17)); });
     }
 
     crypto.subtle.importKey("raw", tv.aes_cbc_enc.key, "AES-CBC", false, ['encrypt'])
       .then(doEncrypt)
       .then(
         error(that),
         complete(that)
       );
@@ -536,20 +533,17 @@ TestArray.addTest(
     function decrypt(x, iv) {
       return crypto.subtle.decrypt(
         { name: "AES-CBC", iv: iv },
         x, tv.aes_cbc_dec.data);
     }
 
     function doDecrypt(x) {
       return decrypt(x, new Uint8Array(15))
-        .then(
-          null,
-          function () { return decrypt(x, new Uint8Array(17)); }
-        );
+        .catch(function () { return decrypt(x, new Uint8Array(17)); });
     }
 
     crypto.subtle.importKey("raw", tv.aes_cbc_dec.key, "AES-CBC", false, ['decrypt'])
       .then(doDecrypt)
       .then(
         error(that),
         complete(that)
       );
@@ -586,20 +580,17 @@ TestArray.addTest(
     function encrypt(x, iv) {
       return crypto.subtle.encrypt(
         { name: "AES-CTR", counter: iv, length: 32 },
         x, tv.aes_ctr_enc.data);
     }
 
     function doEncrypt(x) {
       return encrypt(x, new Uint8Array(15))
-        .then(
-          null,
-          function () { return encrypt(x, new Uint8Array(17)); }
-        );
+        .catch(function () { return encrypt(x, new Uint8Array(17)); });
     }
 
     crypto.subtle.importKey("raw", tv.aes_ctr_enc.key, "AES-CTR", false, ['encrypt'])
       .then(doEncrypt)
       .then(
         error(that),
         complete(that)
       );
@@ -636,20 +627,17 @@ TestArray.addTest(
     function doDecrypt(x, iv) {
       return crypto.subtle.decrypt(
         { name: "AES-CTR", counter: iv, length: 32 },
         x, tv.aes_ctr_dec.data);
     }
 
     function decrypt(x) {
       return decrypt(x, new Uint8Array(15))
-        .then(
-          null,
-          function () { return decrypt(x, new Uint8Array(17)); }
-        );
+        .catch(function () { return decrypt(x, new Uint8Array(17)); });
     }
 
     crypto.subtle.importKey("raw", tv.aes_ctr_dec.key, "AES-CTR", false, ['decrypt'])
       .then(doDecrypt)
       .then(
         error(that),
         complete(that)
       );
--- a/dom/events/AsyncEventDispatcher.cpp
+++ b/dom/events/AsyncEventDispatcher.cpp
@@ -17,41 +17,49 @@ namespace mozilla {
 using namespace dom;
 
 /******************************************************************************
  * mozilla::AsyncEventDispatcher
  ******************************************************************************/
 
 AsyncEventDispatcher::AsyncEventDispatcher(EventTarget* aTarget,
                                            WidgetEvent& aEvent)
-  : mTarget(aTarget)
+  : mTarget(aTarget),
+    mEventMessage(eUnidentifiedEvent)
 {
   MOZ_ASSERT(mTarget);
   RefPtr<Event> event =
     EventDispatcher::CreateEvent(aTarget, nullptr, &aEvent, EmptyString());
   mEvent = event.forget();
+  mEventType.SetIsVoid(true);
   NS_ASSERTION(mEvent, "Should never fail to create an event");
   mEvent->DuplicatePrivateData();
   mEvent->SetTrusted(aEvent.IsTrusted());
 }
 
 NS_IMETHODIMP
 AsyncEventDispatcher::Run()
 {
   if (mCanceled) {
     return NS_OK;
   }
+  nsCOMPtr<nsINode> node = do_QueryInterface(mTarget);
   if (mCheckStillInDoc) {
-    nsCOMPtr<nsINode> node = do_QueryInterface(mTarget);
     MOZ_ASSERT(node);
     if (!node->IsInComposedDoc()) {
       return NS_OK;
     }
   }
   mTarget->AsyncEventRunning(this);
+  if (mEventMessage != eUnidentifiedEvent) {
+    return nsContentUtils::DispatchTrustedEvent<WidgetEvent>
+      (node->OwnerDoc(), mTarget, mEventMessage, mBubbles,
+       false /* aCancelable */, nullptr /* aDefaultAction */,
+       mOnlyChromeDispatch);
+  }
   RefPtr<Event> event = mEvent ? mEvent->InternalDOMEvent() : nullptr;
   if (!event) {
     event = NS_NewDOMEvent(mTarget, nullptr, nullptr);
     event->InitEvent(mEventType, mBubbles, false);
     event->SetTrusted(true);
   }
   if (mOnlyChromeDispatch) {
     MOZ_ASSERT(event->IsTrusted());
--- a/dom/events/AsyncEventDispatcher.h
+++ b/dom/events/AsyncEventDispatcher.h
@@ -33,32 +33,64 @@ public:
    * chrome node. In that case, if aTarget is already a chrome node,
    * the event is dispatched to it, otherwise the dispatch path starts
    * at the first chrome ancestor of that target.
    */
   AsyncEventDispatcher(nsINode* aTarget, const nsAString& aEventType,
                        bool aBubbles, bool aOnlyChromeDispatch)
     : mTarget(aTarget)
     , mEventType(aEventType)
+    , mEventMessage(eUnidentifiedEvent)
     , mBubbles(aBubbles)
     , mOnlyChromeDispatch(aOnlyChromeDispatch)
   {
   }
 
+  /**
+   * If aOnlyChromeDispatch is true, the event is dispatched to only
+   * chrome node. In that case, if aTarget is already a chrome node,
+   * the event is dispatched to it, otherwise the dispatch path starts
+   * at the first chrome ancestor of that target.
+   */
+  AsyncEventDispatcher(nsINode* aTarget,
+                       mozilla::EventMessage aEventMessage,
+                       bool aBubbles, bool aOnlyChromeDispatch)
+    : mTarget(aTarget)
+    , mEventMessage(aEventMessage)
+    , mBubbles(aBubbles)
+    , mOnlyChromeDispatch(aOnlyChromeDispatch)
+  {
+    mEventType.SetIsVoid(true);
+    MOZ_ASSERT(mEventMessage != eUnidentifiedEvent);
+  }
+
   AsyncEventDispatcher(dom::EventTarget* aTarget, const nsAString& aEventType,
                        bool aBubbles)
     : mTarget(aTarget)
     , mEventType(aEventType)
+    , mEventMessage(eUnidentifiedEvent)
     , mBubbles(aBubbles)
   {
   }
 
+  AsyncEventDispatcher(dom::EventTarget* aTarget,
+                       mozilla::EventMessage aEventMessage,
+                       bool aBubbles)
+    : mTarget(aTarget)
+    , mEventMessage(aEventMessage)
+    , mBubbles(aBubbles)
+  {
+    mEventType.SetIsVoid(true);
+    MOZ_ASSERT(mEventMessage != eUnidentifiedEvent);
+  }
+
   AsyncEventDispatcher(dom::EventTarget* aTarget, nsIDOMEvent* aEvent)
     : mTarget(aTarget)
     , mEvent(aEvent)
+    , mEventMessage(eUnidentifiedEvent)
   {
   }
 
   AsyncEventDispatcher(dom::EventTarget* aTarget, WidgetEvent& aEvent);
 
   NS_IMETHOD Run() override;
   nsresult Cancel() override;
   nsresult PostDOMEvent();
@@ -66,17 +98,21 @@ public:
 
   // Calling this causes the Run() method to check that
   // mTarget->IsInComposedDoc(). mTarget must be an nsINode or else we'll
   // assert.
   void RequireNodeInDocument();
 
   nsCOMPtr<dom::EventTarget> mTarget;
   nsCOMPtr<nsIDOMEvent> mEvent;
+  // If mEventType is set, mEventMessage will be eUnidentifiedEvent.
+  // If mEventMessage is set, mEventType will be void.
+  // They can never both be set at the same time.
   nsString              mEventType;
+  mozilla::EventMessage mEventMessage;
   bool                  mBubbles = false;
   bool                  mOnlyChromeDispatch = false;
   bool                  mCanceled = false;
   bool                  mCheckStillInDoc = false;
 };
 
 class LoadBlockingAsyncEventDispatcher final : public AsyncEventDispatcher
 {
--- a/dom/events/EventNameList.h
+++ b/dom/events/EventNameList.h
@@ -159,16 +159,24 @@ EVENT(canplay,
 EVENT(canplaythrough,
       eCanPlayThrough,
       EventNameType_HTML,
       eBasicEventClass)
 EVENT(change,
       eFormChange,
       EventNameType_HTMLXUL,
       eBasicEventClass)
+EVENT(CheckboxStateChange,
+      eFormCheckboxStateChange,
+      EventNameType_None,
+      eBasicEventClass)
+EVENT(RadioStateChange,
+      eFormRadioStateChange,
+      EventNameType_None,
+      eBasicEventClass)
 EVENT(auxclick,
       eMouseAuxClick,
       EventNameType_All,
       eMouseEventClass)
 EVENT(click,
       eMouseClick,
       EventNameType_All,
       eMouseEventClass)
--- a/dom/fetch/Fetch.cpp
+++ b/dom/fetch/Fetch.cpp
@@ -1,28 +1,30 @@
 /* -*- 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 "Fetch.h"
+#include "FetchConsumer.h"
 
 #include "nsIDocument.h"
 #include "nsIGlobalObject.h"
 #include "nsIStreamLoader.h"
 #include "nsIThreadRetargetableRequest.h"
 
 #include "nsCharSeparatedTokenizer.h"
 #include "nsDOMString.h"
 #include "nsJSUtils.h"
 #include "nsNetUtil.h"
 #include "nsReadableUtils.h"
 #include "nsStreamUtils.h"
 #include "nsStringStream.h"
+#include "nsProxyRelease.h"
 
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/dom/BodyUtil.h"
 #include "mozilla/dom/Exceptions.h"
 #include "mozilla/dom/FetchDriver.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/FormData.h"
@@ -119,17 +121,19 @@ public:
   Shutdown()
   {
     Unfollow();
   }
 
 private:
   ~FetchSignalProxy()
   {
-    NS_ProxyRelease(mMainThreadEventTarget, mSignalMainThread.forget());
+    NS_ProxyRelease(
+      "FetchSignalProxy::mSignalMainThread",
+      mMainThreadEventTarget, mSignalMainThread.forget());
   }
 };
 
 class WorkerFetchResolver final : public FetchDriverObserver
 {
   friend class MainThreadFetchRunnable;
   friend class WorkerDataAvailableRunnable;
   friend class WorkerFetchResponseEndBase;
@@ -826,308 +830,29 @@ ExtractByteStreamFromBody(const fetch::B
     return body.GetAsStream(aStream, &aContentLength, aContentTypeWithCharset,
                             charset);
   }
 
   NS_NOTREACHED("Should never reach here");
   return NS_ERROR_FAILURE;
 }
 
-namespace {
-/*
- * Called on successfully reading the complete stream.
- */
-template <class Derived>
-class ContinueConsumeBodyRunnable final : public MainThreadWorkerRunnable
-{
-  // This has been addrefed before this runnable is dispatched,
-  // released in WorkerRun().
-  FetchBody<Derived>* mFetchBody;
-  nsresult mStatus;
-  uint32_t mLength;
-  uint8_t* mResult;
-
-public:
-  ContinueConsumeBodyRunnable(FetchBody<Derived>* aFetchBody, nsresult aStatus,
-                              uint32_t aLength, uint8_t* aResult)
-    : MainThreadWorkerRunnable(aFetchBody->mWorkerPrivate)
-    , mFetchBody(aFetchBody)
-    , mStatus(aStatus)
-    , mLength(aLength)
-    , mResult(aResult)
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-  }
-
-  bool
-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
-  {
-    mFetchBody->ContinueConsumeBody(mStatus, mLength, mResult);
-    return true;
-  }
-};
-
-/*
- * Called on successfully reading the complete stream for Blob.
- */
-template <class Derived>
-class ContinueConsumeBlobBodyRunnable final : public MainThreadWorkerRunnable
-{
-  // This has been addrefed before this runnable is dispatched,
-  // released in WorkerRun().
-  FetchBody<Derived>* mFetchBody;
-  RefPtr<BlobImpl> mBlobImpl;
-
-public:
-  ContinueConsumeBlobBodyRunnable(FetchBody<Derived>* aFetchBody,
-                                  BlobImpl* aBlobImpl)
-    : MainThreadWorkerRunnable(aFetchBody->mWorkerPrivate)
-    , mFetchBody(aFetchBody)
-    , mBlobImpl(aBlobImpl)
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-    MOZ_ASSERT(mBlobImpl);
-  }
-
-  bool
-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
-  {
-    mFetchBody->ContinueConsumeBlobBody(mBlobImpl);
-    return true;
-  }
-};
-
-template <class Derived>
-class FailConsumeBodyWorkerRunnable : public MainThreadWorkerControlRunnable
-{
-  FetchBody<Derived>* mBody;
-public:
-  explicit FailConsumeBodyWorkerRunnable(FetchBody<Derived>* aBody)
-    : MainThreadWorkerControlRunnable(aBody->mWorkerPrivate)
-    , mBody(aBody)
-  {
-    AssertIsOnMainThread();
-  }
-
-  bool
-  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
-  {
-    mBody->ContinueConsumeBody(NS_ERROR_FAILURE, 0, nullptr);
-    return true;
-  }
-};
-
-/*
- * In case of failure to create a stream pump or dispatch stream completion to
- * worker, ensure we cleanup properly. Thread agnostic.
- */
-template <class Derived>
-class MOZ_STACK_CLASS AutoFailConsumeBody final
-{
-  FetchBody<Derived>* mBody;
-public:
-  explicit AutoFailConsumeBody(FetchBody<Derived>* aBody)
-    : mBody(aBody)
-  { }
-
-  ~AutoFailConsumeBody()
-  {
-    AssertIsOnMainThread();
-    if (mBody) {
-      if (mBody->mWorkerPrivate) {
-        RefPtr<FailConsumeBodyWorkerRunnable<Derived>> r =
-          new FailConsumeBodyWorkerRunnable<Derived>(mBody);
-        if (!r->Dispatch()) {
-          MOZ_CRASH("We are going to leak");
-        }
-      } else {
-        mBody->ContinueConsumeBody(NS_ERROR_FAILURE, 0, nullptr);
-      }
-    }
-  }
-
-  void
-  DontFail()
-  {
-    mBody = nullptr;
-  }
-};
-
-template <class Derived>
-class ConsumeBodyDoneObserver : public nsIStreamLoaderObserver
-                              , public MutableBlobStorageCallback
-{
-  FetchBody<Derived>* mFetchBody;
-
-public:
-  NS_DECL_THREADSAFE_ISUPPORTS
-
-  explicit ConsumeBodyDoneObserver(FetchBody<Derived>* aFetchBody)
-    : mFetchBody(aFetchBody)
-  { }
-
-  NS_IMETHOD
-  OnStreamComplete(nsIStreamLoader* aLoader,
-                   nsISupports* aCtxt,
-                   nsresult aStatus,
-                   uint32_t aResultLength,
-                   const uint8_t* aResult) override
-  {
-    MOZ_ASSERT(NS_IsMainThread());
-
-    // If the binding requested cancel, we don't need to call
-    // ContinueConsumeBody, since that is the originator.
-    if (aStatus == NS_BINDING_ABORTED) {
-      return NS_OK;
-    }
-
-    uint8_t* nonconstResult = const_cast<uint8_t*>(aResult);
-    if (mFetchBody->mWorkerPrivate) {
-      RefPtr<ContinueConsumeBodyRunnable<Derived>> r =
-        new ContinueConsumeBodyRunnable<Derived>(mFetchBody,
-                                        aStatus,
-                                        aResultLength,
-                                        nonconstResult);
-      if (!r->Dispatch()) {
-        // XXXcatalinb: The worker is shutting down, the pump will be canceled
-        // by FetchBodyWorkerHolder::Notify.
-        NS_WARNING("Could not dispatch ConsumeBodyRunnable");
-        // Return failure so that aResult is freed.
-        return NS_ERROR_FAILURE;
-      }
-    } else {
-      mFetchBody->ContinueConsumeBody(aStatus, aResultLength, nonconstResult);
-    }
-
-    // FetchBody is responsible for data.
-    return NS_SUCCESS_ADOPTED_DATA;
-  }
-
-  virtual void BlobStoreCompleted(MutableBlobStorage* aBlobStorage,
-                                  Blob* aBlob,
-                                  nsresult aRv) override
-  {
-    // On error.
-    if (NS_FAILED(aRv)) {
-      OnStreamComplete(nullptr, nullptr, aRv, 0, nullptr);
-      return;
-    }
-
-    MOZ_ASSERT(aBlob);
-
-    if (mFetchBody->mWorkerPrivate) {
-      RefPtr<ContinueConsumeBlobBodyRunnable<Derived>> r =
-        new ContinueConsumeBlobBodyRunnable<Derived>(mFetchBody, aBlob->Impl());
-
-      if (!r->Dispatch()) {
-        NS_WARNING("Could not dispatch ConsumeBlobBodyRunnable");
-        return;
-      }
-    } else {
-      mFetchBody->ContinueConsumeBlobBody(aBlob->Impl());
-    }
-  }
-
-private:
-  virtual ~ConsumeBodyDoneObserver()
-  { }
-};
-
-template <class Derived>
-NS_IMPL_ADDREF(ConsumeBodyDoneObserver<Derived>)
-template <class Derived>
-NS_IMPL_RELEASE(ConsumeBodyDoneObserver<Derived>)
-template <class Derived>
-NS_INTERFACE_MAP_BEGIN(ConsumeBodyDoneObserver<Derived>)
-  NS_INTERFACE_MAP_ENTRY(nsIStreamLoaderObserver)
-  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamLoaderObserver)
-NS_INTERFACE_MAP_END
-
-template <class Derived>
-class BeginConsumeBodyRunnable final : public Runnable
-{
-  FetchBody<Derived>* mFetchBody;
-public:
-  explicit BeginConsumeBodyRunnable(FetchBody<Derived>* aBody)
-    : mFetchBody(aBody)
-  { }
-
-  NS_IMETHOD
-  Run() override
-  {
-    mFetchBody->BeginConsumeBodyMainThread();
-    return NS_OK;
-  }
-};
-
-template <class Derived>
-class CancelPumpRunnable final : public WorkerMainThreadRunnable
-{
-  FetchBody<Derived>* mBody;
-public:
-  explicit CancelPumpRunnable(FetchBody<Derived>* aBody)
-    : WorkerMainThreadRunnable(aBody->mWorkerPrivate,
-                               NS_LITERAL_CSTRING("Fetch :: Cancel Pump"))
-    , mBody(aBody)
-  { }
-
-  bool
-  MainThreadRun() override
-  {
-    mBody->CancelPump();
-    return true;
-  }
-};
-} // namespace
-
-template <class Derived>
-class FetchBodyWorkerHolder final : public workers::WorkerHolder
-{
-  // This is addrefed before the workerHolder is created, and is released in
-  // ContinueConsumeBody() so we can hold a rawptr.
-  FetchBody<Derived>* mBody;
-  bool mWasNotified;
-
-public:
-  explicit FetchBodyWorkerHolder(FetchBody<Derived>* aBody)
-    : mBody(aBody)
-    , mWasNotified(false)
-  { }
-
-  ~FetchBodyWorkerHolder()
-  { }
-
-  bool Notify(workers::Status aStatus) override
-  {
-    MOZ_ASSERT(aStatus > workers::Running);
-    if (!mWasNotified) {
-      mWasNotified = true;
-      mBody->ContinueConsumeBody(NS_BINDING_ABORTED, 0, nullptr);
-    }
-    return true;
-  }
-};
-
 template <class Derived>
 FetchBody<Derived>::FetchBody(nsIGlobalObject* aOwner)
-  : mWorkerHolder(nullptr)
-  , mOwner(aOwner)
+  : mOwner(aOwner)
+  , mWorkerPrivate(nullptr)
   , mBodyUsed(false)
-#ifdef DEBUG
-  , mReadDone(false)
-#endif
 {
   MOZ_ASSERT(aOwner);
 
   if (!NS_IsMainThread()) {
     mWorkerPrivate = GetCurrentThreadWorkerPrivate();
     MOZ_ASSERT(mWorkerPrivate);
     mMainThreadEventTarget = mWorkerPrivate->MainThreadEventTarget();
   } else {
-    mWorkerPrivate = nullptr;
     mMainThreadEventTarget = aOwner->EventTargetFor(TaskCategory::Other);
   }
 
   MOZ_ASSERT(mMainThreadEventTarget);
 }
 
 template
 FetchBody<Request>::FetchBody(nsIGlobalObject* aOwner);
@@ -1135,399 +860,45 @@ FetchBody<Request>::FetchBody(nsIGlobalO
 template
 FetchBody<Response>::FetchBody(nsIGlobalObject* aOwner);
 
 template <class Derived>
 FetchBody<Derived>::~FetchBody()
 {
 }
 
-// Returns true if addref succeeded.
-// Always succeeds on main thread.
-// May fail on worker if RegisterWorkerHolder() fails. In that case, it will
-// release the object before returning false.
-template <class Derived>
-bool
-FetchBody<Derived>::AddRefObject()
-{
-  AssertIsOnTargetThread();
-  DerivedClass()->AddRef();
-
-  if (mWorkerPrivate && !mWorkerHolder) {
-    if (!RegisterWorkerHolder()) {
-      ReleaseObject();
-      return false;
-    }
-  }
-  return true;
-}
-
-template <class Derived>
-void
-FetchBody<Derived>::ReleaseObject()
-{
-  AssertIsOnTargetThread();
-
-  if (mWorkerPrivate && mWorkerHolder) {
-    UnregisterWorkerHolder();
-  }
-
-  DerivedClass()->Release();
-}
-
-template <class Derived>
-bool
-FetchBody<Derived>::RegisterWorkerHolder()
-{
-  MOZ_ASSERT(mWorkerPrivate);
-  mWorkerPrivate->AssertIsOnWorkerThread();
-  MOZ_ASSERT(!mWorkerHolder);
-  mWorkerHolder = new FetchBodyWorkerHolder<Derived>(this);
-
-  if (!mWorkerHolder->HoldWorker(mWorkerPrivate, Closing)) {
-    NS_WARNING("Failed to add workerHolder");
-    mWorkerHolder = nullptr;
-    return false;
-  }
-
-  return true;
-}
-
-template <class Derived>
-void
-FetchBody<Derived>::UnregisterWorkerHolder()
-{
-  MOZ_ASSERT(mWorkerPrivate);
-  mWorkerPrivate->AssertIsOnWorkerThread();
-  MOZ_ASSERT(mWorkerHolder);
-
-  mWorkerHolder->ReleaseWorker();
-  mWorkerHolder = nullptr;
-}
-
-template <class Derived>
-void
-FetchBody<Derived>::CancelPump()
-{
-  AssertIsOnMainThread();
-  MOZ_ASSERT(mConsumeBodyPump);
-  mConsumeBodyPump->Cancel(NS_BINDING_ABORTED);
-}
-
-// Return value is used by ConsumeBody to bubble the error code up to WebIDL so
-// mConsumePromise doesn't have to be rejected on early exit.
-template <class Derived>
-nsresult
-FetchBody<Derived>::BeginConsumeBody()
-{
-  AssertIsOnTargetThread();
-  MOZ_ASSERT(!mWorkerHolder);
-  MOZ_ASSERT(mConsumePromise);
-
-  // The FetchBody is not thread-safe refcounted. We addref it here and release
-  // it once the stream read is finished.
-  if (!AddRefObject()) {
-    return NS_ERROR_FAILURE;
-  }
-
-  nsCOMPtr<nsIRunnable> r = new BeginConsumeBodyRunnable<Derived>(this);
-  nsresult rv = mMainThreadEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    ReleaseObject();
-    return rv;
-  }
-  return NS_OK;
-}
-
-/*
- * BeginConsumeBodyMainThread() will automatically reject the consume promise
- * and clean up on any failures, so there is no need for callers to do so,
- * reflected in a lack of error return code.
- */
-template <class Derived>
-void
-FetchBody<Derived>::BeginConsumeBodyMainThread()
-{
-  AssertIsOnMainThread();
-  AutoFailConsumeBody<Derived> autoReject(DerivedClass());
-  nsresult rv;
-  nsCOMPtr<nsIInputStream> stream;
-  DerivedClass()->GetBody(getter_AddRefs(stream));
-  if (!stream) {
-    rv = NS_NewCStringInputStream(getter_AddRefs(stream), EmptyCString());
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return;
-    }
-  }
-
-  nsCOMPtr<nsIInputStreamPump> pump;
-  rv = NS_NewInputStreamPump(getter_AddRefs(pump),
-                             stream, -1, -1, 0, 0, false,
-                             mMainThreadEventTarget);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return;
-  }
-
-  RefPtr<ConsumeBodyDoneObserver<Derived>> p = new ConsumeBodyDoneObserver<Derived>(this);
-
-  nsCOMPtr<nsIStreamListener> listener;
-  if (mConsumeType == CONSUME_BLOB) {
-    MutableBlobStorage::MutableBlobStorageType type =
-      MutableBlobStorage::eOnlyInMemory;
-
-    const mozilla::UniquePtr<mozilla::ipc::PrincipalInfo>& principalInfo =
-      DerivedClass()->GetPrincipalInfo();
-    // We support temporary file for blobs only if the principal is known and
-    // it's system or content not in private Browsing.
-    if (principalInfo &&
-        (principalInfo->type() == mozilla::ipc::PrincipalInfo::TSystemPrincipalInfo ||
-         (principalInfo->type() == mozilla::ipc::PrincipalInfo::TContentPrincipalInfo &&
-          principalInfo->get_ContentPrincipalInfo().attrs().mPrivateBrowsingId == 0))) {
-      type = MutableBlobStorage::eCouldBeInTemporaryFile;
-    }
-
-    listener = new MutableBlobStreamListener(type, nullptr, mMimeType, p,
-                                             mMainThreadEventTarget);
-  } else {
-    nsCOMPtr<nsIStreamLoader> loader;
-    rv = NS_NewStreamLoader(getter_AddRefs(loader), p);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      return;
-    }
-
-    listener = loader;
-  }
-
-  rv = pump->AsyncRead(listener, nullptr);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return;
-  }
-
-  // Now that everything succeeded, we can assign the pump to a pointer that
-  // stays alive for the lifetime of the FetchBody.
-  mConsumeBodyPump =
-    new nsMainThreadPtrHolder<nsIInputStreamPump>(pump, mMainThreadEventTarget);
-  // It is ok for retargeting to fail and reads to happen on the main thread.
-  autoReject.DontFail();
-
-  // Try to retarget, otherwise fall back to main thread.
-  nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(pump);
-  if (rr) {
-    nsCOMPtr<nsIEventTarget> sts = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
-    rv = rr->RetargetDeliveryTo(sts);
-    if (NS_WARN_IF(NS_FAILED(rv))) {
-      NS_WARNING("Retargeting failed");
-    }
-  }
-}
-
-template <class Derived>
-void
-FetchBody<Derived>::ContinueConsumeBody(nsresult aStatus, uint32_t aResultLength, uint8_t* aResult)
-{
-  AssertIsOnTargetThread();
-  // Just a precaution to ensure ContinueConsumeBody is not called out of
-  // sync with a body read.
-  MOZ_ASSERT(mBodyUsed);
-  MOZ_ASSERT(!mReadDone);
-  MOZ_ASSERT_IF(mWorkerPrivate, mWorkerHolder);
-#ifdef DEBUG
-  mReadDone = true;
-#endif
-
-  auto autoFree = mozilla::MakeScopeExit([&] {
-    free(aResult);
-  });
-
-  MOZ_ASSERT(mConsumePromise);
-  RefPtr<Promise> localPromise = mConsumePromise.forget();
-
-  RefPtr<Derived> derivedClass = DerivedClass();
-  ReleaseObject();
-
-  if (NS_WARN_IF(NS_FAILED(aStatus))) {
-    localPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
-
-    // If binding aborted, cancel the pump. We can't assert mConsumeBodyPump.
-    // In the (admittedly rare) situation that BeginConsumeBodyMainThread()
-    // context switches out, and the worker thread gets canceled before the
-    // pump is setup, mConsumeBodyPump will be null.
-    // We've to use the !! form since non-main thread pointer access on
-    // a nsMainThreadPtrHandle is not permitted.
-    if (aStatus == NS_BINDING_ABORTED && !!mConsumeBodyPump) {
-      if (NS_IsMainThread()) {
-        CancelPump();
-      } else {
-        MOZ_ASSERT(mWorkerPrivate);
-        // In case of worker thread, we block the worker while the request is
-        // canceled on the main thread. This ensures that OnStreamComplete has
-        // a valid FetchBody around to call CancelPump and we don't release the
-        // FetchBody on the main thread.
-        RefPtr<CancelPumpRunnable<Derived>> r =
-          new CancelPumpRunnable<Derived>(this);
-        ErrorResult rv;
-        r->Dispatch(Terminating, rv);
-        if (rv.Failed()) {
-          NS_WARNING("Could not dispatch CancelPumpRunnable. Nothing we can do here");
-          // None of our callers are callled directly from JS, so there is no
-          // point in trying to propagate this failure out of here.  And
-          // localPromise is already rejected.  Just suppress the failure.
-          rv.SuppressException();
-        }
-      }
-    }
-  }
-
-  // Release the pump and then early exit if there was an error.
-  // Uses NS_ProxyRelease internally, so this is safe.
-  mConsumeBodyPump = nullptr;
-
-  // Don't warn here since we warned above.
-  if (NS_FAILED(aStatus)) {
-    return;
-  }
-
-  // Finish successfully consuming body according to type.
-  MOZ_ASSERT(aResult);
-
-  AutoJSAPI jsapi;
-  if (!jsapi.Init(derivedClass->GetParentObject())) {
-    localPromise->MaybeReject(NS_ERROR_UNEXPECTED);
-    return;
-  }
-
-  JSContext* cx = jsapi.cx();
-  ErrorResult error;
-
-  switch (mConsumeType) {
-    case CONSUME_ARRAYBUFFER: {
-      JS::Rooted<JSObject*> arrayBuffer(cx);
-      BodyUtil::ConsumeArrayBuffer(cx, &arrayBuffer, aResultLength, aResult,
-                                   error);
-
-      if (!error.Failed()) {
-        JS::Rooted<JS::Value> val(cx);
-        val.setObjectOrNull(arrayBuffer);
-
-        localPromise->MaybeResolve(cx, val);
-        // ArrayBuffer takes over ownership.
-        autoFree.release();
-      }
-      break;
-    }
-    case CONSUME_BLOB: {
-      MOZ_CRASH("This should not happen.");
-      break;
-    }
-    case CONSUME_FORMDATA: {
-      nsCString data;
-      data.Adopt(reinterpret_cast<char*>(aResult), aResultLength);
-      autoFree.release();
-
-      RefPtr<dom::FormData> fd = BodyUtil::ConsumeFormData(
-        derivedClass->GetParentObject(),
-        mMimeType, data, error);
-      if (!error.Failed()) {
-        localPromise->MaybeResolve(fd);
-      }
-      break;
-    }
-    case CONSUME_TEXT:
-      // fall through handles early exit.
-    case CONSUME_JSON: {
-      nsString decoded;
-      if (NS_SUCCEEDED(BodyUtil::ConsumeText(aResultLength, aResult, decoded))) {
-        if (mConsumeType == CONSUME_TEXT) {
-          localPromise->MaybeResolve(decoded);
-        } else {
-          JS::Rooted<JS::Value> json(cx);
-          BodyUtil::ConsumeJson(cx, &json, decoded, error);
-          if (!error.Failed()) {
-            localPromise->MaybeResolve(cx, json);
-          }
-        }
-      };
-      break;
-    }
-    default:
-      NS_NOTREACHED("Unexpected consume body type");
-  }
-
-  error.WouldReportJSException();
-  if (error.Failed()) {
-    localPromise->MaybeReject(error);
-  }
-}
-
-template <class Derived>
-void
-FetchBody<Derived>::ContinueConsumeBlobBody(BlobImpl* aBlobImpl)
-{
-  AssertIsOnTargetThread();
-  // Just a precaution to ensure ContinueConsumeBody is not called out of
-  // sync with a body read.
-  MOZ_ASSERT(mBodyUsed);
-  MOZ_ASSERT(!mReadDone);
-  MOZ_ASSERT(mConsumeType == CONSUME_BLOB);
-  MOZ_ASSERT_IF(mWorkerPrivate, mWorkerHolder);
-#ifdef DEBUG
-  mReadDone = true;
-#endif
-
-  MOZ_ASSERT(mConsumePromise);
-  RefPtr<Promise> localPromise = mConsumePromise.forget();
-
-  RefPtr<Derived> derivedClass = DerivedClass();
-  ReleaseObject();
-
-  // Release the pump and then early exit if there was an error.
-  // Uses NS_ProxyRelease internally, so this is safe.
-  mConsumeBodyPump = nullptr;
-
-  RefPtr<dom::Blob> blob =
-    dom::Blob::Create(derivedClass->GetParentObject(), aBlobImpl);
-  MOZ_ASSERT(blob);
-
-  localPromise->MaybeResolve(blob);
-}
-
 template <class Derived>
 already_AddRefed<Promise>
-FetchBody<Derived>::ConsumeBody(ConsumeType aType, ErrorResult& aRv)
+FetchBody<Derived>::ConsumeBody(FetchConsumeType aType, ErrorResult& aRv)
 {
   if (BodyUsed()) {
     aRv.ThrowTypeError<MSG_FETCH_BODY_CONSUMED_ERROR>();
     return nullptr;
   }
 
-  mConsumeType = aType;
   SetBodyUsed();
 
-  mConsumePromise = Promise::Create(DerivedClass()->GetParentObject(), aRv);
-  if (aRv.Failed()) {
+  RefPtr<Promise> promise =
+    FetchBodyConsumer<Derived>::Create(DerivedClass()->GetParentObject(),
+                                       mMainThreadEventTarget, this, aType,
+                                       aRv);
+  if (NS_WARN_IF(aRv.Failed())) {
     return nullptr;
   }
 
-  aRv = BeginConsumeBody();
-  if (NS_WARN_IF(aRv.Failed())) {
-    mConsumePromise = nullptr;
-    return nullptr;
-  }
-
-  RefPtr<Promise> promise = mConsumePromise;
   return promise.forget();
 }
 
 template
 already_AddRefed<Promise>
-FetchBody<Request>::ConsumeBody(ConsumeType aType, ErrorResult& aRv);
+FetchBody<Request>::ConsumeBody(FetchConsumeType aType, ErrorResult& aRv);
 
 template
 already_AddRefed<Promise>
-FetchBody<Response>::ConsumeBody(ConsumeType aType, ErrorResult& aRv);
+FetchBody<Response>::ConsumeBody(FetchConsumeType aType, ErrorResult& aRv);
 
 template <class Derived>
 void
 FetchBody<Derived>::SetMimeType()
 {
   // Extract mime type.
   ErrorResult result;
   nsCString contentTypeValues;
--- a/dom/fetch/Fetch.h
+++ b/dom/fetch/Fetch.h
@@ -3,29 +3,27 @@
 /* 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_Fetch_h
 #define mozilla_dom_Fetch_h
 
 #include "nsAutoPtr.h"
-#include "nsIInputStreamPump.h"
 #include "nsIStreamLoader.h"
 
 #include "nsCOMPtr.h"
 #include "nsError.h"
 #include "nsProxyRelease.h"
 #include "nsString.h"
 
 #include "mozilla/DebugOnly.h"
 #include "mozilla/ErrorResult.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/RequestBinding.h"
-#include "mozilla/dom/workers/bindings/WorkerHolder.h"
 
 class nsIGlobalObject;
 class nsIEventTarget;
 
 namespace mozilla {
 namespace dom {
 
 class BlobOrArrayBufferViewOrArrayBufferOrFormDataOrURLSearchParamsOrUSVString;
@@ -67,17 +65,26 @@ ExtractByteStreamFromBody(const fetch::O
  * Non-owning version.
  */
 nsresult
 ExtractByteStreamFromBody(const fetch::BodyInit& aBodyInit,
                           nsIInputStream** aStream,
                           nsCString& aContentType,
                           uint64_t& aContentLength);
 
-template <class Derived> class FetchBodyWorkerHolder;
+template <class Derived> class FetchBodyConsumer;
+
+enum FetchConsumeType
+{
+  CONSUME_ARRAYBUFFER,
+  CONSUME_BLOB,
+  CONSUME_FORMDATA,
+  CONSUME_JSON,
+  CONSUME_TEXT,
+};
 
 /*
  * FetchBody's body consumption uses nsIInputStreamPump to read from the
  * underlying stream to a block of memory, which is then adopted by
  * ContinueConsumeBody() and converted to the right type based on the JS
  * function called.
  *
  * Use of the nsIInputStreamPump complicates things on the worker thread.
@@ -103,18 +110,23 @@ template <class Derived> class FetchBody
  *    ensure that mFetchBody remains alive (since mConsumeBodyPump is strongly
  *    held by it) until pump->Cancel() is called. OnStreamComplete() will not
  *    do anything if the error code is NS_BINDING_ABORTED, so we don't have to
  *    worry about keeping anything alive.
  *
  * The pump is always released on the main thread.
  */
 template <class Derived>
-class FetchBody {
+class FetchBody
+{
 public:
+  friend class FetchBodyConsumer<Derived>;
+
+  NS_INLINE_DECL_PURE_VIRTUAL_REFCOUNTING
+
   bool
   BodyUsed() const { return mBodyUsed; }
 
   already_AddRefed<Promise>
   ArrayBuffer(ErrorResult& aRv)
   {
     return ConsumeBody(CONSUME_ARRAYBUFFER, aRv);
   }
@@ -139,83 +151,51 @@ public:
 
   already_AddRefed<Promise>
   Text(ErrorResult& aRv)
   {
     return ConsumeBody(CONSUME_TEXT, aRv);
   }
 
   // Utility public methods accessed by various runnables.
-  void
-  BeginConsumeBodyMainThread();
-
-  void
-  ContinueConsumeBody(nsresult aStatus, uint32_t aLength, uint8_t* aResult);
-
-  void
-  ContinueConsumeBlobBody(BlobImpl* aBlobImpl);
-
-  void
-  CancelPump();
 
   void
   SetBodyUsed()
   {
     mBodyUsed = true;
   }
 
-  // Always set whenever the FetchBody is created on the worker thread.
-  workers::WorkerPrivate* mWorkerPrivate;
-
-  // Set when consuming the body is attempted on a worker.
-  // Unset when consumption is done/aborted.
-  nsAutoPtr<workers::WorkerHolder> mWorkerHolder;
+  const nsCString&
+  MimeType() const
+  {
+    return mMimeType;
+  }
 
 protected:
   nsCOMPtr<nsIGlobalObject> mOwner;
 
+  // Always set whenever the FetchBody is created on the worker thread.
+  workers::WorkerPrivate* mWorkerPrivate;
+
   explicit FetchBody(nsIGlobalObject* aOwner);
 
   virtual ~FetchBody();
 
   void
   SetMimeType();
+
 private:
-  enum ConsumeType
-  {
-    CONSUME_ARRAYBUFFER,
-    CONSUME_BLOB,
-    CONSUME_FORMDATA,
-    CONSUME_JSON,
-    CONSUME_TEXT,
-  };
-
   Derived*
   DerivedClass() const
   {
     return static_cast<Derived*>(const_cast<FetchBody*>(this));
   }
 
-  nsresult
-  BeginConsumeBody();
-
   already_AddRefed<Promise>
-  ConsumeBody(ConsumeType aType, ErrorResult& aRv);
-
-  bool
-  AddRefObject();
-
-  void
-  ReleaseObject();
-
-  bool
-  RegisterWorkerHolder();
-
-  void
-  UnregisterWorkerHolder();
+  ConsumeBody(FetchConsumeType aType, ErrorResult& aRv);
 
   bool
   IsOnTargetThread()
   {
     return NS_IsMainThread() == !mWorkerPrivate;
   }
 
   void
@@ -223,25 +203,16 @@ private:
   {
     MOZ_ASSERT(IsOnTargetThread());
   }
 
   // Only ever set once, always on target thread.
   bool mBodyUsed;
   nsCString mMimeType;
 
-  // Only touched on target thread.
-  ConsumeType mConsumeType;
-  RefPtr<Promise> mConsumePromise;
-#ifdef DEBUG
-  bool mReadDone;
-#endif
-
-  nsMainThreadPtrHandle<nsIInputStreamPump> mConsumeBodyPump;
-
   // The main-thread event target for runnable dispatching.
   nsCOMPtr<nsIEventTarget> mMainThreadEventTarget;
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_Fetch_h
new file mode 100644
--- /dev/null
+++ b/dom/fetch/FetchConsumer.cpp
@@ -0,0 +1,679 @@
+/* -*- 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 "Fetch.h"
+#include "FetchConsumer.h"
+
+#include "nsIInputStreamPump.h"
+#include "nsProxyRelease.h"
+#include "WorkerPrivate.h"
+#include "WorkerRunnable.h"
+#include "WorkerScope.h"
+#include "Workers.h"
+
+namespace mozilla {
+namespace dom {
+
+using namespace workers;
+
+namespace {
+
+template <class Derived>
+class FetchBodyWorkerHolder final : public workers::WorkerHolder
+{
+  RefPtr<FetchBodyConsumer<Derived>> mConsumer;
+  bool mWasNotified;
+
+public:
+  explicit FetchBodyWorkerHolder(FetchBodyConsumer<Derived>* aConsumer)
+    : mConsumer(aConsumer)
+    , mWasNotified(false)
+  {
+    MOZ_ASSERT(aConsumer);
+  }
+
+  ~FetchBodyWorkerHolder() = default;
+
+  bool Notify(workers::Status aStatus) override
+  {
+    MOZ_ASSERT(aStatus > workers::Running);
+    if (!mWasNotified) {
+      mWasNotified = true;
+      // This will probably cause the releasing of the consumer.
+      // The WorkerHolder will be released as well.
+      mConsumer->ContinueConsumeBody(NS_BINDING_ABORTED, 0, nullptr);
+    }
+
+    return true;
+  }
+};
+
+template <class Derived>
+class BeginConsumeBodyRunnable final : public Runnable
+{
+  RefPtr<FetchBodyConsumer<Derived>> mFetchBodyConsumer;
+
+public:
+  explicit BeginConsumeBodyRunnable(FetchBodyConsumer<Derived>* aConsumer)
+    : mFetchBodyConsumer(aConsumer)
+  { }
+
+  NS_IMETHOD
+  Run() override
+  {
+    mFetchBodyConsumer->BeginConsumeBodyMainThread();
+    return NS_OK;
+  }
+};
+
+/*
+ * Called on successfully reading the complete stream.
+ */
+template <class Derived>
+class ContinueConsumeBodyRunnable final : public MainThreadWorkerRunnable
+{
+  RefPtr<FetchBodyConsumer<Derived>> mFetchBodyConsumer;
+  nsresult mStatus;
+  uint32_t mLength;
+  uint8_t* mResult;
+
+public:
+  ContinueConsumeBodyRunnable(FetchBodyConsumer<Derived>* aFetchBodyConsumer,
+                              nsresult aStatus, uint32_t aLength,
+                              uint8_t* aResult)
+    : MainThreadWorkerRunnable(aFetchBodyConsumer->GetWorkerPrivate())
+    , mFetchBodyConsumer(aFetchBodyConsumer)
+    , mStatus(aStatus)
+    , mLength(aLength)
+    , mResult(aResult)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+  }
+
+  bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+  {
+    mFetchBodyConsumer->ContinueConsumeBody(mStatus, mLength, mResult);
+    return true;
+  }
+};
+
+template <class Derived>
+class FailConsumeBodyWorkerRunnable : public MainThreadWorkerControlRunnable
+{
+  RefPtr<FetchBodyConsumer<Derived>> mBodyConsumer;
+
+public:
+  explicit FailConsumeBodyWorkerRunnable(FetchBodyConsumer<Derived>* aBodyConsumer)
+    : MainThreadWorkerControlRunnable(aBodyConsumer->GetWorkerPrivate())
+    , mBodyConsumer(aBodyConsumer)
+  {
+    AssertIsOnMainThread();
+  }
+
+  bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+  {
+    mBodyConsumer->ContinueConsumeBody(NS_ERROR_FAILURE, 0, nullptr);
+    return true;
+  }
+};
+
+/*
+ * In case of failure to create a stream pump or dispatch stream completion to
+ * worker, ensure we cleanup properly. Thread agnostic.
+ */
+template <class Derived>
+class MOZ_STACK_CLASS AutoFailConsumeBody final
+{
+  RefPtr<FetchBodyConsumer<Derived>> mBodyConsumer;
+
+public:
+  explicit AutoFailConsumeBody(FetchBodyConsumer<Derived>* aBodyConsumer)
+    : mBodyConsumer(aBodyConsumer)
+  {}
+
+  ~AutoFailConsumeBody()
+  {
+    AssertIsOnMainThread();
+
+    if (mBodyConsumer) {
+      if (mBodyConsumer->GetWorkerPrivate()) {
+        RefPtr<FailConsumeBodyWorkerRunnable<Derived>> r =
+          new FailConsumeBodyWorkerRunnable<Derived>(mBodyConsumer);
+        if (!r->Dispatch()) {
+          MOZ_CRASH("We are going to leak");
+        }
+      } else {
+        mBodyConsumer->ContinueConsumeBody(NS_ERROR_FAILURE, 0, nullptr);
+      }
+    }
+  }
+
+  void
+  DontFail()
+  {
+    mBodyConsumer = nullptr;
+  }
+};
+
+/*
+ * Called on successfully reading the complete stream for Blob.
+ */
+template <class Derived>
+class ContinueConsumeBlobBodyRunnable final : public MainThreadWorkerRunnable
+{
+  RefPtr<FetchBodyConsumer<Derived>> mFetchBodyConsumer;
+  RefPtr<BlobImpl> mBlobImpl;
+
+public:
+  ContinueConsumeBlobBodyRunnable(FetchBodyConsumer<Derived>* aFetchBodyConsumer,
+                                  BlobImpl* aBlobImpl)
+    : MainThreadWorkerRunnable(aFetchBodyConsumer->GetWorkerPrivate())
+    , mFetchBodyConsumer(aFetchBodyConsumer)
+    , mBlobImpl(aBlobImpl)
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+    MOZ_ASSERT(mBlobImpl);
+  }
+
+  bool
+  WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+  {
+    mFetchBodyConsumer->ContinueConsumeBlobBody(mBlobImpl);
+    return true;
+  }
+};
+
+template <class Derived>
+class ConsumeBodyDoneObserver : public nsIStreamLoaderObserver
+                              , public MutableBlobStorageCallback
+{
+  RefPtr<FetchBodyConsumer<Derived>> mFetchBodyConsumer;
+
+public:
+  NS_DECL_THREADSAFE_ISUPPORTS
+
+  explicit ConsumeBodyDoneObserver(FetchBodyConsumer<Derived>* aFetchBodyConsumer)
+    : mFetchBodyConsumer(aFetchBodyConsumer)
+  { }
+
+  NS_IMETHOD
+  OnStreamComplete(nsIStreamLoader* aLoader,
+                   nsISupports* aCtxt,
+                   nsresult aStatus,
+                   uint32_t aResultLength,
+                   const uint8_t* aResult) override
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    // If the binding requested cancel, we don't need to call
+    // ContinueConsumeBody, since that is the originator.
+    if (aStatus == NS_BINDING_ABORTED) {
+      return NS_OK;
+    }
+
+    uint8_t* nonconstResult = const_cast<uint8_t*>(aResult);
+    if (mFetchBodyConsumer->GetWorkerPrivate()) {
+      RefPtr<ContinueConsumeBodyRunnable<Derived>> r =
+        new ContinueConsumeBodyRunnable<Derived>(mFetchBodyConsumer,
+                                                 aStatus,
+                                                 aResultLength,
+                                                 nonconstResult);
+      if (!r->Dispatch()) {
+        // XXXcatalinb: The worker is shutting down, the pump will be canceled
+        // by FetchBodyWorkerHolder::Notify.
+        NS_WARNING("Could not dispatch ConsumeBodyRunnable");
+        // Return failure so that aResult is freed.
+        return NS_ERROR_FAILURE;
+      }
+    } else {
+      mFetchBodyConsumer->ContinueConsumeBody(aStatus, aResultLength,
+                                              nonconstResult);
+    }
+
+    // FetchBody is responsible for data.
+    return NS_SUCCESS_ADOPTED_DATA;
+  }
+
+  virtual void BlobStoreCompleted(MutableBlobStorage* aBlobStorage,
+                                  Blob* aBlob,
+                                  nsresult aRv) override
+  {
+    // On error.
+    if (NS_FAILED(aRv)) {
+      OnStreamComplete(nullptr, nullptr, aRv, 0, nullptr);
+      return;
+    }
+
+    MOZ_ASSERT(aBlob);
+
+    if (mFetchBodyConsumer->GetWorkerPrivate()) {
+      RefPtr<ContinueConsumeBlobBodyRunnable<Derived>> r =
+        new ContinueConsumeBlobBodyRunnable<Derived>(mFetchBodyConsumer,
+                                                     aBlob->Impl());
+
+      if (!r->Dispatch()) {
+        NS_WARNING("Could not dispatch ConsumeBlobBodyRunnable");
+        return;
+      }
+    } else {
+      mFetchBodyConsumer->ContinueConsumeBlobBody(aBlob->Impl());
+    }
+  }
+
+private:
+  virtual ~ConsumeBodyDoneObserver()
+  { }
+};
+
+template <class Derived>
+NS_IMPL_ADDREF(ConsumeBodyDoneObserver<Derived>)
+template <class Derived>
+NS_IMPL_RELEASE(ConsumeBodyDoneObserver<Derived>)
+template <class Derived>
+NS_INTERFACE_MAP_BEGIN(ConsumeBodyDoneObserver<Derived>)
+  NS_INTERFACE_MAP_ENTRY(nsIStreamLoaderObserver)
+  NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamLoaderObserver)
+NS_INTERFACE_MAP_END
+
+template <class Derived>
+class CancelPumpRunnable final : public WorkerMainThreadRunnable
+{
+  RefPtr<FetchBodyConsumer<Derived>> mBodyConsumer;
+
+public:
+  explicit CancelPumpRunnable(FetchBodyConsumer<Derived>* aBodyConsumer)
+    : WorkerMainThreadRunnable(aBodyConsumer->GetWorkerPrivate(),
+                               NS_LITERAL_CSTRING("Fetch :: Cancel Pump"))
+    , mBodyConsumer(aBodyConsumer)
+  {}
+
+  bool
+  MainThreadRun() override
+  {
+    mBodyConsumer->CancelPump();
+    return true;
+  }
+};
+
+} // anonymous
+
+template <class Derived>
+/* static */ already_AddRefed<Promise>
+FetchBodyConsumer<Derived>::Create(nsIGlobalObject* aGlobal,
+                                   nsIEventTarget* aMainThreadEventTarget,
+                                   FetchBody<Derived>* aBody,
+                                   FetchConsumeType aType,
+                                   ErrorResult& aRv)
+{
+  MOZ_ASSERT(aBody);
+  MOZ_ASSERT(aMainThreadEventTarget);
+
+  RefPtr<Promise> promise = Promise::Create(aGlobal, aRv);
+  if (aRv.Failed()) {
+    return nullptr;
+  }
+
+  WorkerPrivate* workerPrivate = nullptr;
+  if (!NS_IsMainThread()) {
+    workerPrivate = GetCurrentThreadWorkerPrivate();
+    MOZ_ASSERT(workerPrivate);
+  }
+
+  RefPtr<FetchBodyConsumer<Derived>> consumer =
+    new FetchBodyConsumer<Derived>(aMainThreadEventTarget, workerPrivate,
+                                   aBody, promise, aType);
+
+  if (!NS_IsMainThread()) {
+    MOZ_ASSERT(workerPrivate);
+    if (NS_WARN_IF(!consumer->RegisterWorkerHolder(workerPrivate))) {
+      aRv.Throw(NS_ERROR_FAILURE);
+      return nullptr;
+    }
+  }
+
+  nsCOMPtr<nsIRunnable> r = new BeginConsumeBodyRunnable<Derived>(consumer);
+  aRv = aMainThreadEventTarget->Dispatch(r.forget(), NS_DISPATCH_NORMAL);
+  if (NS_WARN_IF(aRv.Failed())) {
+    return nullptr;
+  }
+
+  return promise.forget();
+}
+
+template <class Derived>
+void
+FetchBodyConsumer<Derived>::ReleaseObject()
+{
+  AssertIsOnTargetThread();
+
+  mWorkerHolder = nullptr;
+  mBody = nullptr;
+}
+
+template <class Derived>
+FetchBodyConsumer<Derived>::FetchBodyConsumer(nsIEventTarget* aMainThreadEventTarget,
+                                              WorkerPrivate* aWorkerPrivate,
+                                              FetchBody<Derived>* aBody,
+                                              Promise* aPromise,
+                                              FetchConsumeType aType)
+  : mTargetThread(NS_GetCurrentThread())
+  , mMainThreadEventTarget(aMainThreadEventTarget)
+  , mBody(aBody)
+  , mWorkerPrivate(aWorkerPrivate)
+  , mConsumeType(aType)
+  , mConsumePromise(aPromise)
+#ifdef DEBUG
+  , mReadDone(false)
+#endif
+{
+  MOZ_ASSERT(aMainThreadEventTarget);
+  MOZ_ASSERT(aBody);
+  MOZ_ASSERT(aPromise);
+}
+
+template <class Derived>
+FetchBodyConsumer<Derived>::~FetchBodyConsumer()
+{
+  NS_ProxyRelease("FetchBodyConsumer::mBody",
+                  mTargetThread, mBody.forget());
+}
+
+template <class Derived>
+void
+FetchBodyConsumer<Derived>::AssertIsOnTargetThread() const
+{
+  MOZ_ASSERT(NS_GetCurrentThread() == mTargetThread);
+}
+
+template <class Derived>
+bool
+FetchBodyConsumer<Derived>::RegisterWorkerHolder(WorkerPrivate* aWorkerPrivate)
+{
+  MOZ_ASSERT(aWorkerPrivate);
+  aWorkerPrivate->AssertIsOnWorkerThread();
+
+  MOZ_ASSERT(!mWorkerHolder);
+  mWorkerHolder.reset(new FetchBodyWorkerHolder<Derived>(this));
+
+  if (!mWorkerHolder->HoldWorker(aWorkerPrivate, Closing)) {
+    NS_WARNING("Failed to add workerHolder");
+    mWorkerHolder = nullptr;
+    return false;
+  }
+
+  return true;
+}
+
+/*
+ * BeginConsumeBodyMainThread() will automatically reject the consume promise
+ * and clean up on any failures, so there is no need for callers to do so,
+ * reflected in a lack of error return code.
+ */
+template <class Derived>
+void
+FetchBodyConsumer<Derived>::BeginConsumeBodyMainThread()
+{
+  AssertIsOnMainThread();
+
+  AutoFailConsumeBody<Derived> autoReject(this);
+
+  nsresult rv;
+  nsCOMPtr<nsIInputStream> stream;
+  mBody->DerivedClass()->GetBody(getter_AddRefs(stream));
+  if (!stream) {
+    rv = NS_NewCStringInputStream(getter_AddRefs(stream), EmptyCString());
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return;
+    }
+  }
+
+  nsCOMPtr<nsIInputStreamPump> pump;
+  rv = NS_NewInputStreamPump(getter_AddRefs(pump),
+                             stream, -1, -1, 0, 0, false,
+                             mMainThreadEventTarget);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  RefPtr<ConsumeBodyDoneObserver<Derived>> p =
+   new ConsumeBodyDoneObserver<Derived>(this);
+
+  nsCOMPtr<nsIStreamListener> listener;
+  if (mConsumeType == CONSUME_BLOB) {
+    MutableBlobStorage::MutableBlobStorageType type =
+      MutableBlobStorage::eOnlyInMemory;
+
+    const mozilla::UniquePtr<mozilla::ipc::PrincipalInfo>& principalInfo =
+      mBody->DerivedClass()->GetPrincipalInfo();
+    // We support temporary file for blobs only if the principal is known and
+    // it's system or content not in private Browsing.
+    if (principalInfo &&
+        (principalInfo->type() == mozilla::ipc::PrincipalInfo::TSystemPrincipalInfo ||
+         (principalInfo->type() == mozilla::ipc::PrincipalInfo::TContentPrincipalInfo &&
+          principalInfo->get_ContentPrincipalInfo().attrs().mPrivateBrowsingId == 0))) {
+      type = MutableBlobStorage::eCouldBeInTemporaryFile;
+    }
+
+    listener = new MutableBlobStreamListener(type, nullptr, mBody->MimeType(),
+                                             p, mMainThreadEventTarget);
+  } else {
+    nsCOMPtr<nsIStreamLoader> loader;
+    rv = NS_NewStreamLoader(getter_AddRefs(loader), p);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      return;
+    }
+
+    listener = loader;
+  }
+
+  rv = pump->AsyncRead(listener, nullptr);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  // Now that everything succeeded, we can assign the pump to a pointer that
+  // stays alive for the lifetime of the FetchBody.
+  mConsumeBodyPump =
+    new nsMainThreadPtrHolder<nsIInputStreamPump>("FetchBodyConsumer::mConsumeBodyPump",
+                                                  pump, mMainThreadEventTarget);
+  // It is ok for retargeting to fail and reads to happen on the main thread.
+  autoReject.DontFail();
+
+  // Try to retarget, otherwise fall back to main thread.
+  nsCOMPtr<nsIThreadRetargetableRequest> rr = do_QueryInterface(pump);
+  if (rr) {
+    nsCOMPtr<nsIEventTarget> sts = do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID);
+    rv = rr->RetargetDeliveryTo(sts);
+    if (NS_WARN_IF(NS_FAILED(rv))) {
+      NS_WARNING("Retargeting failed");
+    }
+  }
+}
+
+template <class Derived>
+void
+FetchBodyConsumer<Derived>::ContinueConsumeBody(nsresult aStatus,
+                                                uint32_t aResultLength,
+                                                uint8_t* aResult)
+{
+  AssertIsOnTargetThread();
+  // Just a precaution to ensure ContinueConsumeBody is not called out of
+  // sync with a body read.
+  MOZ_ASSERT(mBody->BodyUsed());
+  MOZ_ASSERT(!mReadDone);
+#ifdef DEBUG
+  mReadDone = true;
+#endif
+
+  auto autoFree = mozilla::MakeScopeExit([&] {
+    free(aResult);
+  });
+
+  MOZ_ASSERT(mConsumePromise);
+  RefPtr<Promise> localPromise = mConsumePromise.forget();
+
+  RefPtr<FetchBodyConsumer<Derived>> self = this;
+  auto autoReleaseObject = mozilla::MakeScopeExit([&] {
+    self->ReleaseObject();
+  });
+
+  if (NS_WARN_IF(NS_FAILED(aStatus))) {
+    localPromise->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
+
+    // If binding aborted, cancel the pump. We can't assert mConsumeBodyPump.
+    // In the (admittedly rare) situation that BeginConsumeBodyMainThread()
+    // context switches out, and the worker thread gets canceled before the
+    // pump is setup, mConsumeBodyPump will be null.
+    // We've to use the !! form since non-main thread pointer access on
+    // a nsMainThreadPtrHandle is not permitted.
+    if (aStatus == NS_BINDING_ABORTED && !!mConsumeBodyPump) {
+      if (NS_IsMainThread()) {
+        CancelPump();
+      } else {
+        MOZ_ASSERT(mWorkerPrivate);
+        // In case of worker thread, we block the worker while the request is
+        // canceled on the main thread. This ensures that OnStreamComplete has
+        // a valid FetchBody around to call CancelPump and we don't release the
+        // FetchBody on the main thread.
+        RefPtr<CancelPumpRunnable<Derived>> r =
+          new CancelPumpRunnable<Derived>(this);
+        ErrorResult rv;
+        r->Dispatch(Terminating, rv);
+        if (rv.Failed()) {
+          NS_WARNING("Could not dispatch CancelPumpRunnable. Nothing we can do here");
+          // None of our callers are callled directly from JS, so there is no
+          // point in trying to propagate this failure out of here.  And
+          // localPromise is already rejected.  Just suppress the failure.
+          rv.SuppressException();
+        }
+      }
+    }
+  }
+
+  // Release the pump and then early exit if there was an error.
+  // Uses NS_ProxyRelease internally, so this is safe.
+  mConsumeBodyPump = nullptr;
+
+  // Don't warn here since we warned above.
+  if (NS_FAILED(aStatus)) {
+    return;
+  }
+
+  // Finish successfully consuming body according to type.
+  MOZ_ASSERT(aResult);
+
+  AutoJSAPI jsapi;
+  if (!jsapi.Init(mBody->DerivedClass()->GetParentObject())) {
+    localPromise->MaybeReject(NS_ERROR_UNEXPECTED);
+    return;
+  }
+
+  JSContext* cx = jsapi.cx();
+  ErrorResult error;
+
+  switch (mConsumeType) {
+    case CONSUME_ARRAYBUFFER: {
+      JS::Rooted<JSObject*> arrayBuffer(cx);
+      BodyUtil::ConsumeArrayBuffer(cx, &arrayBuffer, aResultLength, aResult,
+                                   error);
+
+      if (!error.Failed()) {
+        JS::Rooted<JS::Value> val(cx);
+        val.setObjectOrNull(arrayBuffer);
+
+        localPromise->MaybeResolve(cx, val);
+        // ArrayBuffer takes over ownership.
+        aResult = nullptr;
+      }
+      break;
+    }
+    case CONSUME_BLOB: {
+      MOZ_CRASH("This should not happen.");
+      break;
+    }
+    case CONSUME_FORMDATA: {
+      nsCString data;
+      data.Adopt(reinterpret_cast<char*>(aResult), aResultLength);
+      aResult = nullptr;
+
+      RefPtr<dom::FormData> fd =
+        BodyUtil::ConsumeFormData(mBody->DerivedClass()->GetParentObject(),
+                                  mBody->MimeType(), data, error);
+      if (!error.Failed()) {
+        localPromise->MaybeResolve(fd);
+      }
+      break;
+    }
+    case CONSUME_TEXT:
+      // fall through handles early exit.
+    case CONSUME_JSON: {
+      nsString decoded;
+      if (NS_SUCCEEDED(BodyUtil::ConsumeText(aResultLength, aResult, decoded))) {
+        if (mConsumeType == CONSUME_TEXT) {
+          localPromise->MaybeResolve(decoded);
+        } else {
+          JS::Rooted<JS::Value> json(cx);
+          BodyUtil::ConsumeJson(cx, &json, decoded, error);
+          if (!error.Failed()) {
+            localPromise->MaybeResolve(cx, json);
+          }
+        }
+      };
+      break;
+    }
+    default:
+      NS_NOTREACHED("Unexpected consume body type");
+  }
+
+  error.WouldReportJSException();
+  if (error.Failed()) {
+    localPromise->MaybeReject(error);
+  }
+}
+
+template <class Derived>
+void
+FetchBodyConsumer<Derived>::ContinueConsumeBlobBody(BlobImpl* aBlobImpl)
+{
+  AssertIsOnTargetThread();
+  // Just a precaution to ensure ContinueConsumeBody is not called out of
+  // sync with a body read.
+  MOZ_ASSERT(mBody->BodyUsed());
+  MOZ_ASSERT(!mReadDone);
+  MOZ_ASSERT(mConsumeType == CONSUME_BLOB);
+#ifdef DEBUG
+  mReadDone = true;
+#endif
+
+  MOZ_ASSERT(mConsumePromise);
+  RefPtr<Promise> localPromise = mConsumePromise.forget();
+
+  // Release the pump and then early exit if there was an error.
+  // Uses NS_ProxyRelease internally, so this is safe.
+  mConsumeBodyPump = nullptr;
+
+  RefPtr<dom::Blob> blob =
+    dom::Blob::Create(mBody->DerivedClass()->GetParentObject(), aBlobImpl);
+  MOZ_ASSERT(blob);
+
+  localPromise->MaybeResolve(blob);
+
+  ReleaseObject();
+}
+
+template <class Derived>
+void
+FetchBodyConsumer<Derived>::CancelPump()
+{
+  AssertIsOnMainThread();
+  MOZ_ASSERT(mConsumeBodyPump);
+  mConsumeBodyPump->Cancel(NS_BINDING_ABORTED);
+}
+
+} // namespace dom
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/dom/fetch/FetchConsumer.h
@@ -0,0 +1,110 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_FetchConsumer_h
+#define mozilla_dom_FetchConsumer_h
+
+#include "Fetch.h"
+
+class nsIThread;
+
+namespace mozilla {
+namespace dom {
+
+class Promise;
+
+namespace workers {
+class WorkerPrivate;
+class WorkerHolder;
+}
+
+template <class Derived> class FetchBody;
+
+// FetchBody is not thread-safe but we need to move it around threads.
+// In order to keep it alive all the time, we use a WorkerHolder, if created on
+// workers, plus a this consumer.
+template <class Derived>
+class FetchBodyConsumer final
+{
+public:
+  NS_INLINE_DECL_THREADSAFE_REFCOUNTING(FetchBodyConsumer<Derived>)
+
+  static already_AddRefed<Promise>
+  Create(nsIGlobalObject* aGlobal,
+         nsIEventTarget* aMainThreadEventTarget,
+         FetchBody<Derived>* aBody,
+         FetchConsumeType aType,
+         ErrorResult& aRv);
+
+  void
+  ReleaseObject();
+
+  FetchBody<Derived>*
+  Body() const
+  {
+    return mBody;
+  }
+
+  void
+  BeginConsumeBodyMainThread();
+
+  void
+  ContinueConsumeBody(nsresult aStatus, uint32_t aLength, uint8_t* aResult);
+
+  void
+  ContinueConsumeBlobBody(BlobImpl* aBlobImpl);
+
+  void
+  CancelPump();
+
+  workers::WorkerPrivate*
+  GetWorkerPrivate() const
+  {
+    return mWorkerPrivate;
+  }
+
+private:
+  FetchBodyConsumer(nsIEventTarget* aMainThreadEventTarget,
+                    workers::WorkerPrivate* aWorkerPrivate,
+                    FetchBody<Derived>* aBody,
+                    Promise* aPromise,
+                    FetchConsumeType aType);
+
+  ~FetchBodyConsumer();
+
+  void
+  AssertIsOnTargetThread() const;
+
+  bool
+  RegisterWorkerHolder(workers::WorkerPrivate* aWorkerPrivate);
+
+  nsCOMPtr<nsIThread> mTargetThread;
+  nsCOMPtr<nsIEventTarget> mMainThreadEventTarget;
+  RefPtr<FetchBody<Derived>> mBody;
+
+  // Set when consuming the body is attempted on a worker.
+  // Unset when consumption is done/aborted.
+  // This WorkerHolder keeps alive the consumer via a cycle.
+  UniquePtr<workers::WorkerHolder> mWorkerHolder;
+
+  // Always set whenever the FetchBodyConsumer is created on the worker thread.
+  workers::WorkerPrivate* mWorkerPrivate;
+
+  nsMainThreadPtrHandle<nsIInputStreamPump> mConsumeBodyPump;
+
+  // Only ever set once, always on target thread.
+  FetchConsumeType mConsumeType;
+  RefPtr<Promise> mConsumePromise;
+
+#ifdef DEBUG
+  bool mReadDone;
+#endif
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_FetchConsumer_h
--- a/dom/fetch/moz.build
+++ b/dom/fetch/moz.build
@@ -24,16 +24,17 @@ EXPORTS.mozilla.dom += [
     'Request.h',
     'Response.h',
 ]
 
 UNIFIED_SOURCES += [
     'BodyExtractor.cpp',
     'ChannelInfo.cpp',
     'Fetch.cpp',
+    'FetchConsumer.cpp',
     'FetchController.cpp',
     'FetchDriver.cpp',
     'FetchObserver.cpp',
     'FetchSignal.cpp',
     'FetchUtil.cpp',
     'Headers.cpp',
     'InternalHeaders.cpp',
     'InternalRequest.cpp',
--- a/dom/file/MutableBlobStorage.cpp
+++ b/dom/file/MutableBlobStorage.cpp
@@ -54,18 +54,22 @@ public:
   }
 
 private:
   ~BlobCreationDoneRunnable()
   {
     MOZ_ASSERT(mBlobStorage);
     // If something when wrong, we still have to release these objects in the
     // correct thread.
-    NS_ProxyRelease(mBlobStorage->EventTarget(), mCallback.forget());
-    NS_ProxyRelease(mBlobStorage->EventTarget(), mBlob.forget());
+    NS_ProxyRelease(
+      "BlobCreationDoneRunnable::mCallback",
+      mBlobStorage->EventTarget(), mCallback.forget());
+    NS_ProxyRelease(
+      "BlobCreationDoneRunnable::mBlob",
+      mBlobStorage->EventTarget(), mBlob.forget());
   }
 
   RefPtr<MutableBlobStorage> mBlobStorage;
   RefPtr<MutableBlobStorageCallback> mCallback;
   RefPtr<Blob> mBlob;
   nsresult mRv;
 };
 
@@ -295,18 +299,22 @@ public:
   }
 
 private:
   ~CreateBlobRunnable()
   {
     MOZ_ASSERT(mBlobStorage);
     // If something when wrong, we still have to release data in the correct
     // thread.
-    NS_ProxyRelease(mBlobStorage->EventTarget(), mParent.forget());
-    NS_ProxyRelease(mBlobStorage->EventTarget(), mCallback.forget());
+    NS_ProxyRelease(
+      "CreateBlobRunnable::mParent",
+      mBlobStorage->EventTarget(), mParent.forget());
+    NS_ProxyRelease(
+      "CreateBlobRunnable::mCallback",
+      mBlobStorage->EventTarget(), mCallback.forget());
   }