Title: [258664] trunk
Revision
258664
Author
[email protected]
Date
2020-03-18 15:27:22 -0700 (Wed, 18 Mar 2020)

Log Message

Add a way to mark a rejected promise as handled
https://bugs.webkit.org/show_bug.cgi?id=209241

Reviewed by Michael Saboff.

JSTests:

* stress/reject-as-handled.js: Added.
(shouldBe):
(set new):

Source/_javascript_Core:

Some of WebCore promise implementations (WebAnimation etc.) want to reject promise
as handled state to suppress UnhandledPromiseRejection tracking. For example, a
lot of WebCore implementations expose Promise DOM attributes which will be rejected
at some conditions. But we do not want to force users setting a handler for each such an
attribute.

This patch adds `JSPromise::rejectAsHandled` C++ function. This simply sets isHandledFlag
before executing `JSPromise::reject` if we are not calling a reject function yet.

* runtime/JSPromise.cpp:
(JSC::JSPromise::rejectAsHandled):
* runtime/JSPromise.h:
* tools/JSDollarVM.cpp:
(JSC::functionRejectPromiseAsHandled):
(JSC::JSDollarVM::finishCreation):

Source/WebCore:

This adds an interface using JSPromise::rejectAsHandled to DOMPromise classes.

* bindings/js/DOMPromiseProxy.h:
(WebCore::DOMPromiseProxy<IDLType>::reject):
(WebCore::DOMPromiseProxy<IDLVoid>::reject):
(WebCore::DOMPromiseProxyWithResolveCallback<IDLType>::reject):
* bindings/js/JSDOMPromiseDeferred.cpp:
(WebCore::DeferredPromise::callFunction):
(WebCore::DeferredPromise::reject):
* bindings/js/JSDOMPromiseDeferred.h:
(WebCore::DeferredPromise::reject):
(WebCore::DeferredPromise::rejectWithCallback):
(WebCore::DOMPromiseDeferredBase::reject):
(WebCore::DOMPromiseDeferredBase::rejectType):

Modified Paths

Added Paths

Diff

Modified: trunk/JSTests/ChangeLog (258663 => 258664)


--- trunk/JSTests/ChangeLog	2020-03-18 20:30:02 UTC (rev 258663)
+++ trunk/JSTests/ChangeLog	2020-03-18 22:27:22 UTC (rev 258664)
@@ -1,3 +1,14 @@
+2020-03-18  Yusuke Suzuki  <[email protected]>
+
+        Add a way to mark a rejected promise as handled
+        https://bugs.webkit.org/show_bug.cgi?id=209241
+
+        Reviewed by Michael Saboff.
+
+        * stress/reject-as-handled.js: Added.
+        (shouldBe):
+        (set new):
+
 2020-03-16  Keith Miller  <[email protected]>
 
         _javascript_ identifier grammar supports unescaped astral symbols, but JSC doesn’t

Added: trunk/JSTests/stress/reject-as-handled.js (0 => 258664)


--- trunk/JSTests/stress/reject-as-handled.js	                        (rev 0)
+++ trunk/JSTests/stress/reject-as-handled.js	2020-03-18 22:27:22 UTC (rev 258664)
@@ -0,0 +1,38 @@
+function shouldBe(actual, expected) {
+    if (actual !== expected)
+        throw new Error('bad value: ' + actual);
+}
+
+var set = new Set;
+var count = 0;
+setUnhandledRejectionCallback(function (promise) {
+    shouldBe(set.has(promise), true);
+    ++count;
+});
+var promise1 = Promise.reject(42);
+set.add(promise1);
+var promise2 = new Promise(function () { });
+$vm.rejectPromiseAsHandled(promise2, 42);
+
+// If it is already rejected, rejectPromiseAsHandled is no-op: not marking the promise as handled.
+var promise3 = Promise.reject(43);
+set.add(promise3);
+$vm.rejectPromiseAsHandled(promise3, 43);
+
+drainMicrotasks();
+shouldBe(count, 2);
+
+promise1.then($vm.abort, function (value) {
+    shouldBe(value, 42);
+    ++count;
+});
+promise2.then($vm.abort, function (value) {
+    shouldBe(value, 42);
+    ++count;
+});
+promise3.then($vm.abort, function (value) {
+    shouldBe(value, 43);
+    ++count;
+});
+drainMicrotasks();
+shouldBe(count, 5);

