author | Wes 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 id | 91680 |
push user | kwierso@gmail.com |
push date | Wed, 21 Jun 2017 01:32:01 +0000 |
treeherder | mozilla-inbound@f7b9dc31956c [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | merge |
milestone | 56.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
|
browser/themes/shared/urlbar-star.svg | file | annotate | diff | comparison | revisions | |
toolkit/themes/shared/icons/find-arrows.svg | file | annotate | diff | comparison | revisions |
--- 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()); }