527 lines
20 KiB
C++
527 lines
20 KiB
C++
/* 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 "ExtensionTest.h"
|
|
#include "ExtensionEventManager.h"
|
|
#include "ExtensionAPICallFunctionNoReturn.h"
|
|
|
|
#include "js/Equality.h" // JS::StrictlyEqual
|
|
#include "js/PropertyAndElement.h" // JS_GetProperty
|
|
#include "mozilla/dom/ExtensionTestBinding.h"
|
|
#include "nsIGlobalObject.h"
|
|
#include "js/RegExp.h"
|
|
#include "mozilla/dom/WorkerScope.h"
|
|
#include "prenv.h"
|
|
|
|
namespace mozilla {
|
|
namespace extensions {
|
|
|
|
bool IsInAutomation(JSContext* aCx, JSObject* aGlobal) {
|
|
return NS_IsMainThread()
|
|
? xpc::IsInAutomation()
|
|
: dom::WorkerGlobalScope::IsInAutomation(aCx, aGlobal);
|
|
}
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(ExtensionTest);
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(ExtensionTest)
|
|
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(ExtensionTest, mGlobal, mExtensionBrowser,
|
|
mOnMessageEventMgr);
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(ExtensionTest)
|
|
NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMPL_WEBEXT_EVENTMGR(ExtensionTest, u"onMessage"_ns, OnMessage)
|
|
|
|
ExtensionTest::ExtensionTest(nsIGlobalObject* aGlobal,
|
|
ExtensionBrowser* aExtensionBrowser)
|
|
: mGlobal(aGlobal), mExtensionBrowser(aExtensionBrowser) {
|
|
MOZ_DIAGNOSTIC_ASSERT(mGlobal);
|
|
MOZ_DIAGNOSTIC_ASSERT(mExtensionBrowser);
|
|
}
|
|
|
|
/* static */
|
|
bool ExtensionTest::IsAllowed(JSContext* aCx, JSObject* aGlobal) {
|
|
// Allow browser.test API namespace while running in xpcshell tests.
|
|
if (PR_GetEnv("XPCSHELL_TEST_PROFILE_DIR")) {
|
|
return true;
|
|
}
|
|
|
|
return IsInAutomation(aCx, aGlobal);
|
|
}
|
|
|
|
JSObject* ExtensionTest::WrapObject(JSContext* aCx,
|
|
JS::Handle<JSObject*> aGivenProto) {
|
|
return dom::ExtensionTest_Binding::Wrap(aCx, this, aGivenProto);
|
|
}
|
|
|
|
nsIGlobalObject* ExtensionTest::GetParentObject() const { return mGlobal; }
|
|
|
|
void ExtensionTest::CallWebExtMethodAssertEq(
|
|
JSContext* aCx, const nsAString& aApiMethod,
|
|
const dom::Sequence<JS::Value>& aArgs, ErrorResult& aRv) {
|
|
uint32_t argsCount = aArgs.Length();
|
|
|
|
JS::Rooted<JS::Value> expectedVal(
|
|
aCx, argsCount > 0 ? aArgs[0] : JS::UndefinedValue());
|
|
JS::Rooted<JS::Value> actualVal(
|
|
aCx, argsCount > 1 ? aArgs[1] : JS::UndefinedValue());
|
|
JS::Rooted<JS::Value> messageVal(
|
|
aCx, argsCount > 2 ? aArgs[2] : JS::UndefinedValue());
|
|
|
|
bool isEqual;
|
|
if (NS_WARN_IF(!JS::StrictlyEqual(aCx, actualVal, expectedVal, &isEqual))) {
|
|
ThrowUnexpectedError(aCx, aRv);
|
|
return;
|
|
}
|
|
|
|
JS::Rooted<JSString*> expectedJSString(aCx, JS::ToString(aCx, expectedVal));
|
|
JS::Rooted<JSString*> actualJSString(aCx, JS::ToString(aCx, actualVal));
|
|
JS::Rooted<JSString*> messageJSString(aCx, JS::ToString(aCx, messageVal));
|
|
|
|
nsString expected;
|
|
nsString actual;
|
|
nsString message;
|
|
|
|
if (NS_WARN_IF(!AssignJSString(aCx, expected, expectedJSString) ||
|
|
!AssignJSString(aCx, actual, actualJSString) ||
|
|
!AssignJSString(aCx, message, messageJSString))) {
|
|
ThrowUnexpectedError(aCx, aRv);
|
|
return;
|
|
}
|
|
|
|
if (!isEqual && actual.Equals(expected)) {
|
|
actual.AppendLiteral(" (different)");
|
|
}
|
|
|
|
if (NS_WARN_IF(!dom::ToJSValue(aCx, expected, &expectedVal) ||
|
|
!dom::ToJSValue(aCx, actual, &actualVal) ||
|
|
!dom::ToJSValue(aCx, message, &messageVal))) {
|
|
ThrowUnexpectedError(aCx, aRv);
|
|
return;
|
|
}
|
|
|
|
dom::Sequence<JS::Value> args;
|
|
if (NS_WARN_IF(!args.AppendElement(expectedVal, fallible) ||
|
|
!args.AppendElement(actualVal, fallible) ||
|
|
!args.AppendElement(messageVal, fallible))) {
|
|
ThrowUnexpectedError(aCx, aRv);
|
|
return;
|
|
}
|
|
|
|
CallWebExtMethodNoReturn(aCx, aApiMethod, args, aRv);
|
|
}
|
|
|
|
MOZ_CAN_RUN_SCRIPT bool ExtensionTest::AssertMatchInternal(
|
|
JSContext* aCx, const JS::HandleValue aActualValue,
|
|
const JS::HandleValue aExpectedMatchValue, const nsAString& aMessagePre,
|
|
const nsAString& aMessage,
|
|
UniquePtr<dom::SerializedStackHolder> aSerializedCallerStack,
|
|
ErrorResult& aRv) {
|
|
// Stringify the actual value, if the expected value is a regexp or a string
|
|
// then it will be used as part of the matching assertion, otherwise it is
|
|
// still interpolated in the assertion message.
|
|
JS::Rooted<JSString*> actualToString(aCx, JS::ToString(aCx, aActualValue));
|
|
NS_ENSURE_TRUE(actualToString, false);
|
|
nsAutoJSString actualString;
|
|
NS_ENSURE_TRUE(actualString.init(aCx, actualToString), false);
|
|
|
|
bool matched = false;
|
|
|
|
if (aExpectedMatchValue.isObject()) {
|
|
JS::Rooted<JSObject*> expectedMatchObj(aCx,
|
|
&aExpectedMatchValue.toObject());
|
|
|
|
bool isRegexp;
|
|
NS_ENSURE_TRUE(JS::ObjectIsRegExp(aCx, expectedMatchObj, &isRegexp), false);
|
|
|
|
if (isRegexp) {
|
|
// Expected value is a regexp, test if the stringified actual value does
|
|
// match.
|
|
nsString input(actualString);
|
|
size_t index = 0;
|
|
JS::Rooted<JS::Value> rxResult(aCx);
|
|
NS_ENSURE_TRUE(JS::ExecuteRegExpNoStatics(
|
|
aCx, expectedMatchObj, input.BeginWriting(),
|
|
actualString.Length(), &index, true, &rxResult),
|
|
false);
|
|
matched = !rxResult.isNull();
|
|
} else if (JS::IsCallable(expectedMatchObj) &&
|
|
!JS::IsConstructor(expectedMatchObj)) {
|
|
// Expected value is a matcher function, execute it with the value as a
|
|
// parameter:
|
|
//
|
|
// - if the matcher function throws, steal the exception to re-raise it
|
|
// to the extension code that called the assertion method, but
|
|
// continue to still report the assertion as failed to the WebExtensions
|
|
// internals.
|
|
//
|
|
// - if the function return a falsey value, the assertion should fail and
|
|
// no exception is raised to the extension code that called the
|
|
// assertion
|
|
JS::Rooted<JS::Value> retval(aCx);
|
|
aRv.MightThrowJSException();
|
|
if (!JS::Call(aCx, JS::UndefinedHandleValue, expectedMatchObj,
|
|
JS::HandleValueArray(aActualValue), &retval)) {
|
|
aRv.StealExceptionFromJSContext(aCx);
|
|
matched = false;
|
|
} else {
|
|
matched = JS::ToBoolean(retval);
|
|
}
|
|
} else if (JS::IsConstructor(expectedMatchObj)) {
|
|
// Expected value is a constructor, test if the actual value is an
|
|
// instanceof the expected constructor.
|
|
NS_ENSURE_TRUE(
|
|
JS_HasInstance(aCx, expectedMatchObj, aActualValue, &matched), false);
|
|
} else {
|
|
// Fallback to strict equal for any other js object type we don't expect.
|
|
NS_ENSURE_TRUE(
|
|
JS::StrictlyEqual(aCx, aActualValue, aExpectedMatchValue, &matched),
|
|
false);
|
|
}
|
|
} else if (aExpectedMatchValue.isString()) {
|
|
// Expected value is a string, assertion should fail if the expected string
|
|
// isn't equal to the stringified actual value.
|
|
JS::Rooted<JSString*> expectedToString(
|
|
aCx, JS::ToString(aCx, aExpectedMatchValue));
|
|
NS_ENSURE_TRUE(expectedToString, false);
|
|
|
|
nsAutoJSString expectedString;
|
|
NS_ENSURE_TRUE(expectedString.init(aCx, expectedToString), false);
|
|
|
|
// If actual is an object and it has a message property that is a string,
|
|
// then we want to use that message string as the string to compare the
|
|
// expected one with.
|
|
//
|
|
// This is needed mainly to match the current JS implementation.
|
|
//
|
|
// TODO(Bug 1731094): as a low priority follow up, we may want to reconsider
|
|
// and compare the entire stringified error (which is also often a common
|
|
// behavior in many third party JS test frameworks).
|
|
JS::Rooted<JS::Value> messageVal(aCx);
|
|
if (aActualValue.isObject()) {
|
|
JS::Rooted<JSObject*> actualValueObj(aCx, &aActualValue.toObject());
|
|
|
|
if (!JS_GetProperty(aCx, actualValueObj, "message", &messageVal)) {
|
|
// GetProperty may raise an exception, in that case we steal the
|
|
// exception to re-raise it to the caller, but continue to still report
|
|
// the assertion as failed to the WebExtensions internals.
|
|
aRv.StealExceptionFromJSContext(aCx);
|
|
matched = false;
|
|
}
|
|
|
|
if (messageVal.isString()) {
|
|
actualToString.set(messageVal.toString());
|
|
NS_ENSURE_TRUE(actualString.init(aCx, actualToString), false);
|
|
}
|
|
}
|
|
matched = expectedString.Equals(actualString);
|
|
} else {
|
|
// Fallback to strict equal for any other js value type we don't expect.
|
|
NS_ENSURE_TRUE(
|
|
JS::StrictlyEqual(aCx, aActualValue, aExpectedMatchValue, &matched),
|
|
false);
|
|
}
|
|
|
|
// Convert the expected value to a source string, to be interpolated
|
|
// in the assertion message.
|
|
JS::Rooted<JSString*> expectedToSource(
|
|
aCx, JS_ValueToSource(aCx, aExpectedMatchValue));
|
|
NS_ENSURE_TRUE(expectedToSource, false);
|
|
nsAutoJSString expectedSource;
|
|
NS_ENSURE_TRUE(expectedSource.init(aCx, expectedToSource), false);
|
|
|
|
nsString message;
|
|
message.AppendPrintf("%s to match '%s', got '%s'",
|
|
NS_ConvertUTF16toUTF8(aMessagePre).get(),
|
|
NS_ConvertUTF16toUTF8(expectedSource).get(),
|
|
NS_ConvertUTF16toUTF8(actualString).get());
|
|
if (!aMessage.IsEmpty()) {
|
|
message.AppendPrintf(": %s", NS_ConvertUTF16toUTF8(aMessage).get());
|
|
}
|
|
|
|
// Complete the assertion by forwarding the boolean result and the
|
|
// interpolated assertion message to the test.assertTrue API method on the
|
|
// main thread.
|
|
dom::Sequence<JS::Value> assertTrueArgs;
|
|
JS::Rooted<JS::Value> arg0(aCx);
|
|
JS::Rooted<JS::Value> arg1(aCx);
|
|
NS_ENSURE_FALSE(!dom::ToJSValue(aCx, matched, &arg0) ||
|
|
!dom::ToJSValue(aCx, message, &arg1) ||
|
|
!assertTrueArgs.AppendElement(arg0, fallible) ||
|
|
!assertTrueArgs.AppendElement(arg1, fallible),
|
|
false);
|
|
|
|
auto request = CallFunctionNoReturn(u"assertTrue"_ns);
|
|
IgnoredErrorResult erv;
|
|
if (aSerializedCallerStack) {
|
|
request->SetSerializedCallerStack(std::move(aSerializedCallerStack));
|
|
}
|
|
request->Run(GetGlobalObject(), aCx, assertTrueArgs, erv);
|
|
NS_ENSURE_FALSE(erv.Failed(), false);
|
|
return true;
|
|
}
|
|
|
|
MOZ_CAN_RUN_SCRIPT void ExtensionTest::AssertThrows(
|
|
JSContext* aCx, dom::Function& aFunction,
|
|
const JS::HandleValue aExpectedError, const nsAString& aMessage,
|
|
ErrorResult& aRv) {
|
|
// Call the function that is expected to throw, then get the pending exception
|
|
// to pass it to the AssertMatchInternal.
|
|
ErrorResult erv;
|
|
erv.MightThrowJSException();
|
|
JS::Rooted<JS::Value> ignoredRetval(aCx);
|
|
aFunction.Call({}, &ignoredRetval, erv, "ExtensionTest::AssertThrows",
|
|
dom::Function::eRethrowExceptions);
|
|
|
|
bool didThrow = false;
|
|
JS::Rooted<JS::Value> exn(aCx);
|
|
|
|
if (erv.MaybeSetPendingException(aCx) && JS_GetPendingException(aCx, &exn)) {
|
|
JS_ClearPendingException(aCx);
|
|
didThrow = true;
|
|
}
|
|
|
|
// If the function did not throw, then the assertion is failed
|
|
// and the result should be forwarded to assertTrue on the main thread.
|
|
if (!didThrow) {
|
|
JS::Rooted<JSString*> expectedErrorToSource(
|
|
aCx, JS_ValueToSource(aCx, aExpectedError));
|
|
if (NS_WARN_IF(!expectedErrorToSource)) {
|
|
ThrowUnexpectedError(aCx, aRv);
|
|
return;
|
|
}
|
|
nsAutoJSString expectedErrorSource;
|
|
if (NS_WARN_IF(!expectedErrorSource.init(aCx, expectedErrorToSource))) {
|
|
ThrowUnexpectedError(aCx, aRv);
|
|
return;
|
|
}
|
|
|
|
nsString message;
|
|
message.AppendPrintf("Function did not throw, expected error '%s'",
|
|
NS_ConvertUTF16toUTF8(expectedErrorSource).get());
|
|
if (!aMessage.IsEmpty()) {
|
|
message.AppendPrintf(": %s", NS_ConvertUTF16toUTF8(aMessage).get());
|
|
}
|
|
|
|
dom::Sequence<JS::Value> assertTrueArgs;
|
|
JS::Rooted<JS::Value> arg0(aCx);
|
|
JS::Rooted<JS::Value> arg1(aCx);
|
|
if (NS_WARN_IF(!dom::ToJSValue(aCx, false, &arg0) ||
|
|
!dom::ToJSValue(aCx, message, &arg1) ||
|
|
!assertTrueArgs.AppendElement(arg0, fallible) ||
|
|
!assertTrueArgs.AppendElement(arg1, fallible))) {
|
|
ThrowUnexpectedError(aCx, aRv);
|
|
return;
|
|
}
|
|
|
|
CallWebExtMethodNoReturn(aCx, u"assertTrue"_ns, assertTrueArgs, aRv);
|
|
if (NS_WARN_IF(aRv.Failed())) {
|
|
ThrowUnexpectedError(aCx, aRv);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (NS_WARN_IF(!AssertMatchInternal(aCx, exn, aExpectedError,
|
|
u"Function threw, expecting error"_ns,
|
|
aMessage, nullptr, aRv))) {
|
|
ThrowUnexpectedError(aCx, aRv);
|
|
}
|
|
}
|
|
|
|
MOZ_CAN_RUN_SCRIPT void ExtensionTest::AssertThrows(
|
|
JSContext* aCx, dom::Function& aFunction,
|
|
const JS::HandleValue aExpectedError, ErrorResult& aRv) {
|
|
AssertThrows(aCx, aFunction, aExpectedError, EmptyString(), aRv);
|
|
}
|
|
|
|
#define ASSERT_REJECT_UNKNOWN_FAIL_STR "Failed to complete assertRejects call"
|
|
|
|
class AssertRejectsHandler final : public dom::PromiseNativeHandler {
|
|
public:
|
|
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
|
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(AssertRejectsHandler)
|
|
|
|
static void Create(ExtensionTest* aExtensionTest, dom::Promise* aPromise,
|
|
dom::Promise* outPromise,
|
|
JS::Handle<JS::Value> aExpectedMatchValue,
|
|
const nsAString& aMessage,
|
|
UniquePtr<dom::SerializedStackHolder>&& aCallerStack) {
|
|
MOZ_ASSERT(aPromise);
|
|
MOZ_ASSERT(outPromise);
|
|
MOZ_ASSERT(aExtensionTest);
|
|
|
|
RefPtr<AssertRejectsHandler> handler = new AssertRejectsHandler(
|
|
aExtensionTest, outPromise, aExpectedMatchValue, aMessage,
|
|
std::move(aCallerStack));
|
|
|
|
aPromise->AppendNativeHandler(handler);
|
|
}
|
|
|
|
MOZ_CAN_RUN_SCRIPT void ResolvedCallback(JSContext* aCx,
|
|
JS::Handle<JS::Value> aValue,
|
|
ErrorResult& aRv) override {
|
|
nsAutoJSString expectedErrorSource;
|
|
JS::Rooted<JS::Value> rootedExpectedMatchValue(aCx, mExpectedMatchValue);
|
|
JS::Rooted<JSString*> expectedErrorToSource(
|
|
aCx, JS_ValueToSource(aCx, rootedExpectedMatchValue));
|
|
if (NS_WARN_IF(!expectedErrorToSource ||
|
|
!expectedErrorSource.init(aCx, expectedErrorToSource))) {
|
|
mOutPromise->MaybeRejectWithUnknownError(ASSERT_REJECT_UNKNOWN_FAIL_STR);
|
|
return;
|
|
}
|
|
|
|
nsString message;
|
|
message.AppendPrintf("Promise resolved, expect rejection '%s'",
|
|
NS_ConvertUTF16toUTF8(expectedErrorSource).get());
|
|
|
|
if (!mMessageStr.IsEmpty()) {
|
|
message.AppendPrintf(": %s", NS_ConvertUTF16toUTF8(mMessageStr).get());
|
|
}
|
|
|
|
dom::Sequence<JS::Value> assertTrueArgs;
|
|
JS::Rooted<JS::Value> arg0(aCx);
|
|
JS::Rooted<JS::Value> arg1(aCx);
|
|
if (NS_WARN_IF(!dom::ToJSValue(aCx, false, &arg0) ||
|
|
!dom::ToJSValue(aCx, message, &arg1) ||
|
|
!assertTrueArgs.AppendElement(arg0, fallible) ||
|
|
!assertTrueArgs.AppendElement(arg1, fallible))) {
|
|
mOutPromise->MaybeRejectWithUnknownError(ASSERT_REJECT_UNKNOWN_FAIL_STR);
|
|
return;
|
|
}
|
|
|
|
IgnoredErrorResult erv;
|
|
auto request = mExtensionTest->CallFunctionNoReturn(u"assertTrue"_ns);
|
|
request->SetSerializedCallerStack(std::move(mCallerStack));
|
|
request->Run(mExtensionTest->GetGlobalObject(), aCx, assertTrueArgs, erv);
|
|
if (NS_WARN_IF(erv.Failed())) {
|
|
mOutPromise->MaybeRejectWithUnknownError(ASSERT_REJECT_UNKNOWN_FAIL_STR);
|
|
return;
|
|
}
|
|
mOutPromise->MaybeResolve(JS::UndefinedValue());
|
|
}
|
|
|
|
MOZ_CAN_RUN_SCRIPT void RejectedCallback(JSContext* aCx,
|
|
JS::Handle<JS::Value> aValue,
|
|
ErrorResult& aRv) override {
|
|
JS::Rooted<JS::Value> expectedMatchRooted(aCx, mExpectedMatchValue);
|
|
ErrorResult erv;
|
|
|
|
if (NS_WARN_IF(!MOZ_KnownLive(mExtensionTest)
|
|
->AssertMatchInternal(
|
|
aCx, aValue, expectedMatchRooted,
|
|
u"Promise rejected, expected rejection"_ns,
|
|
mMessageStr, std::move(mCallerStack), erv))) {
|
|
// Reject for other unknown errors.
|
|
mOutPromise->MaybeRejectWithUnknownError(ASSERT_REJECT_UNKNOWN_FAIL_STR);
|
|
return;
|
|
}
|
|
|
|
// Reject with the matcher function exception.
|
|
erv.WouldReportJSException();
|
|
if (erv.Failed()) {
|
|
mOutPromise->MaybeReject(std::move(erv));
|
|
return;
|
|
}
|
|
mExpectedMatchValue.setUndefined();
|
|
mOutPromise->MaybeResolveWithUndefined();
|
|
}
|
|
|
|
private:
|
|
AssertRejectsHandler(ExtensionTest* aExtensionTest, dom::Promise* mOutPromise,
|
|
JS::Handle<JS::Value> aExpectedMatchValue,
|
|
const nsAString& aMessage,
|
|
UniquePtr<dom::SerializedStackHolder>&& aCallerStack)
|
|
: mOutPromise(mOutPromise), mExtensionTest(aExtensionTest) {
|
|
MOZ_ASSERT(mOutPromise);
|
|
MOZ_ASSERT(mExtensionTest);
|
|
mozilla::HoldJSObjects(this);
|
|
mExpectedMatchValue.set(aExpectedMatchValue);
|
|
mCallerStack = std::move(aCallerStack);
|
|
mMessageStr = aMessage;
|
|
}
|
|
|
|
~AssertRejectsHandler() {
|
|
mOutPromise = nullptr;
|
|
mExtensionTest = nullptr;
|
|
mExpectedMatchValue.setUndefined();
|
|
mozilla::DropJSObjects(this);
|
|
};
|
|
|
|
RefPtr<dom::Promise> mOutPromise;
|
|
RefPtr<ExtensionTest> mExtensionTest;
|
|
JS::Heap<JS::Value> mExpectedMatchValue;
|
|
UniquePtr<dom::SerializedStackHolder> mCallerStack;
|
|
nsString mMessageStr;
|
|
};
|
|
|
|
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(AssertRejectsHandler)
|
|
NS_INTERFACE_MAP_ENTRY(nsISupports)
|
|
NS_INTERFACE_MAP_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_CLASS(AssertRejectsHandler)
|
|
|
|
NS_IMPL_CYCLE_COLLECTING_ADDREF(AssertRejectsHandler)
|
|
NS_IMPL_CYCLE_COLLECTING_RELEASE(AssertRejectsHandler)
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(AssertRejectsHandler)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mExtensionTest)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mOutPromise)
|
|
NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN(AssertRejectsHandler)
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mExpectedMatchValue)
|
|
NS_IMPL_CYCLE_COLLECTION_TRACE_END
|
|
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(AssertRejectsHandler)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mExtensionTest)
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK(mOutPromise)
|
|
tmp->mExpectedMatchValue.setUndefined();
|
|
NS_IMPL_CYCLE_COLLECTION_UNLINK_END
|
|
|
|
void ExtensionTest::AssertRejects(
|
|
JSContext* aCx, dom::Promise& aPromise,
|
|
const JS::HandleValue aExpectedError, const nsAString& aMessage,
|
|
const dom::Optional<OwningNonNull<dom::Function>>& aCallback,
|
|
JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv) {
|
|
auto* global = GetGlobalObject();
|
|
|
|
IgnoredErrorResult erv;
|
|
RefPtr<dom::Promise> outPromise = dom::Promise::Create(global, erv);
|
|
if (NS_WARN_IF(erv.Failed())) {
|
|
ThrowUnexpectedError(aCx, aRv);
|
|
return;
|
|
}
|
|
MOZ_ASSERT(outPromise);
|
|
|
|
AssertRejectsHandler::Create(this, &aPromise, outPromise, aExpectedError,
|
|
aMessage, dom::GetCurrentStack(aCx));
|
|
|
|
if (aCallback.WasPassed()) {
|
|
// In theory we could also support the callback-based behavior, but we
|
|
// only use this in tests and so we don't really need to support it
|
|
// for Chrome-compatibility reasons.
|
|
aRv.ThrowNotSupportedError("assertRejects does not support a callback");
|
|
return;
|
|
}
|
|
|
|
if (NS_WARN_IF(!ToJSValue(aCx, outPromise, aRetval))) {
|
|
ThrowUnexpectedError(aCx, aRv);
|
|
return;
|
|
}
|
|
}
|
|
|
|
void ExtensionTest::AssertRejects(
|
|
JSContext* aCx, dom::Promise& aPromise,
|
|
const JS::HandleValue aExpectedError,
|
|
const dom::Optional<OwningNonNull<dom::Function>>& aCallback,
|
|
JS::MutableHandle<JS::Value> aRetval, ErrorResult& aRv) {
|
|
AssertRejects(aCx, aPromise, aExpectedError, EmptyString(), aCallback,
|
|
aRetval, aRv);
|
|
}
|
|
|
|
} // namespace extensions
|
|
} // namespace mozilla
|