Modified: trunk/Source/_javascript_Core/ChangeLog (258663 => 258664)


--- trunk/Source/_javascript_Core/ChangeLog	2020-03-18 20:30:02 UTC (rev 258663)
+++ trunk/Source/_javascript_Core/ChangeLog	2020-03-18 22:27:22 UTC (rev 258664)
@@ -1,3 +1,26 @@
+2020-03-18  Yusuke Suzuki  <[email protected]>
+
+        Add a way to mark a rejected promise as handled
+        https://bugs.webkit.org/show_bug.cgi?id=209241
+
+        Reviewed by Michael Saboff.
+
+        Some of WebCore promise implementations (WebAnimation etc.) want to reject promise
+        as handled state to suppress UnhandledPromiseRejection tracking. For example, a
+        lot of WebCore implementations expose Promise DOM attributes which will be rejected
+        at some conditions. But we do not want to force users setting a handler for each such an
+        attribute.
+
+        This patch adds `JSPromise::rejectAsHandled` C++ function. This simply sets isHandledFlag
+        before executing `JSPromise::reject` if we are not calling a reject function yet.
+
+        * runtime/JSPromise.cpp:
+        (JSC::JSPromise::rejectAsHandled):
+        * runtime/JSPromise.h:
+        * tools/JSDollarVM.cpp:
+        (JSC::functionRejectPromiseAsHandled):
+        (JSC::JSDollarVM::finishCreation):
+
 2020-03-17  Yusuke Suzuki  <[email protected]>
 
         [JSC] DeleteIC patchpoint in FTL should require tag and mask registers

Modified: trunk/Source/_javascript_Core/runtime/JSPromise.cpp (258663 => 258664)


--- trunk/Source/_javascript_Core/runtime/JSPromise.cpp	2020-03-18 20:30:02 UTC (rev 258663)
+++ trunk/Source/_javascript_Core/runtime/JSPromise.cpp	2020-03-18 22:27:22 UTC (rev 258664)
@@ -182,9 +182,24 @@
     vm.promiseTimer->cancelPendingPromise(this);
 }
 
+void JSPromise::rejectAsHandled(JSGlobalObject* lexicalGlobalObject, JSValue value)
+{
+    // Setting isHandledFlag before calling reject since this removes round-trip between JSC and PromiseRejectionTracker, and it does not show an user-observable behavior.
+    VM& vm = lexicalGlobalObject->vm();
+    uint32_t flags = this->flags();
+    if (!(flags & isFirstResolvingFunctionCalledFlag))
+        internalField(static_cast<unsigned>(Field::Flags)).set(vm, this, jsNumber(flags | isHandledFlag));
+    reject(lexicalGlobalObject, value);
+}
+
 void JSPromise::reject(JSGlobalObject* lexicalGlobalObject, Exception* reason)
 {
     reject(lexicalGlobalObject, reason->value());
 }
 
+void JSPromise::rejectAsHandled(JSGlobalObject* lexicalGlobalObject, Exception* reason)
+{
+    rejectAsHandled(lexicalGlobalObject, reason->value());
+}
+
 } // namespace JSC

Modified: trunk/Source/_javascript_Core/runtime/JSPromise.h (258663 => 258664)


--- trunk/Source/_javascript_Core/runtime/JSPromise.h	2020-03-18 20:30:02 UTC (rev 258663)
+++ trunk/Source/_javascript_Core/runtime/JSPromise.h	2020-03-18 22:27:22 UTC (rev 258664)
@@ -68,7 +68,9 @@
 
     JS_EXPORT_PRIVATE void resolve(JSGlobalObject*, JSValue);
     JS_EXPORT_PRIVATE void reject(JSGlobalObject*, JSValue);
+    JS_EXPORT_PRIVATE void rejectAsHandled(JSGlobalObject*, JSValue);
     JS_EXPORT_PRIVATE void reject(JSGlobalObject*, Exception*);
