forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			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
 | 