+    JS_EXPORT_PRIVATE void rejectAsHandled(JSGlobalObject*, Exception*);
 
     struct DeferredData {
         WTF_FORBID_HEAP_ALLOCATION;

Modified: trunk/Source/_javascript_Core/tools/JSDollarVM.cpp (258663 => 258664)


--- trunk/Source/_javascript_Core/tools/JSDollarVM.cpp	2020-03-18 20:30:02 UTC (rev 258663)
+++ trunk/Source/_javascript_Core/tools/JSDollarVM.cpp	2020-03-18 22:27:22 UTC (rev 258664)
@@ -2929,6 +2929,14 @@
     return JSValue::encode(jsBoolean(function->canAssumeNameAndLengthAreOriginal(vm)));
 }
 
+static EncodedJSValue JSC_HOST_CALL functionRejectPromiseAsHandled(JSGlobalObject* globalObject, CallFrame* callFrame)
+{
+    JSPromise* promise = jsCast<JSPromise*>(callFrame->uncheckedArgument(0));
+    JSValue reason = callFrame->uncheckedArgument(1);
+    promise->rejectAsHandled(globalObject, reason);
+    return JSValue::encode(jsUndefined());
+}
+
 void JSDollarVM::finishCreation(VM& vm)
 {
     DollarVMAssertScope assertScope;
@@ -3063,6 +3071,7 @@
     addFunction(vm, "getConcurrently", functionGetConcurrently, 2);
 
     addFunction(vm, "hasOwnLengthProperty", functionHasOwnLengthProperty, 1);
+    addFunction(vm, "rejectPromiseAsHandled", functionRejectPromiseAsHandled, 1);
 
     m_objectDoingSideEffectPutWithoutCorrectSlotStatusStructure.set(vm, this, ObjectDoingSideEffectPutWithoutCorrectSlotStatus::createStructure(vm, globalObject, jsNull()));
 }

Modified: trunk/Source/WebCore/ChangeLog (258663 => 258664)


--- trunk/Source/WebCore/ChangeLog	2020-03-18 20:30:02 UTC (rev 258663)
+++ trunk/Source/WebCore/ChangeLog	2020-03-18 22:27:22 UTC (rev 258664)
@@ -1,3 +1,25 @@
+2020-03-18  Yusuke Suzuki  <[email protected]>
+
+        Add a way to mark a rejected promise as handled
+        https://bugs.webkit.org/show_bug.cgi?id=209241
+
+        Reviewed by Michael Saboff.
+
+        This adds an interface using JSPromise::rejectAsHandled to DOMPromise classes.
+
+        * bindings/js/DOMPromiseProxy.h:
+        (WebCore::DOMPromiseProxy<IDLType>::reject):
+        (WebCore::DOMPromiseProxy<IDLVoid>::reject):
+        (WebCore::DOMPromiseProxyWithResolveCallback<IDLType>::reject):
+        * bindings/js/JSDOMPromiseDeferred.cpp:
+        (WebCore::DeferredPromise::callFunction):
+        (WebCore::DeferredPromise::reject):
+        * bindings/js/JSDOMPromiseDeferred.h:
+        (WebCore::DeferredPromise::reject):
+        (WebCore::DeferredPromise::rejectWithCallback):
+        (WebCore::DOMPromiseDeferredBase::reject):
+        (WebCore::DOMPromiseDeferredBase::rejectType):
+
 2020-03-18  youenn fablet  <[email protected]>
 
         WebPage should own a Ref<WebFrame>

Modified: trunk/Source/WebCore/bindings/js/DOMPromiseProxy.h (258663 => 258664)


--- trunk/Source/WebCore/bindings/js/DOMPromiseProxy.h	2020-03-18 20:30:02 UTC (rev 258663)
+++ trunk/Source/WebCore/bindings/js/DOMPromiseProxy.h	2020-03-18 22:27:22 UTC (rev 258664)
@@ -51,7 +51,7 @@
 
     void resolve(typename IDLType::ParameterType);
     void resolveWithNewlyCreated(typename IDLType::ParameterType);
-    void reject(Exception);
+    void reject(Exception, RejectAsHandled = RejectAsHandled::No);
     
 private:
     Optional<ExceptionOr<Value>> m_valueOrException;
@@ -72,7 +72,7 @@
     bool isFulfilled() const;
 
     void resolve();
-    void reject(Exception);
+    void reject(Exception, RejectAsHandled = RejectAsHandled::No);
 
 private:
     Optional<ExceptionOr<void>> m_valueOrException;
@@ -102,7 +102,7 @@
 
     void resolve(typename IDLType::ParameterType);
     void resolveWithNewlyCreated(typename IDLType::ParameterType);
-    void reject(Exception);
+    void reject(Exception, RejectAsHandled = RejectAsHandled::No);
     
 private:
     ResolveCallback m_resolveCallback;
@@ -173,13 +173,13 @@
 }
 
 template<typename IDLType>
-inline void DOMPromiseProxy<IDLType>::reject(Exception exception)
+inline void DOMPromiseProxy<IDLType>::reject(Exception exception, RejectAsHandled rejectAsHandled)
 {
     ASSERT(!m_valueOrException);
 
     m_valueOrException = ExceptionOr<Value> { WTFMove(exception) };
     for (auto& deferredPromise : m_deferredPromises)
-        deferredPromise->reject(m_valueOrException->exception());
+        deferredPromise->reject(m_valueOrException->exception(), rejectAsHandled);
 }
 
 
@@ -229,12 +229,12 @@
         deferredPromise->resolve();
 }
 
-inline void DOMPromiseProxy<IDLVoid>::reject(Exception exception)
+inline void DOMPromiseProxy<IDLVoid>::reject(Exception exception, RejectAsHandled rejectAsHandled)
 {
     ASSERT(!m_valueOrException);
     m_valueOrException = ExceptionOr<void> { WTFMove(exception) };
     for (auto& deferredPromise : m_deferredPromises)
-        deferredPromise->reject(m_valueOrException->exception());
+        deferredPromise->reject(m_valueOrException->exception(), rejectAsHandled);
 }
 
 // MARK: - DOMPromiseProxyWithResolveCallback<IDLType> implementation
@@ -312,13 +312,13 @@
 }
 
 template<typename IDLType>
-inline void DOMPromiseProxyWithResolveCallback<IDLType>::reject(Exception exception)
+inline void DOMPromiseProxyWithResolveCallback<IDLType>::reject(Exception exception, RejectAsHandled rejectAsHandled)
 {
     ASSERT(!m_valueOrException);
 
     m_valueOrException = ExceptionOr<void> { WTFMove(exception) };
     for (auto& deferredPromise : m_deferredPromises)
-        deferredPromise->reject(m_valueOrException->exception());
+        deferredPromise->reject(m_valueOrException->exception(), rejectAsHandled);
 }
 
 }

Modified: trunk/Source/WebCore/bindings/js/JSDOMPromiseDeferred.cpp (258663 => 258664)


--- trunk/Source/WebCore/bindings/js/JSDOMPromiseDeferred.cpp	2020-03-18 20:30:02 UTC (rev 258663)
+++ trunk/Source/WebCore/bindings/js/JSDOMPromiseDeferred.cpp	2020-03-18 22:27:22 UTC (rev 258664)
@@ -71,6 +71,9 @@
     case ResolveMode::Reject:
         deferred()->reject(&lexicalGlobalObject, resolution);
         break;
+    case ResolveMode::RejectAsHandled:
+        deferred()->rejectAsHandled(&lexicalGlobalObject, resolution);
+        break;
     }
 
     if (m_mode == Mode::ClearPromiseOnResolve)
@@ -92,7 +95,7 @@
     DOMPromise::whenPromiseIsSettled(globalObject(), deferred(), WTFMove(callback));
 }
 
-void DeferredPromise::reject()
+void DeferredPromise::reject(RejectAsHandled rejectAsHandled)
 {
     if (shouldIgnoreRequestToFulfill())
         return;
@@ -101,10 +104,10 @@
     ASSERT(m_globalObject);
     auto& lexicalGlobalObject = *m_globalObject;
     JSC::JSLockHolder locker(&lexicalGlobalObject);
-    reject(lexicalGlobalObject, JSC::jsUndefined());
+    reject(lexicalGlobalObject, JSC::jsUndefined(), rejectAsHandled);
 }
 
-void DeferredPromise::reject(std::nullptr_t)
+void DeferredPromise::reject(std::nullptr_t, RejectAsHandled rejectAsHandled)
 {
     if (shouldIgnoreRequestToFulfill())
         return;
@@ -113,10 +116,10 @@
     ASSERT(m_globalObject);
     auto& lexicalGlobalObject = *m_globalObject;
     JSC::JSLockHolder locker(&lexicalGlobalObject);
-    reject(lexicalGlobalObject, JSC::jsNull());
+    reject(lexicalGlobalObject, JSC::jsNull(), rejectAsHandled);
 }
 
-void DeferredPromise::reject(Exception exception)
+void DeferredPromise::reject(Exception exception, RejectAsHandled rejectAsHandled)
 {
     if (shouldIgnoreRequestToFulfill())
         return;
@@ -134,7 +137,7 @@
         auto error = scope.exception()->value();
         scope.clearException();
 
-        reject<IDLAny>(error);
+        reject<IDLAny>(error, rejectAsHandled);
         return;
     }
 
@@ -145,10 +148,10 @@
         return;
     }
 
-    reject(lexicalGlobalObject, error);
+    reject(lexicalGlobalObject, error, rejectAsHandled);
 }
 
-void DeferredPromise::reject(ExceptionCode ec, const String& message)
+void DeferredPromise::reject(ExceptionCode ec, const String& message, RejectAsHandled rejectAsHandled)
 {
     if (shouldIgnoreRequestToFulfill())
         return;
@@ -166,7 +169,7 @@
         auto error = scope.exception()->value();
         scope.clearException();
 
-        reject<IDLAny>(error);
+        reject<IDLAny>(error, rejectAsHandled);
         return;
     }
 
@@ -178,10 +181,10 @@
     }
 
 
-    reject(lexicalGlobalObject, error);
+    reject(lexicalGlobalObject, error, rejectAsHandled);
 }
 
-void DeferredPromise::reject(const JSC::PrivateName& privateName)
+void DeferredPromise::reject(const JSC::PrivateName& privateName, RejectAsHandled rejectAsHandled)
 {
     if (shouldIgnoreRequestToFulfill())
         return;
@@ -190,7 +193,7 @@
     ASSERT(m_globalObject);
     JSC::JSGlobalObject* lexicalGlobalObject = m_globalObject.get();
     JSC::JSLockHolder locker(lexicalGlobalObject);
-    reject(*lexicalGlobalObject, JSC::Symbol::create(lexicalGlobalObject->vm(), privateName.uid()));
+    reject(*lexicalGlobalObject, JSC::Symbol::create(lexicalGlobalObject->vm(), privateName.uid()), rejectAsHandled);
 }
 
 void rejectPromiseWithExceptionIfAny(JSC::JSGlobalObject& lexicalGlobalObject, JSDOMGlobalObject& globalObject, JSPromise& promise)

Modified: trunk/Source/WebCore/bindings/js/JSDOMPromiseDeferred.h (258663 => 258664)


--- trunk/Source/WebCore/bindings/js/JSDOMPromiseDeferred.h	2020-03-18 20:30:02 UTC (rev 258663)
+++ trunk/Source/WebCore/bindings/js/JSDOMPromiseDeferred.h	2020-03-18 22:27:22 UTC (rev 258664)
@@ -36,6 +36,7 @@
 namespace WebCore {
 
 class JSDOMWindow;
+enum class RejectAsHandled : uint8_t { No, Yes };
 
 class DeferredPromise : public DOMGuarded<JSC::JSPromise> {
 public:
@@ -109,7 +110,7 @@
     }
 
     template<class IDLType>
-    void reject(typename IDLType::ParameterType value)
+    void reject(typename IDLType::ParameterType value, RejectAsHandled rejectAsHandled = RejectAsHandled::No)
     {
         if (shouldIgnoreRequestToFulfill())
             return;
@@ -118,14 +119,14 @@
         ASSERT(globalObject());
         JSC::JSGlobalObject* lexicalGlobalObject = globalObject();
         JSC::JSLockHolder locker(lexicalGlobalObject);
-        reject(*lexicalGlobalObject, toJS<IDLType>(*lexicalGlobalObject, *globalObject(), std::forward<typename IDLType::ParameterType>(value)));
+        reject(*lexicalGlobalObject, toJS<IDLType>(*lexicalGlobalObject, *globalObject(), std::forward<typename IDLType::ParameterType>(value)), rejectAsHandled);
     }
 
-    void reject();
-    void reject(std::nullptr_t);
-    void reject(Exception);
-    WEBCORE_EXPORT void reject(ExceptionCode, const String& = { });
-    void reject(const JSC::PrivateName&);
+    void reject(RejectAsHandled = RejectAsHandled::No);
+    void reject(std::nullptr_t, RejectAsHandled = RejectAsHandled::No);
+    void reject(Exception, RejectAsHandled = RejectAsHandled::No);
+    WEBCORE_EXPORT void reject(ExceptionCode, const String& = { }, RejectAsHandled = RejectAsHandled::No);
+    void reject(const JSC::PrivateName&, RejectAsHandled = RejectAsHandled::No);
 
     template<typename Callback>
     void resolveWithCallback(Callback callback)
@@ -141,7 +142,7 @@
     }
 
     template<typename Callback>
-    void rejectWithCallback(Callback callback)
+    void rejectWithCallback(Callback callback, RejectAsHandled rejectAsHandled = RejectAsHandled::No)
     {
         if (shouldIgnoreRequestToFulfill())
             return;
@@ -150,7 +151,7 @@
         ASSERT(globalObject());
         JSC::JSGlobalObject* lexicalGlobalObject = globalObject();
         JSC::JSLockHolder locker(lexicalGlobalObject);
-        reject(*lexicalGlobalObject, callback(*globalObject()));
+        reject(*lexicalGlobalObject, callback(*globalObject()), rejectAsHandled);
     }
 
     JSC::JSValue promise() const;
@@ -168,11 +169,14 @@
 
     JSC::JSPromise* deferred() const { return guarded(); }
 
-    enum class ResolveMode { Resolve, Reject };
+    enum class ResolveMode { Resolve, Reject, RejectAsHandled };
     WEBCORE_EXPORT void callFunction(JSC::JSGlobalObject&, ResolveMode, JSC::JSValue resolution);
 
     void resolve(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSValue resolution) { callFunction(lexicalGlobalObject, ResolveMode::Resolve, resolution); }
-    void reject(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSValue resolution) { callFunction(lexicalGlobalObject, ResolveMode::Reject, resolution); }
+    void reject(JSC::JSGlobalObject& lexicalGlobalObject, JSC::JSValue resolution, RejectAsHandled rejectAsHandled)
+    {
+        callFunction(lexicalGlobalObject, rejectAsHandled == RejectAsHandled::Yes ? ResolveMode::RejectAsHandled : ResolveMode::Reject, resolution);
+    }
 
     Mode m_mode;
 };
@@ -207,9 +211,9 @@
         return *this;
     }
 
-    void reject()
+    void reject(RejectAsHandled rejectAsHandled = RejectAsHandled::No)
     {
-        m_promise->reject();
+        m_promise->reject(rejectAsHandled);
     }
 
     template<typename... ErrorType> 
@@ -219,9 +223,9 @@
     }
 
     template<typename IDLType>
-    void rejectType(typename IDLType::ParameterType value)
+    void rejectType(typename IDLType::ParameterType value, RejectAsHandled rejectAsHandled = RejectAsHandled::No)
     {
-        m_promise->reject<IDLType>(std::forward<typename IDLType::ParameterType>(value));
+        m_promise->reject<IDLType>(std::forward<typename IDLType::ParameterType>(value), rejectAsHandled);
     }
 
     JSC::JSValue promise() const { return m_promise->promise(); };
_______________________________________________
webkit-changes mailing list
[email protected]
https://lists.webkit.org/mailman/listinfo/webkit-changes

Reply via email to