Support for promise rejection events (unhandledrejection)
authorjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 28 Apr 2017 03:33:57 +0000 (03:33 +0000)
committerjoepeck@webkit.org <joepeck@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Fri, 28 Apr 2017 03:33:57 +0000 (03:33 +0000)
https://bugs.webkit.org/show_bug.cgi?id=150358
<rdar://problem/28441651>

Reviewed by Saam Barati.

Patch by Joseph Pecoraro and Yusuke Suzuki.

LayoutTests/imported/w3c:

* web-platform-tests/WebCryptoAPI/derive_bits_keys/test_ecdh_bits-expected.txt:
* web-platform-tests/WebCryptoAPI/derive_bits_keys/test_ecdh_keys-expected.txt:
* web-platform-tests/html/semantics/embedded-content/media-elements/event_pause_noautoplay-expected.txt:
* web-platform-tests/html/semantics/embedded-content/media-elements/event_play_noautoplay-expected.txt:
* web-platform-tests/html/semantics/embedded-content/media-elements/paused_true_during_pause-expected.txt:
* web-platform-tests/html/syntax/parsing/html5lib_innerHTML_adoption01-expected.txt:
* web-platform-tests/html/webappapis/scripting/events/body-exposed-window-event-handlers-expected.txt:
* web-platform-tests/streams/piping/close-propagation-forward-expected.txt:
* web-platform-tests/streams/piping/error-propagation-backward-expected.txt:
* web-platform-tests/streams/piping/error-propagation-forward-expected.txt:
* web-platform-tests/streams/piping/flow-control-expected.txt:
* web-platform-tests/streams/piping/general-expected.txt:
* web-platform-tests/user-timing/measure_exceptions_navigation_timing-expected.txt:
Rebaseline expectations to include unhandled promise rejection messages.
Also change how the test harness reports success or failure to use the values
immediately on completion and then allow a run loop cycle before completing
the test to gather the output.

* web-platform-tests/resource-timing/rt-resource-errors.html:
This is our own WPT test yet to be uploaded, so update the test to prevent
an unexpected unhandled rejection.

Source/JavaScriptCore:

Implement support for promise.[[PromiseIsHandled]] and the
HostPromiseRejectionTracker hook for HTML to track promise rejections:
https://tc39.github.io/ecma262/#sec-host-promise-rejection-tracker
https://html.spec.whatwg.org/multipage/webappapis.html#unhandled-promise-rejections

* builtins/BuiltinNames.h:
New private symbols.

* builtins/PromiseOperations.js:
(globalPrivate.newHandledRejectedPromise):
Utility to create a rejected promise with [[PromiseIsHandled]] to true.

(globalPrivate.rejectPromise):
(globalPrivate.initializePromise):
* builtins/PromisePrototype.js:
(then):
Implement standard behavior of [[PromiseIsHandled]] and the host hook.

* runtime/JSPromise.cpp:
(JSC::JSPromise::isHandled):
* runtime/JSPromise.h:
C++ accessors for the [[PromiseIsHandled]] state.

* bytecode/BytecodeIntrinsicRegistry.cpp:
(JSC::BytecodeIntrinsicRegistry::BytecodeIntrinsicRegistry):
* bytecode/BytecodeIntrinsicRegistry.h:
Expose private values for the Reject / Handle enum values in built-ins.

* jsc.cpp:
* runtime/JSGlobalObject.h:
(JSC::JSGlobalObject::promiseResolveFunction):
Add a new GlobalObjectMethodTable hook matching the promise rejection hook.

* runtime/JSGlobalObject.cpp:
(JSC::JSGlobalObject::init):
(JSC::JSGlobalObject::visitChildren):
* runtime/JSGlobalObjectFunctions.cpp:
(JSC::globalFuncHostPromiseRejectionTracker):
* runtime/JSGlobalObjectFunctions.h:
Plumb the builtin hook through to the optional GlobalObjectMethodTable hook.

* inspector/InjectedScriptSource.js:
(InjectedScript.prototype.createFakeValueDescriptor):
Silence possible rejected promises created internally via Web Inspector.

Source/WebCore:

Implement support for the `onunhandledrejection` and `rejectionhandled` events.
They dispatch a new PromiseRejectionEvent using the ES6 HostPromiseRejectionTracker hook:
https://tc39.github.io/ecma262/#sec-host-promise-rejection-tracker
https://html.spec.whatwg.org/multipage/webappapis.html#unhandled-promise-rejections

This is currently implemented only for Documents and not yet Web Workers.

Tests: js/dom/unhandled-promise-rejection-basic.html
       js/dom/unhandled-promise-rejection-bindings-type-error.html
       js/dom/unhandled-promise-rejection-console-no-report.html
       js/dom/unhandled-promise-rejection-console-report.html
       js/dom/unhandled-promise-rejection-handle-during-event.html
       js/dom/unhandled-promise-rejection-handle-in-handler.html
       js/dom/unhandled-promise-rejection-handle.html
       js/dom/unhandled-promise-rejection-order.html

* CMakeLists.txt:
* DerivedSources.cpp:
* DerivedSources.make:
* WebCore.xcodeproj/project.pbxproj:
* dom/DOMAllInOne.cpp:
New files.

* bindings/scripts/CodeGenerator.pm:
(IsPromiseType):
* bindings/scripts/CodeGeneratorJS.pm:
(AddToIncludesForIDLType):
(GetBaseIDLType):
Binding support for Promise<T> attributes.

* bindings/js/JSDOMConvert.h:
* bindings/js/JSDOMConvertPromise.h: Copied from Source/JavaScriptCore/runtime/JSPromise.h.
(WebCore::Converter<IDLPromise<T>>::convert):
(WebCore::JSConverter<IDLPromise<T>>::convert):
Promise<T> binding conversion is currently unimplemented, which only means
web developers creating their own PromiseRejectionEvent will not get
autowrapping of values assigned to `promise` in event initialization.
Engine generated events will have expected behavior.

* bindings/js/JSDOMWindowBase.cpp:
(WebCore::JSDOMWindowBase::promiseRejectionTracker):
* bindings/js/JSDOMWindowBase.h:
* bindings/js/JSWorkerGlobalScopeBase.cpp:
Implement HostPromiseRejectionTracker hook for Document but not Worker.
Passes through to the ScriptExecutionContext's tracker.

* bindings/js/JSMainThreadExecState.cpp:
(WebCore::JSMainThreadExecState::didLeaveScriptContext):
* bindings/js/JSMainThreadExecState.h:
(WebCore::JSMainThreadExecState::~JSMainThreadExecState):
When completing script execution and performing microtasks notify
about rejected promises. Technically this should go inside of
performing a microtask checkpoint, except lacking EventLoop
concepts we use ScriptExecutionState.

* dom/EventNames.h:
* dom/EventNames.in:
* dom/PromiseRejectionEvent.cpp: Added.
(WebCore::PromiseRejectionEvent::PromiseRejectionEvent):
(WebCore::PromiseRejectionEvent::~PromiseRejectionEvent):
* dom/PromiseRejectionEvent.h: Added.
* dom/PromiseRejectionEvent.idl: Added.
New PromiseRejectionEvent event interface.

* dom/GlobalEventHandlers.idl:
New onunhandledrejection and onrejectionhandled.

* dom/RejectedPromiseTracker.cpp: Added.
(WebCore::RejectedPromise::RejectedPromise):
(WebCore::RejectedPromise::globalObject):
(WebCore::RejectedPromise::promise):
(WebCore::UnhandledPromise::UnhandledPromise):
(WebCore::UnhandledPromise::callStack):
(WebCore::RejectedPromiseTracker::RejectedPromiseTracker):
(WebCore::RejectedPromiseTracker::~RejectedPromiseTracker):
(WebCore::createScriptCallStackFromReason):
(WebCore::RejectedPromiseTracker::promiseRejected):
(WebCore::RejectedPromiseTracker::promiseHandled):
(WebCore::RejectedPromiseTracker::processQueueSoon):
(WebCore::RejectedPromiseTracker::reportUnhandledRejections):
(WebCore::RejectedPromiseTracker::reportRejectionHandled):
* dom/RejectedPromiseTracker.h: Added.
Track and report rejected promises. The promises are tracked weakly
allowing them to be collected before they are reported. When reporting
we dispatch PromiseRejectionEvent events, and if the default is not
prevented we log a message to the console.

* dom/ScriptExecutionContext.cpp:
(WebCore::ScriptExecutionContext::reportUnhandledPromiseRejection):
(WebCore::ScriptExecutionContext::ensureRejectedPromiseTrackerSlow):
* dom/ScriptExecutionContext.h:
(WebCore::ScriptExecutionContext::ensureRejectedPromiseTracker):
Each ScriptExecutionContext can own a rejected promise tracker.

* html/HTMLMediaElement.cpp:
(WebCore::HTMLMediaElement::pauseInternal):
https://html.spec.whatwg.org/multipage/embedded-content.html#internal-pause-steps
Internal pause steps say to timeupdate, pause, and rejecting pending play promises
should all happen in a queued task. Here the first two actions are already scheduled
on tasks, but rejecting play promises was not being done in a task, so this makes
that change.

* Modules/streams/ReadableStream.js:
(pipeThrough):
* Modules/streams/ReadableStreamInternals.js:
(readableStreamReaderGenericInitialize):
(readableStreamError):
(readableStreamReaderGenericRelease):
Satisfy parts of the Streams specification which state to set the
[[PromiseIsHandled]] internal state of promises created internally
by the Streams APIs. This prevents some internal promises from
appearing as unhandled promise rejections.

LayoutTests:

* js/dom/unhandled-promise-rejection-basic-expected.txt: Added.
* js/dom/unhandled-promise-rejection-basic.html: Added.
* js/dom/unhandled-promise-rejection-bindings-type-error-expected.txt: Added.
* js/dom/unhandled-promise-rejection-bindings-type-error.html: Added.
* js/dom/unhandled-promise-rejection-console-no-report-expected.txt: Added.
* js/dom/unhandled-promise-rejection-console-no-report.html: Added.
* js/dom/unhandled-promise-rejection-console-report-expected.txt: Added.
* js/dom/unhandled-promise-rejection-console-report.html: Added.
* js/dom/unhandled-promise-rejection-handle-during-event-expected.txt: Added.
* js/dom/unhandled-promise-rejection-handle-during-event.html: Added.
* js/dom/unhandled-promise-rejection-handle-expected.txt: Added.
* js/dom/unhandled-promise-rejection-handle-in-handler-expected.txt: Added.
* js/dom/unhandled-promise-rejection-handle-in-handler.html: Added.
* js/dom/unhandled-promise-rejection-handle.html: Added.
* js/dom/unhandled-promise-rejection-order-expected.txt: Added.
* js/dom/unhandled-promise-rejection-order.html: Added.
New tests specific to the `onunhandledrejection` and `onrejectionhandled` events.

* resources/testharnessreport.js:
(self.testRunner.add_completion_callback.sanitize):
(self.testRunner.add_completion_callback):
Report results immediately and then finish the test after a turn. This way
if the test ends with a pass, but may get unhandled rejections after
completing which should not make the test appear as if it failed. Some tests
have unhandled promise rejections but are expected to pass. Likewise some
tests perform cleanup in their own completion callbacks, which happen after
this initial completion callback, and we want to report results after all
the work is done as it may eliminate non-deterministic debug test output.

* TestExpectations:
Mark some tests as flakey that have sometimes have unhandled promise rejections.
These tests are all various imported tests that use the testharness.

* fast/mediastream/MediaStream-MediaElement-setObject-null-expected.txt:
* http/tests/security/video-cross-origin-caching-expected.txt:
* inspector/debugger/break-on-exception-throw-in-promise-expected.txt:
* inspector/debugger/break-on-uncaught-exception-throw-in-promise-expected.txt:
* inspector/worker/resources-in-worker-expected.txt:
* js/dom/dom-static-property-for-in-iteration-expected.txt:
* js/dom/global-constructors-attributes-dedicated-worker-expected.txt:
* js/promises-tests/promises-tests-2-2-6-expected.txt:
* platform/mac/inspector/model/remote-object-expected.txt:
* webrtc/libwebrtc/release-while-creating-offer-expected.txt:
* webrtc/libwebrtc/release-while-setting-local-description-expected.txt:
Update tests with unhandled promise rejection messages.

* media/W3C/audio/events/event_pause_manual.html:
* media/audio-playback-restriction-play-expected.txt:
* media/audio-playback-restriction-play.html:
* media/click-volume-bar-not-pausing.html:
* media/remote-control-command-is-user-gesture-expected.txt:
* media/remote-control-command-is-user-gesture.html:
* media/track/track-mode.html:
* media/video-autoplay-allowed-but-fullscreen-required.html:
* media/video-display-none-crash.html:
* media/video-main-content-deny-display-none.html:
* media/video-main-content-deny-not-in-dom.html:
* media/video-main-content-deny-not-visible.html:
* media/video-main-content-deny-obscured.html:
* media/video-main-content-deny-too-small.html:
* media/video-multiple-concurrent-playback-expected.txt:
* media/video-play-audio-require-user-gesture-expected.txt:
* media/video-play-audio-require-user-gesture.html:
* media/video-play-pause-events-expected.txt:
* media/video-play-pause-events.html:
* media/video-play-pause-exception-expected.txt:
* media/video-play-pause-exception.html:
* media/video-play-require-user-gesture-expected.txt:
* media/video-play-require-user-gesture.html:
* media/video-preload-expected.txt:
* media/video-preload.html:
* media/video-test.js:
(handlePromise.handle):
Snuff many possible unhandled promise rejections in media via media.play().

* streams/reference-implementation/pipe-to-expected.txt:
* streams/reference-implementation/pipe-to-options-expected.txt:
* streams/reference-implementation/readable-stream-templated-expected.txt:
* streams/reference-implementation/writable-stream-abort-expected.txt:
* streams/reference-implementation/writable-stream-expected.txt:
These stream tests are out of date and produce unhandled rejections.

* streams/shadowing-Promise.html:
Update non-imported tests to prevent unhandled rejection messages.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@215916 268f45cc-cd09-0410-ab3c-d52691b4dbfc

128 files changed:
LayoutTests/ChangeLog
LayoutTests/TestExpectations
LayoutTests/fast/mediastream/MediaStream-MediaElement-setObject-null-expected.txt
LayoutTests/http/tests/security/video-cross-origin-caching-expected.txt
LayoutTests/imported/w3c/ChangeLog
LayoutTests/imported/w3c/web-platform-tests/WebCryptoAPI/derive_bits_keys/test_ecdh_bits-expected.txt
LayoutTests/imported/w3c/web-platform-tests/WebCryptoAPI/derive_bits_keys/test_ecdh_keys-expected.txt
LayoutTests/imported/w3c/web-platform-tests/html/semantics/embedded-content/media-elements/event_pause_noautoplay-expected.txt
LayoutTests/imported/w3c/web-platform-tests/html/semantics/embedded-content/media-elements/event_play_noautoplay-expected.txt
LayoutTests/imported/w3c/web-platform-tests/html/semantics/embedded-content/media-elements/paused_true_during_pause-expected.txt
LayoutTests/imported/w3c/web-platform-tests/html/syntax/parsing/html5lib_innerHTML_adoption01-expected.txt
LayoutTests/imported/w3c/web-platform-tests/html/webappapis/scripting/events/body-exposed-window-event-handlers-expected.txt
LayoutTests/imported/w3c/web-platform-tests/resource-timing/rt-resource-errors.html
LayoutTests/imported/w3c/web-platform-tests/streams/piping/close-propagation-forward-expected.txt
LayoutTests/imported/w3c/web-platform-tests/streams/piping/error-propagation-backward-expected.txt
LayoutTests/imported/w3c/web-platform-tests/streams/piping/error-propagation-forward-expected.txt
LayoutTests/imported/w3c/web-platform-tests/streams/piping/flow-control-expected.txt
LayoutTests/imported/w3c/web-platform-tests/streams/piping/general-expected.txt
LayoutTests/imported/w3c/web-platform-tests/user-timing/measure_exceptions_navigation_timing-expected.txt
LayoutTests/inspector/debugger/break-on-exception-throw-in-promise-expected.txt
LayoutTests/inspector/debugger/break-on-uncaught-exception-throw-in-promise-expected.txt
LayoutTests/inspector/worker/resources-in-worker-expected.txt
LayoutTests/js/dom/dom-static-property-for-in-iteration-expected.txt
LayoutTests/js/dom/global-constructors-attributes-dedicated-worker-expected.txt
LayoutTests/js/dom/unhandled-promise-rejection-basic-expected.txt [new file with mode: 0644]
LayoutTests/js/dom/unhandled-promise-rejection-basic.html [new file with mode: 0644]
LayoutTests/js/dom/unhandled-promise-rejection-bindings-type-error-expected.txt [new file with mode: 0644]
LayoutTests/js/dom/unhandled-promise-rejection-bindings-type-error.html [new file with mode: 0644]
LayoutTests/js/dom/unhandled-promise-rejection-console-no-report-expected.txt [new file with mode: 0644]
LayoutTests/js/dom/unhandled-promise-rejection-console-no-report.html [new file with mode: 0644]
LayoutTests/js/dom/unhandled-promise-rejection-console-report-expected.txt [new file with mode: 0644]
LayoutTests/js/dom/unhandled-promise-rejection-console-report.html [new file with mode: 0644]
LayoutTests/js/dom/unhandled-promise-rejection-handle-during-event-expected.txt [new file with mode: 0644]
LayoutTests/js/dom/unhandled-promise-rejection-handle-during-event.html [new file with mode: 0644]
LayoutTests/js/dom/unhandled-promise-rejection-handle-expected.txt [new file with mode: 0644]
LayoutTests/js/dom/unhandled-promise-rejection-handle-in-handler-expected.txt [new file with mode: 0644]
LayoutTests/js/dom/unhandled-promise-rejection-handle-in-handler.html [new file with mode: 0644]
LayoutTests/js/dom/unhandled-promise-rejection-handle.html [new file with mode: 0644]
LayoutTests/js/dom/unhandled-promise-rejection-order-expected.txt [new file with mode: 0644]
LayoutTests/js/dom/unhandled-promise-rejection-order.html [new file with mode: 0644]
LayoutTests/js/promises-tests/promises-tests-2-2-6-expected.txt
LayoutTests/media/W3C/w3cwrapper.js
LayoutTests/media/audio-playback-restriction-play-expected.txt
LayoutTests/media/audio-playback-restriction-play.html
LayoutTests/media/click-volume-bar-not-pausing.html
LayoutTests/media/media-file.js
LayoutTests/media/modern-media-controls/media-controller/media-controller-auto-hide-mouse-leave-after-play.html
LayoutTests/media/modern-media-controls/playback-support/playback-support-media-api.html
LayoutTests/media/modern-media-controls/start-support/start-support-manual-play.html
LayoutTests/media/remote-control-command-is-user-gesture-expected.txt
LayoutTests/media/remote-control-command-is-user-gesture.html
LayoutTests/media/track/track-cues-pause-on-exit.html
LayoutTests/media/track/track-mode.html
LayoutTests/media/video-autoplay-allowed-but-fullscreen-required.html
LayoutTests/media/video-display-none-crash.html
LayoutTests/media/video-main-content-deny-display-none.html
LayoutTests/media/video-main-content-deny-not-in-dom.html
LayoutTests/media/video-main-content-deny-not-visible.html
LayoutTests/media/video-main-content-deny-obscured.html
LayoutTests/media/video-main-content-deny-too-small.html
LayoutTests/media/video-paint-test.js
LayoutTests/media/video-play-audio-require-user-gesture-expected.txt
LayoutTests/media/video-play-audio-require-user-gesture.html
LayoutTests/media/video-play-pause-events-expected.txt
LayoutTests/media/video-play-pause-events.html
LayoutTests/media/video-play-pause-exception-expected.txt
LayoutTests/media/video-play-pause-exception.html
LayoutTests/media/video-play-require-user-gesture-expected.txt
LayoutTests/media/video-play-require-user-gesture.html
LayoutTests/media/video-played-collapse-expected.txt
LayoutTests/media/video-played-ranges-1-expected.txt
LayoutTests/media/video-played-reset-expected.txt
LayoutTests/media/video-played.js
LayoutTests/media/video-preload-expected.txt
LayoutTests/media/video-preload.html
LayoutTests/media/video-test.js
LayoutTests/platform/mac/inspector/model/remote-object-expected.txt
LayoutTests/resources/testharnessreport.js
LayoutTests/streams/reference-implementation/pipe-to-expected.txt
LayoutTests/streams/reference-implementation/pipe-to-options-expected.txt
LayoutTests/streams/reference-implementation/readable-stream-templated-expected.txt
LayoutTests/streams/reference-implementation/writable-stream-abort-expected.txt
LayoutTests/streams/reference-implementation/writable-stream-expected.txt
LayoutTests/streams/shadowing-Promise.html
LayoutTests/webrtc/libwebrtc/release-while-creating-offer-expected.txt
LayoutTests/webrtc/libwebrtc/release-while-setting-local-description-expected.txt
Source/JavaScriptCore/ChangeLog
Source/JavaScriptCore/builtins/BuiltinNames.h
Source/JavaScriptCore/builtins/PromiseOperations.js
Source/JavaScriptCore/builtins/PromisePrototype.js
Source/JavaScriptCore/bytecode/BytecodeIntrinsicRegistry.cpp
Source/JavaScriptCore/bytecode/BytecodeIntrinsicRegistry.h
Source/JavaScriptCore/inspector/InjectedScriptSource.js
Source/JavaScriptCore/jsc.cpp
Source/JavaScriptCore/runtime/JSGlobalObject.cpp
Source/JavaScriptCore/runtime/JSGlobalObject.h
Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.cpp
Source/JavaScriptCore/runtime/JSGlobalObjectFunctions.h
Source/JavaScriptCore/runtime/JSPromise.cpp
Source/JavaScriptCore/runtime/JSPromise.h
Source/WebCore/CMakeLists.txt
Source/WebCore/ChangeLog
Source/WebCore/DerivedSources.cpp
Source/WebCore/DerivedSources.make
Source/WebCore/Modules/streams/ReadableStream.js
Source/WebCore/Modules/streams/ReadableStreamInternals.js
Source/WebCore/WebCore.xcodeproj/project.pbxproj
Source/WebCore/bindings/js/JSDOMConvert.h
Source/WebCore/bindings/js/JSDOMConvertPromise.h [new file with mode: 0644]
Source/WebCore/bindings/js/JSDOMWindowBase.cpp
Source/WebCore/bindings/js/JSDOMWindowBase.h
Source/WebCore/bindings/js/JSMainThreadExecState.cpp
Source/WebCore/bindings/js/JSMainThreadExecState.h
Source/WebCore/bindings/js/JSWorkerGlobalScopeBase.cpp
Source/WebCore/bindings/scripts/CodeGenerator.pm
Source/WebCore/bindings/scripts/CodeGeneratorJS.pm
Source/WebCore/dom/DOMAllInOne.cpp
Source/WebCore/dom/EventNames.h
Source/WebCore/dom/EventNames.in
Source/WebCore/dom/GlobalEventHandlers.idl
Source/WebCore/dom/PromiseRejectionEvent.cpp [new file with mode: 0644]
Source/WebCore/dom/PromiseRejectionEvent.h [new file with mode: 0644]
Source/WebCore/dom/PromiseRejectionEvent.idl [new file with mode: 0644]
Source/WebCore/dom/RejectedPromiseTracker.cpp [new file with mode: 0644]
Source/WebCore/dom/RejectedPromiseTracker.h [new file with mode: 0644]
Source/WebCore/dom/ScriptExecutionContext.cpp
Source/WebCore/dom/ScriptExecutionContext.h
Source/WebCore/html/HTMLMediaElement.cpp

index a0f43d8..4d5aace 100644 (file)
@@ -1,3 +1,98 @@
+2017-04-27  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Support for promise rejection events (unhandledrejection)
+        https://bugs.webkit.org/show_bug.cgi?id=150358
+        <rdar://problem/28441651>
+
+        Reviewed by Saam Barati.
+
+        Patch by Joseph Pecoraro and Yusuke Suzuki.
+
+        * js/dom/unhandled-promise-rejection-basic-expected.txt: Added.
+        * js/dom/unhandled-promise-rejection-basic.html: Added.
+        * js/dom/unhandled-promise-rejection-bindings-type-error-expected.txt: Added.
+        * js/dom/unhandled-promise-rejection-bindings-type-error.html: Added.
+        * js/dom/unhandled-promise-rejection-console-no-report-expected.txt: Added.
+        * js/dom/unhandled-promise-rejection-console-no-report.html: Added.
+        * js/dom/unhandled-promise-rejection-console-report-expected.txt: Added.
+        * js/dom/unhandled-promise-rejection-console-report.html: Added.
+        * js/dom/unhandled-promise-rejection-handle-during-event-expected.txt: Added.
+        * js/dom/unhandled-promise-rejection-handle-during-event.html: Added.
+        * js/dom/unhandled-promise-rejection-handle-expected.txt: Added.
+        * js/dom/unhandled-promise-rejection-handle-in-handler-expected.txt: Added.
+        * js/dom/unhandled-promise-rejection-handle-in-handler.html: Added.
+        * js/dom/unhandled-promise-rejection-handle.html: Added.
+        * js/dom/unhandled-promise-rejection-order-expected.txt: Added.
+        * js/dom/unhandled-promise-rejection-order.html: Added.
+        New tests specific to the `onunhandledrejection` and `onrejectionhandled` events.
+
+        * resources/testharnessreport.js:
+        (self.testRunner.add_completion_callback.sanitize):
+        (self.testRunner.add_completion_callback):
+        Report results immediately and then finish the test after a turn. This way
+        if the test ends with a pass, but may get unhandled rejections after
+        completing which should not make the test appear as if it failed. Some tests
+        have unhandled promise rejections but are expected to pass. Likewise some
+        tests perform cleanup in their own completion callbacks, which happen after
+        this initial completion callback, and we want to report results after all
+        the work is done as it may eliminate non-deterministic debug test output.
+
+        * TestExpectations:
+        Mark some tests as flakey that have sometimes have unhandled promise rejections.
+        These tests are all various imported tests that use the testharness.
+
+        * fast/mediastream/MediaStream-MediaElement-setObject-null-expected.txt:
+        * http/tests/security/video-cross-origin-caching-expected.txt:
+        * inspector/debugger/break-on-exception-throw-in-promise-expected.txt:
+        * inspector/debugger/break-on-uncaught-exception-throw-in-promise-expected.txt:
+        * inspector/worker/resources-in-worker-expected.txt:
+        * js/dom/dom-static-property-for-in-iteration-expected.txt:
+        * js/dom/global-constructors-attributes-dedicated-worker-expected.txt:
+        * js/promises-tests/promises-tests-2-2-6-expected.txt:
+        * platform/mac/inspector/model/remote-object-expected.txt:
+        * webrtc/libwebrtc/release-while-creating-offer-expected.txt:
+        * webrtc/libwebrtc/release-while-setting-local-description-expected.txt:
+        Update tests with unhandled promise rejection messages.
+
+        * media/W3C/audio/events/event_pause_manual.html:
+        * media/audio-playback-restriction-play-expected.txt:
+        * media/audio-playback-restriction-play.html:
+        * media/click-volume-bar-not-pausing.html:
+        * media/remote-control-command-is-user-gesture-expected.txt:
+        * media/remote-control-command-is-user-gesture.html:
+        * media/track/track-mode.html:
+        * media/video-autoplay-allowed-but-fullscreen-required.html:
+        * media/video-display-none-crash.html:
+        * media/video-main-content-deny-display-none.html:
+        * media/video-main-content-deny-not-in-dom.html:
+        * media/video-main-content-deny-not-visible.html:
+        * media/video-main-content-deny-obscured.html:
+        * media/video-main-content-deny-too-small.html:
+        * media/video-multiple-concurrent-playback-expected.txt:
+        * media/video-play-audio-require-user-gesture-expected.txt:
+        * media/video-play-audio-require-user-gesture.html:
+        * media/video-play-pause-events-expected.txt:
+        * media/video-play-pause-events.html:
+        * media/video-play-pause-exception-expected.txt:
+        * media/video-play-pause-exception.html:
+        * media/video-play-require-user-gesture-expected.txt:
+        * media/video-play-require-user-gesture.html:
+        * media/video-preload-expected.txt:
+        * media/video-preload.html:
+        * media/video-test.js:
+        (handlePromise.handle):
+        Snuff many possible unhandled promise rejections in media via media.play().
+
+        * streams/reference-implementation/pipe-to-expected.txt:
+        * streams/reference-implementation/pipe-to-options-expected.txt:
+        * streams/reference-implementation/readable-stream-templated-expected.txt:
+        * streams/reference-implementation/writable-stream-abort-expected.txt:
+        * streams/reference-implementation/writable-stream-expected.txt:
+        These stream tests are out of date and produce unhandled rejections.
+
+        * streams/shadowing-Promise.html:
+        Update non-imported tests to prevent unhandled rejection messages.
+
 2017-04-27  Chris Dumez  <cdumez@apple.com>
 
         Align colspan/rowspan limits with the latest HTML specification
index ec78c54..0445930 100644 (file)
@@ -330,6 +330,11 @@ webkit.org/b/169565 imported/w3c/web-platform-tests/cors/status-preflight.htm [
 webkit.org/b/169565 imported/w3c/web-platform-tests/cors/status-async.htm [ Failure ]
 webkit.org/b/169565 imported/w3c/web-platform-tests/cors/304.htm [ Failure ]
 
+# Tests that are flakey due to unhandled promise rejection error messages
+webkit.org/b/171094 imported/w3c/web-platform-tests/streams/readable-streams/tee.html [ Pass Failure ]
+webkit.org/b/171094 streams/brand-checks.html [ Pass Failure ]
+webkit.org/b/171094 streams/reference-implementation/abstract-ops.html [ Pass Failure ]
+
 # WPT tests that fail after doing full test repository reimport and need further investigation
 imported/w3c/web-platform-tests/XMLHttpRequest/send-entity-body-document.htm [ Pass Failure Timeout ]
 imported/w3c/web-platform-tests/dom/nodes/Document-createElement-namespace.html [ Pass Failure ]
index b84f24b..3773dcb 100644 (file)
@@ -1,3 +1,4 @@
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: [object DOMError]
 PASS video.srcObject = stream did not throw exception.
 PASS video.duration is Number.POSITIVE_INFINITY
 PASS video.srcObject = null did not throw exception.
index a272198..586087c 100644 (file)
@@ -1,3 +1,4 @@
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: [object DOMError]
 
 This test passes if you do not see a CORS error.
 
index db59aad..df7e623 100644 (file)
@@ -1,3 +1,35 @@
+2017-04-27  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Support for promise rejection events (unhandledrejection)
+        https://bugs.webkit.org/show_bug.cgi?id=150358
+        <rdar://problem/28441651>
+
+        Reviewed by Saam Barati.
+
+        Patch by Joseph Pecoraro and Yusuke Suzuki.
+
+        * web-platform-tests/WebCryptoAPI/derive_bits_keys/test_ecdh_bits-expected.txt:
+        * web-platform-tests/WebCryptoAPI/derive_bits_keys/test_ecdh_keys-expected.txt:
+        * web-platform-tests/html/semantics/embedded-content/media-elements/event_pause_noautoplay-expected.txt:
+        * web-platform-tests/html/semantics/embedded-content/media-elements/event_play_noautoplay-expected.txt:
+        * web-platform-tests/html/semantics/embedded-content/media-elements/paused_true_during_pause-expected.txt:
+        * web-platform-tests/html/syntax/parsing/html5lib_innerHTML_adoption01-expected.txt:
+        * web-platform-tests/html/webappapis/scripting/events/body-exposed-window-event-handlers-expected.txt:
+        * web-platform-tests/streams/piping/close-propagation-forward-expected.txt:
+        * web-platform-tests/streams/piping/error-propagation-backward-expected.txt:
+        * web-platform-tests/streams/piping/error-propagation-forward-expected.txt:
+        * web-platform-tests/streams/piping/flow-control-expected.txt:
+        * web-platform-tests/streams/piping/general-expected.txt:
+        * web-platform-tests/user-timing/measure_exceptions_navigation_timing-expected.txt:
+        Rebaseline expectations to include unhandled promise rejection messages.
+        Also change how the test harness reports success or failure to use the values
+        immediately on completion and then allow a run loop cycle before completing
+        the test to gather the output.
+
+        * web-platform-tests/resource-timing/rt-resource-errors.html:
+        This is our own WPT test yet to be uploaded, so update the test to prevent
+        an unexpected unhandled rejection.
+
 2017-04-27  Chris Dumez  <cdumez@apple.com>
 
         Align colspan/rowspan limits with the latest HTML specification
index 33b1ac4..818d365 100644 (file)
@@ -1,6 +1,7 @@
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: DataError (DOM Exception 30): Data provided to an operation does not meet requirements
 deriveBits Tests for ECDH
 
 
-Harness Error (TIMEOUT), message = null
+Harness Error (FAIL), message = Data provided to an operation does not meet requirements
 
 
index 859c8f2..45132b8 100644 (file)
@@ -1,6 +1,7 @@
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: DataError (DOM Exception 30): Data provided to an operation does not meet requirements
 deriveKey Tests for ECDH
 
 
-Harness Error (TIMEOUT), message = null
+Harness Error (FAIL), message = Data provided to an operation does not meet requirements
 
 
index 39dd4aa..2fce2f3 100644 (file)
@@ -1,7 +1,11 @@
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: [object DOMError]
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: [object DOMError]
 spec reference
 
   
 
+Harness Error (FAIL), message = The operation was aborted.
+
 PASS audio events - pause 
 PASS calling play() then pause() on non-autoplay audio should trigger pause event 
 PASS video events - pause 
index 8a36798..0b99d80 100644 (file)
@@ -1,7 +1,11 @@
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: [object DOMError]
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: [object DOMError]
 spec reference
 
   
 
+Harness Error (FAIL), message = The operation was aborted.
+
 PASS audio events - play 
 PASS calling play() on audio should trigger play event 
 PASS video events - play 
index 26c5c87..9a70008 100644 (file)
@@ -1,7 +1,11 @@
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: [object DOMError]
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: [object DOMError]
 spec reference
 
   
 
+Harness Error (FAIL), message = The operation was aborted.
+
 PASS audio events - paused property 
 PASS audio.paused should be true during pause event 
 PASS video events - paused property 
index d0a66da..976b620 100644 (file)
@@ -2,7 +2,5 @@ CONSOLE MESSAGE: line 257: ReferenceError: Can't find variable: template
 html5lib Parser Test
 
 
-Harness Error (FAIL), message = ReferenceError: Can't find variable: template
-
 FAIL html5lib_innerHTML_adoption01.html 0bf80e1546d4c221354aa9734f61713b7d64ee6d assert_equals: expected "#document\n| <b>\n|   <em>\n|     <foo>\n|       <foob>\n|         <fooc>\n| <aside>\n|   <b>" but got "#document\n| <b>\n|   <em>\n|     <foo>\n|       <foob>\n|         <fooc>\n| <aside>\n|   <em>\n|     <b>"
 
index 966cf14..dec4ee4 100644 (file)
@@ -31,11 +31,11 @@ PASS Return null when getting the pageshow event handler of a windowless body
 PASS Ignore setting of pageshow window event handlers on windowless body 
 PASS Return null when getting the popstate event handler of a windowless body 
 PASS Ignore setting of popstate window event handlers on windowless body 
-FAIL Return null when getting the rejectionhandled event handler of a windowless body assert_equals: expected (object) null but got (undefined) undefined
+PASS Return null when getting the rejectionhandled event handler of a windowless body 
 FAIL Ignore setting of rejectionhandled window event handlers on windowless body assert_equals: expected (object) null but got (function) function "function () { return "Handler attached to windowless element"; }"
 PASS Return null when getting the storage event handler of a windowless body 
 PASS Ignore setting of storage window event handlers on windowless body 
-FAIL Return null when getting the unhandledrejection event handler of a windowless body assert_equals: expected (object) null but got (undefined) undefined
+PASS Return null when getting the unhandledrejection event handler of a windowless body 
 FAIL Ignore setting of unhandledrejection window event handlers on windowless body assert_equals: expected (object) null but got (function) function "function () { return "Handler attached to windowless element"; }"
 PASS Return null when getting the unload event handler of a windowless body 
 PASS Ignore setting of unload window event handlers on windowless body 
@@ -71,11 +71,11 @@ PASS Return null when getting the pageshow event handler of a windowless framese
 PASS Ignore setting of pageshow window event handlers on windowless frameset 
 PASS Return null when getting the popstate event handler of a windowless frameset 
 PASS Ignore setting of popstate window event handlers on windowless frameset 
-FAIL Return null when getting the rejectionhandled event handler of a windowless frameset assert_equals: expected (object) null but got (undefined) undefined
+PASS Return null when getting the rejectionhandled event handler of a windowless frameset 
 FAIL Ignore setting of rejectionhandled window event handlers on windowless frameset assert_equals: expected (object) null but got (function) function "function () { return "Handler attached to windowless element"; }"
 PASS Return null when getting the storage event handler of a windowless frameset 
 PASS Ignore setting of storage window event handlers on windowless frameset 
-FAIL Return null when getting the unhandledrejection event handler of a windowless frameset assert_equals: expected (object) null but got (undefined) undefined
+PASS Return null when getting the unhandledrejection event handler of a windowless frameset 
 FAIL Ignore setting of unhandledrejection window event handlers on windowless frameset assert_equals: expected (object) null but got (function) function "function () { return "Handler attached to windowless element"; }"
 PASS Return null when getting the unload event handler of a windowless frameset 
 PASS Ignore setting of unload window event handlers on windowless frameset 
index 08b7ae1..bd5410d 100644 (file)
@@ -33,7 +33,7 @@ promise_test(function(t) {
     let rejectedDataURL = crossOriginURL("cors-denied", "resource-timing/resources/data.json");
     let allowedDataURL = crossOriginURL("cors-allowed", "resource-timing/resources/data.json") + "&pipe=header(Access-Control-Allow-Origin,*)";
 
-    fetch(rejectedDataURL);
+    fetch(rejectedDataURL).catch(function(){});
     fetch(allowedDataURL);
 
     return observeResources(1).then(([entry]) => {
index a827b7b..a39de3b 100644 (file)
@@ -1,3 +1,21 @@
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+
+Harness Error (FAIL), message = cancel() called on a reader owned by no readable stream
 
 FAIL Closing must be propagated forward: starts closed; preventClose omitted; fulfilled close promise assert_array_equals: close must always be called with the controller lengths differ, expected 1 got 0
 FAIL Closing must be propagated forward: starts closed; preventClose omitted; rejected close promise assert_array_equals: close must always be called with the controller lengths differ, expected 1 got 0
index b8d0a2a..27d5649 100644 (file)
@@ -1,5 +1,7 @@
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error2: error2!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
 
-Harness Error (TIMEOUT), message = null
+Harness Error (FAIL), message = error1!
 
 PASS Errors must be propagated backward: starts errored; preventCancel omitted; fulfilled cancel promise 
 FAIL Errors must be propagated backward: becomes errored before piping due to write; preventCancel omitted; fulfilled cancel promise ws.getWriter is not a function. (In 'ws.getWriter()', 'ws.getWriter' is undefined)
index 62a938f..6bace4e 100644 (file)
@@ -1,3 +1,58 @@
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error2: error2!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error2: error2!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error2: error2!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error2: error2!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error2: error2!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: error1: error1!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+
+Harness Error (FAIL), message = cancel() called on a reader owned by no readable stream
 
 PASS Errors must be propagated forward: starts errored; preventAbort = false; fulfilled abort promise 
 FAIL Errors must be propagated forward: starts errored; preventAbort = false; rejected abort promise assert_throws: pipeTo must reject with the abort error function "function () { throw e }" threw object "error1: error1!" ("error1") expected object "error2: error2!" ("error2")
index 6225ea0..2df32d3 100644 (file)
@@ -1,3 +1,6 @@
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: Error: assert_array_equals: close must always be called with the controller lengths differ, expected 1 got 0
+
+Harness Error (FAIL), message = assert_array_equals: close must always be called with the controller lengths differ, expected 1 got 0
 
 FAIL Piping from a non-empty ReadableStream into a WritableStream that does not desire chunks promise_test: Unhandled rejection with value: object "TypeError: ws.controller.error is not a function. (In 'ws.controller.error(error1)', 'ws.controller.error' is undefined)"
 FAIL Piping from a non-empty ReadableStream into a WritableStream that does not desire chunks, but then does ws.getWriter is not a function. (In 'ws.getWriter()', 'ws.getWriter' is undefined)
index ac3a2fd..fc8ffef 100644 (file)
@@ -1,3 +1,18 @@
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: The WritableStream.ready getter can only be used on instances of WritableStream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 2684: TypeError: undefined is not an object (evaluating 'e.message')
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: undefined
+CONSOLE MESSAGE: line 2684: TypeError: undefined is not an object (evaluating 'e.message')
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: undefined
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 2684: TypeError: undefined is not an object (evaluating 'e.message')
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: undefined
+CONSOLE MESSAGE: line 2684: TypeError: undefined is not an object (evaluating 'e.message')
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: undefined
+CONSOLE MESSAGE: line 2684: TypeError: undefined is not an object (evaluating 'e.message')
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: undefined
+
+Harness Error (FAIL), message = The WritableStream.ready getter can only be used on instances of WritableStream
 
 FAIL Piping must lock both the ReadableStream and WritableStream assert_false: sanity check: the WritableStream must not start locked expected false got undefined
 FAIL Piping finishing must unlock both the ReadableStream and WritableStream assert_false: the WritableStream must become unlocked expected false got undefined
index d2da151..e65343a 100644 (file)
@@ -4,8 +4,6 @@ Description
 window.performance.measure() method throws a InvalidAccessError whenever a navigation timing attribute with a value of zero is provided as the startMark or endMark.
 
 
-Harness Error (FAIL), message = ReferenceError: Can't find variable: onload_test
-
 PASS window.performance.measure("measure", "loadEventEnd"), where "loadEventEnd" is a navigation timing attribute with a value of 0, throws a InvalidAccessError exception. 
 PASS window.performance.measure("measure", "loadEventEnd", "responseEnd"), where "loadEventEnd" is a navigation timing attribute with a value of 0, throws a InvalidAccessError exception. 
 PASS window.performance.measure("measure", "navigationStart", "loadEventEnd"), where "loadEventEnd" is a navigation timing attribute with a value of 0, throws a InvalidAccessError exception. 
index e1614b0..e95d3f9 100644 (file)
@@ -1,10 +1,12 @@
 CONSOLE MESSAGE: line 36: testThrowingInPromise
 CONSOLE MESSAGE: line 38: in promise
 CONSOLE MESSAGE: line 7: throwing TestError
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TestError
 CONSOLE MESSAGE: line 55: testThrowingInPromiseThen
 CONSOLE MESSAGE: line 57: in promise
 CONSOLE MESSAGE: line 60: in promise.then
 CONSOLE MESSAGE: line 7: throwing TestError
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TestError
 CONSOLE MESSAGE: line 66: testThrowingInPromiseThenWithCatch
 CONSOLE MESSAGE: line 68: in promise
 CONSOLE MESSAGE: line 71: in promise.then
@@ -20,6 +22,7 @@ CONSOLE MESSAGE: line 80: testThrowingInPromiseWithRethrowInCatch
 CONSOLE MESSAGE: line 82: in promise
 CONSOLE MESSAGE: line 7: throwing TestError
 CONSOLE MESSAGE: line 85: in promise.catch
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TestError
 Checking pause locations within Promises when pausing on all exceptions.
 
 
index 19bcf6d..e4eb70f 100644 (file)
@@ -1,10 +1,12 @@
 CONSOLE MESSAGE: line 36: testThrowingInPromise
 CONSOLE MESSAGE: line 38: in promise
 CONSOLE MESSAGE: line 7: throwing TestError
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TestError
 CONSOLE MESSAGE: line 55: testThrowingInPromiseThen
 CONSOLE MESSAGE: line 57: in promise
 CONSOLE MESSAGE: line 60: in promise.then
 CONSOLE MESSAGE: line 7: throwing TestError
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TestError
 CONSOLE MESSAGE: line 66: testThrowingInPromiseThenWithCatch
 CONSOLE MESSAGE: line 68: in promise
 CONSOLE MESSAGE: line 71: in promise.then
@@ -20,6 +22,7 @@ CONSOLE MESSAGE: line 80: testThrowingInPromiseWithRethrowInCatch
 CONSOLE MESSAGE: line 82: in promise
 CONSOLE MESSAGE: line 7: throwing TestError
 CONSOLE MESSAGE: line 85: in promise.catch
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TestError
 Checking no pauses within Promises when pausing on uncaught exceptions.
 
 
index 3c2bc41..d143763 100644 (file)
@@ -1,3 +1,4 @@
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: Type error
 Test for Resources in a Worker.
 
 
index 23dc557..610ffb0 100644 (file)
@@ -89,6 +89,7 @@ PASS a["onplay"] is null
 PASS a["onplaying"] is null
 PASS a["onprogress"] is null
 PASS a["onratechange"] is null
+PASS a["onrejectionhandled"] is null
 PASS a["onreset"] is null
 PASS a["onresize"] is null
 PASS a["onscroll"] is null
@@ -100,6 +101,7 @@ PASS a["onsubmit"] is null
 PASS a["onsuspend"] is null
 PASS a["ontimeupdate"] is null
 PASS a["ontoggle"] is null
+PASS a["onunhandledrejection"] is null
 PASS a["onvolumechange"] is null
 PASS a["onwaiting"] is null
 PASS a["tagName"] is A
@@ -110,7 +112,7 @@ PASS a["namespaceURI"] is http://www.w3.org/1999/xhtml
 PASS a["prefix"] is null
 PASS a["localName"] is a
 PASS a["offsetLeft"] is 8
-PASS a["offsetTop"] is 1689
+PASS a["offsetTop"] is 1719
 PASS a["offsetWidth"] is 40
 PASS a["offsetHeight"] is 18
 PASS a["clientLeft"] is 0
index 1f67726..ffc12c6 100644 (file)
@@ -239,6 +239,11 @@ PASS [Worker] Object.getOwnPropertyDescriptor(global, 'Promise').hasOwnProperty(
 PASS [Worker] Object.getOwnPropertyDescriptor(global, 'Promise').hasOwnProperty('set') is false
 PASS [Worker] Object.getOwnPropertyDescriptor(global, 'Promise').enumerable is false
 PASS [Worker] Object.getOwnPropertyDescriptor(global, 'Promise').configurable is true
+PASS [Worker] Object.getOwnPropertyDescriptor(global, 'PromiseRejectionEvent').value is PromiseRejectionEvent
+PASS [Worker] Object.getOwnPropertyDescriptor(global, 'PromiseRejectionEvent').hasOwnProperty('get') is false
+PASS [Worker] Object.getOwnPropertyDescriptor(global, 'PromiseRejectionEvent').hasOwnProperty('set') is false
+PASS [Worker] Object.getOwnPropertyDescriptor(global, 'PromiseRejectionEvent').enumerable is false
+PASS [Worker] Object.getOwnPropertyDescriptor(global, 'PromiseRejectionEvent').configurable is true
 PASS [Worker] Object.getOwnPropertyDescriptor(global, 'RangeError').value is RangeError
 PASS [Worker] Object.getOwnPropertyDescriptor(global, 'RangeError').hasOwnProperty('get') is false
 PASS [Worker] Object.getOwnPropertyDescriptor(global, 'RangeError').hasOwnProperty('set') is false
diff --git a/LayoutTests/js/dom/unhandled-promise-rejection-basic-expected.txt b/LayoutTests/js/dom/unhandled-promise-rejection-basic-expected.txt
new file mode 100644 (file)
index 0000000..a8e08f9
--- /dev/null
@@ -0,0 +1,12 @@
+Test unhandled promise rejection event.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS error.type is "unhandledrejection"
+PASS error.cancelable is true
+PASS error.promise is promise
+PASS error.reason is "ERROR"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/js/dom/unhandled-promise-rejection-basic.html b/LayoutTests/js/dom/unhandled-promise-rejection-basic.html
new file mode 100644 (file)
index 0000000..a49db99
--- /dev/null
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+description('Test unhandled promise rejection event.');
+
+window.jsTestIsAsync = true;
+
+window.error = null;
+window.promise = null;
+window.onunhandledrejection = function (e) {
+    error = e;
+    shouldBe(`error.type`, `"unhandledrejection"`);
+    shouldBe(`error.cancelable`, `true`);
+    shouldBe(`error.promise`, `promise`);
+    shouldBe(`error.reason`, `"ERROR"`);
+    finishJSTest();
+    return false;
+};
+
+window.promise = Promise.reject("ERROR");
+
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/js/dom/unhandled-promise-rejection-bindings-type-error-expected.txt b/LayoutTests/js/dom/unhandled-promise-rejection-bindings-type-error-expected.txt
new file mode 100644 (file)
index 0000000..7f4f447
--- /dev/null
@@ -0,0 +1,11 @@
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: The PromiseRejectionEvent.promise getter can only be used on instances of PromiseRejectionEvent
+Test rejected promises are returned from bindings and trigger unhandledrejection.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS error.promise is promise
+PASS error.reason instanceof TypeError is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/js/dom/unhandled-promise-rejection-bindings-type-error.html b/LayoutTests/js/dom/unhandled-promise-rejection-bindings-type-error.html
new file mode 100644 (file)
index 0000000..7783d41
--- /dev/null
@@ -0,0 +1,33 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+description('Test rejected promises are returned from bindings and trigger unhandledrejection.');
+
+window.jsTestIsAsync = true;
+
+window.error = null;
+window.promise = null;
+window.onunhandledrejection = function (e) {
+    error = e;
+    shouldBe(`error.promise`, `promise`);
+    shouldBeTrue(`error.reason instanceof TypeError`);
+};
+
+try {
+    promise = PromiseRejectionEvent.prototype.promise;
+} catch (e) {
+    testFailed("TypeErrors produced by getters for Promise results should be wrapped in a Promise");
+}
+
+setTimeout(function () { finishJSTest(); }, 100);
+
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/js/dom/unhandled-promise-rejection-console-no-report-expected.txt b/LayoutTests/js/dom/unhandled-promise-rejection-console-no-report-expected.txt
new file mode 100644 (file)
index 0000000..9291b09
--- /dev/null
@@ -0,0 +1,9 @@
+Test unhandled promise rejection event handler returning false does not produce console messages.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS count is 3
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/js/dom/unhandled-promise-rejection-console-no-report.html b/LayoutTests/js/dom/unhandled-promise-rejection-console-no-report.html
new file mode 100644 (file)
index 0000000..350a881
--- /dev/null
@@ -0,0 +1,32 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+description('Test unhandled promise rejection event handler returning false does not produce console messages.');
+
+window.jsTestIsAsync = true;
+
+window.promise = [];
+for (let i = 0; i < 3; ++i)
+    window.promise[i] = Promise.reject(i);
+
+window.count = 0;
+window.onunhandledrejection = function (e) {
+    count++;
+    return false;
+};
+
+setTimeout(function () {
+    shouldBe(`count`, `3`);
+    finishJSTest();
+}, 100);
+
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/js/dom/unhandled-promise-rejection-console-report-expected.txt b/LayoutTests/js/dom/unhandled-promise-rejection-console-report-expected.txt
new file mode 100644 (file)
index 0000000..179a174
--- /dev/null
@@ -0,0 +1,11 @@
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: 0
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: 1
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: 2
+Test unhandled promise rejection event produces console messages. The test passes when you see 3 console messages for unhandled promise rejections.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/js/dom/unhandled-promise-rejection-console-report.html b/LayoutTests/js/dom/unhandled-promise-rejection-console-report.html
new file mode 100644 (file)
index 0000000..bbe9af3
--- /dev/null
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+description('Test unhandled promise rejection event produces console messages. The test passes when you see 3 console messages for unhandled promise rejections.');
+
+window.jsTestIsAsync = true;
+
+window.promise = [];
+for (let i = 0; i < 3; ++i)
+    window.promise[i] = Promise.reject(i);
+
+window.onunhandledrejection = function (e) { return true; };
+setTimeout(function () { finishJSTest(); }, 100);
+
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/js/dom/unhandled-promise-rejection-handle-during-event-expected.txt b/LayoutTests/js/dom/unhandled-promise-rejection-handle-during-event-expected.txt
new file mode 100644 (file)
index 0000000..a509f87
--- /dev/null
@@ -0,0 +1,17 @@
+Test unhandled promise rejection event.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+window.promise[0] = Promise.reject(0);
+window.promise[1] = Promise.reject(1);
+window.promise[2] = Promise.reject(2);
+PASS error.type is "unhandledrejection"
+PASS error.cancelable is true
+PASS error.promise is promise[0]
+PASS error.reason is 0
+window.promise[1].catch(function () { });
+window.promise[2].catch(function () { });
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/js/dom/unhandled-promise-rejection-handle-during-event.html b/LayoutTests/js/dom/unhandled-promise-rejection-handle-during-event.html
new file mode 100644 (file)
index 0000000..cae0f58
--- /dev/null
@@ -0,0 +1,36 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+description('Test unhandled promise rejection event.');
+
+window.jsTestIsAsync = true;
+
+window.error = null;
+window.promise = [];
+window.onunhandledrejection = function (e) {
+    error = e;
+
+    shouldBe(`error.type`, `"unhandledrejection"`);
+    shouldBe(`error.cancelable`, `true`);
+    shouldBe(`error.promise`, `promise[0]`);
+    shouldBe(`error.reason`, `0`);
+
+    evalAndLog(`window.promise[1].catch(function () { });`);
+    evalAndLog(`window.promise[2].catch(function () { });`);
+    setTimeout(function () { finishJSTest(); }, 100);
+    return false;
+};
+
+for (let i = 0; i < 3; ++i)
+    evalAndLog(`window.promise[${i}] = Promise.reject(${i});`);
+
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/js/dom/unhandled-promise-rejection-handle-expected.txt b/LayoutTests/js/dom/unhandled-promise-rejection-handle-expected.txt
new file mode 100644 (file)
index 0000000..109a91b
--- /dev/null
@@ -0,0 +1,17 @@
+Test rejection handled event.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS error.type is "unhandledrejection"
+PASS error.cancelable is true
+PASS error.promise is promise
+PASS error.reason is "ERROR"
+PASS reason is "ERROR"
+PASS handled.type is "rejectionhandled"
+PASS handled.cancelable is false
+PASS handled.promise is promise
+PASS handled.reason is "ERROR"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/js/dom/unhandled-promise-rejection-handle-in-handler-expected.txt b/LayoutTests/js/dom/unhandled-promise-rejection-handle-in-handler-expected.txt
new file mode 100644 (file)
index 0000000..50260da
--- /dev/null
@@ -0,0 +1,13 @@
+Test rejection handled event.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS error.type is "unhandledrejection"
+PASS error.cancelable is true
+PASS error.promise is promise
+PASS error.reason is "ERROR"
+PASS reason is "ERROR"
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/js/dom/unhandled-promise-rejection-handle-in-handler.html b/LayoutTests/js/dom/unhandled-promise-rejection-handle-in-handler.html
new file mode 100644 (file)
index 0000000..cef836f
--- /dev/null
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+description('Test rejection handled event.');
+
+window.jsTestIsAsync = true;
+
+window.error = null;
+window.promise = null;
+window.reason = null;
+
+window.onunhandledrejection = function (e) {
+    error = e;
+    shouldBe(`error.type`, `"unhandledrejection"`);
+    shouldBe(`error.cancelable`, `true`);
+    shouldBe(`error.promise`, `promise`);
+    shouldBe(`error.reason`, `"ERROR"`);
+    promise.catch(function (r) {
+        window.reason = r;
+        shouldBe(`reason`, `"ERROR"`);
+    });
+
+    setTimeout(function () {
+        finishJSTest();
+    }, 100);
+    return false;
+};
+
+window.onrejectionhandled = function (e) {
+    testFailed(`rejectionhandled is fired.`);
+};
+
+window.promise = Promise.reject("ERROR");
+
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/js/dom/unhandled-promise-rejection-handle.html b/LayoutTests/js/dom/unhandled-promise-rejection-handle.html
new file mode 100644 (file)
index 0000000..513c939
--- /dev/null
@@ -0,0 +1,48 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+description('Test rejection handled event.');
+
+window.jsTestIsAsync = true;
+
+window.error = null;
+window.handled = null;
+window.promise = null;
+window.reason = null;
+
+window.onunhandledrejection = function (e) {
+    error = e;
+    shouldBe(`error.type`, `"unhandledrejection"`);
+    shouldBe(`error.cancelable`, `true`);
+    shouldBe(`error.promise`, `promise`);
+    shouldBe(`error.reason`, `"ERROR"`);
+    setTimeout(function () {
+        promise.catch(function (r) {
+            window.reason = r;
+            shouldBe(`reason`, `"ERROR"`);
+        });
+    }, 0);
+    return false;
+};
+
+window.onrejectionhandled = function (e) {
+    window.handled = e;
+    shouldBe(`handled.type`, `"rejectionhandled"`);
+    shouldBe(`handled.cancelable`, `false`);
+    shouldBe(`handled.promise`, `promise`);
+    shouldBe(`handled.reason`, `"ERROR"`);
+    finishJSTest();
+};
+
+window.promise = Promise.reject("ERROR");
+
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/js/dom/unhandled-promise-rejection-order-expected.txt b/LayoutTests/js/dom/unhandled-promise-rejection-order-expected.txt
new file mode 100644 (file)
index 0000000..0c02799
--- /dev/null
@@ -0,0 +1,20 @@
+Test unhandled promise rejection event.
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+PASS error.type is "unhandledrejection"
+PASS error.cancelable is true
+PASS error.promise is promise[0]
+PASS error.reason is 0
+PASS error.type is "unhandledrejection"
+PASS error.cancelable is true
+PASS error.promise is promise[1]
+PASS error.reason is 1
+PASS error.type is "unhandledrejection"
+PASS error.cancelable is true
+PASS error.promise is promise[2]
+PASS error.reason is 2
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/js/dom/unhandled-promise-rejection-order.html b/LayoutTests/js/dom/unhandled-promise-rejection-order.html
new file mode 100644 (file)
index 0000000..9bd21c9
--- /dev/null
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<head>
+<script src="../../resources/js-test-pre.js"></script>
+</head>
+<body>
+<div id="description"></div>
+<div id="console"></div>
+<script>
+description('Test unhandled promise rejection event.');
+
+window.jsTestIsAsync = true;
+
+window.error = null;
+window.promise = [];
+window.count = 0;
+window.index = 0;
+window.onunhandledrejection = function (e) {
+    error = e;
+    index = count++;
+
+    shouldBe(`error.type`, `"unhandledrejection"`);
+    shouldBe(`error.cancelable`, `true`);
+    shouldBe(`error.promise`, `promise[${index}]`);
+    shouldBe(`error.reason`, `${index}`);
+
+    if (count === 3)
+        finishJSTest();
+    return false;
+};
+
+for (let i = 0; i < 3; ++i)
+    window.promise[i] = Promise.reject(i);
+
+</script>
+<script src="../../resources/js-test-post.js"></script>
+</body>
+</html>
index 48721eb..aa73574 100644 (file)
@@ -1,3 +1,9 @@
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: [object Object]
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: [object Object]
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: [object Object]
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: [object Object]
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: [object Object]
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: [object Object]
 Test Promise with Promise A+ tests 2.2.6.
 
 On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
index 0da9a29..19f0d93 100644 (file)
@@ -59,4 +59,7 @@ assert_equals = function(a, b) { shouldBe('"' + a + '"', '"' + b + '"'); }
 assert_true = function(a) { shouldBeTrue("" + a); }
 assert_false = function(a) { shouldBeFalse("" + a); }
 
+// Don't report unhandled promise rejections in these tests.
+window.onunhandledrejection = function() { return false; }
+
 var successfullyParsed = true;
index 6589f81..c010c8b 100644 (file)
@@ -4,7 +4,7 @@ Test that, when RequireUserGestureForAudioRateChange is set, starting playback o
 RUN(internals.setMediaElementRestrictions(video, "RequireUserGestureForAudioRateChange"))
 RUN(video.src = findMediaFile('audio', 'content/test'))
 EVENT(canplaythrough)
-RUN(video.play())
+RUN(handlePromise(video.play()))
 EXPECTED (video.paused == 'true') OK
 Running with keyDown.
 RUN(video.play())
index 9fff3d3..30a0829 100644 (file)
@@ -20,7 +20,7 @@
 
     function canplaythrough()
     {
-        run("video.play()");
+        run("handlePromise(video.play())");
         testExpected('video.paused', true);
         runWithKeyDown(playWithKeyDown);
     }
index 2d02b80..dcb1b03 100644 (file)
@@ -78,7 +78,7 @@
                 waitForEventOnce('playing', playing);
 
                 mediaElement.pause();
-                mediaElement.play();
+                handlePromise(mediaElement.play());
             }
 
             function playing()
index 5928cdf..9540574 100644 (file)
@@ -72,3 +72,8 @@ function stripExtension(filename) {
     return filename.substring(0, lastPeriodIndex);
   return filename;
 }
+
+function handlePromise(promise) {
+    function handle() { }
+    return promise.then(handle, handle);
+}
index b62cfe7..5ddbbd9 100644 (file)
@@ -38,7 +38,7 @@ function mediaPaused()
     debug("");
     debug("Resuming media playback.");
     media.addEventListener("play", mediaResumedPlaying);
-    media.play();
+    media.play().catch(function(){});
 }
 
 function mediaResumedPlaying()
index b32a35b..39da5ee 100644 (file)
@@ -33,7 +33,7 @@ media.addEventListener("pause", function() {
     finishJSTest();
 });
 
-media.play();
+media.play().catch(function(){});
 
 </script>
 <script src="../../../resources/js-test-post.js"></script>
index ff54e20..c692ad2 100644 (file)
@@ -31,7 +31,7 @@ media.addEventListener("play", function() {
     media.pause();
 });
 
-media.play();
+media.play().catch(function(){});
 
 </script>
 <script src="../../../resources/js-test-post.js"></script>
index 16be059..254c01e 100644 (file)
@@ -6,7 +6,7 @@ RUN(internals.setMediaElementRestrictions(video, "RequireUserGestureForVideoRate
 EVENT(loadedmetadata)
 
 * video.play() should fail 
-RUN(video.play())
+RUN(handlePromise(video.play()))
 EXPECTED (video.paused == 'true') OK
 
 * Send a play command, it should succeed.
index d401b34..071b114 100644 (file)
@@ -25,7 +25,7 @@
             function loadedmetadata()
             {
                 consoleWrite('<br>* video.play() should fail ');
-                run('video.play()');
+                run('handlePromise(video.play())');
                 testExpected('video.paused', true);
 
                 consoleWrite('<br>* Send a play command, it should succeed.');
index c75f8eb..54e2450 100644 (file)
@@ -5,6 +5,12 @@
 
         <script src=../media-file.js></script>
         <script src=../video-test.js></script>
+    </head>
+    <body onload="bodyLoaded()">
+        <p>Tests that the video is paused after cues that have pause-on-exit flag are processed</p>
+        <video controls>
+            <track id="testTrack" src="captions-webvtt/simple-captions.vtt" onload="loaded()" default>
+        </video>
         <script>
             var videoCanPlayThrough = false;
             var trackLoaded = false;
 
             setCaptionDisplayMode('Automatic');
         </script>
-    </head>
-    <body onload="bodyLoaded()">
-        <p>Tests that the video is paused after cues that have pause-on-exit flag are processed</p>
-        <video controls>
-            <track id="testTrack" src="captions-webvtt/simple-captions.vtt" onload="loaded()" default>
-        </video>
     </body>
 </html>
index b3f83df..618ece9 100644 (file)
@@ -76,7 +76,7 @@
                 setMode("showing");
 
                 consoleWrite("<b>++ at least " + textTrack.cues.length + " events expected while mode = showing</b>");
-                video.play();
+                handlePromise(video.play());
             }
             
             function cuechange(event)
index 20a554b..7264d6f 100644 (file)
     }
 
     function canPlayThrough() {
-        video.play();
+        var promise = video.play();
         waitForEventAndFail('playing');
         setTimeout(didNotPlay, 100);
+        promise.catch(didNotPlay);
     }
 
     function didNotPlay() {
@@ -28,4 +29,4 @@
 </head>
 <body onload="go()">
 </body>
-</html>
\ No newline at end of file
+</html>
index 4f66c1c..49d6c61 100644 (file)
@@ -4,7 +4,7 @@
 <script src="video-test.js"></script>
 <script>
     video.src = findMediaFile("video", "content/test");
-    video.play();
+    handlePromise(video.play());
     video.style.display = "none";
     video.pause();
     endTest();
index abc278f..e3cad03 100644 (file)
     }
 
     function canPlayThrough() {
-        video.play();
+        var promise = video.play();
         waitForEventAndFail('playing');
         setTimeout(didNotBeginPlaying, 100);
+        promise.catch(didNotBeginPlaying);
     }
 
     function didNotBeginPlaying() {
@@ -35,4 +36,4 @@
 </head>
 <body onload="go()">
 </body>
-</html>
\ No newline at end of file
+</html>
index c143863..07196c5 100644 (file)
     }
 
     function canPlayThrough() {
-        video.play();
+        var promise = video.play();
         waitForEventAndFail('playing');
         setTimeout(didNotBeginPlaying, 100);
+        promise.catch(didNotBeginPlaying);
     }
 
     function didNotBeginPlaying() {
@@ -34,4 +35,4 @@
 </head>
 <body onload="go()">
 </body>
-</html>
\ No newline at end of file
+</html>
index 7fa14bc..f14b5e9 100644 (file)
     }
 
     function canPlayThrough() {
-        video.play();
+        var promise = video.play();
         waitForEventAndFail('playing');
         setTimeout(didNotBeginPlaying, 100);
+        promise.catch(didNotBeginPlaying);
     }
 
     function didNotBeginPlaying() {
@@ -35,4 +36,4 @@
 </head>
 <body onload="go()">
 </body>
-</html>
\ No newline at end of file
+</html>
index 6b311cb..82360a4 100644 (file)
     }
 
     function canPlayThrough() {
-        video.play();
+        var promise = video.play();
         waitForEventAndFail('playing');
         setTimeout(didNotBeginPlaying, 100);
+        promise.catch(didNotBeginPlaying);
     }
 
     function didNotBeginPlaying() {
@@ -43,4 +44,4 @@
 <body onload="go()">
     <div class="block"></div>
 </body>
-</html>
\ No newline at end of file
+</html>
index c06f3f0..555d930 100644 (file)
     }
 
     function canPlayThrough() {
-        video.play();
+        var promise = video.play();
         waitForEventAndFail('playing');
         setTimeout(didNotBeginPlaying, 100);
+        promise.catch(didNotBeginPlaying);
     }
 
     function didNotBeginPlaying() {
@@ -35,4 +36,4 @@
 </head>
 <body onload="go()">
 </body>
-</html>
\ No newline at end of file
+</html>
index aad1f86..3c8fb15 100644 (file)
@@ -13,7 +13,7 @@ function init()
 
     waitForMultipleEvents("canplaythrough", videos.length, function() {
         for (var i = 0; i < videos.length; ++i) {
-            videos[i].play();
+            handlePromise(videos[i].play());
             videos[i].addEventListener("playing", function(event) {
                 event.target.pause();
                 event.target.currentTime = 0;
@@ -42,7 +42,7 @@ function initAndPause()
 
     waitForMultipleEvents("canplaythrough", videos.length, function() {
         for (var i = 0; i < videos.length; ++i) {
-            videos[i].play();
+            handlePromise(videos[i].play());
             videos[i].addEventListener("playing", function(event) {
                 event.target.pause();
             });
@@ -65,3 +65,9 @@ function initAndSeeked()
             testRunner.notifyDone();
     });
 }
+
+function handlePromise(promise)
+{
+    function handle() { }
+    return promise.then(handle, handle);
+}
index 5075013..ccf8ede 100644 (file)
@@ -4,7 +4,7 @@ Test that video play(), pause() and webkitEnterFullScreen() should not work unle
 EVENT(canplaythrough)
 
 * No user gesture initiated
-RUN(video.play())
+RUN(handlePromise(video.play()))
 TEST(video.webkitEnterFullScreen()) THROWS(DOMException.INVALID_STATE_ERR) OK
 EXPECTED (video.paused == 'true') OK
 
index 539d3d1..5958798 100644 (file)
@@ -37,7 +37,7 @@
             {
                 consoleWrite("");
                 consoleWrite("* No user gesture initiated");
-                run("video.play()");
+                run("handlePromise(video.play())");
                 testDOMException("video.webkitEnterFullScreen()", "DOMException.INVALID_STATE_ERR");
                 testExpected("video.paused", true);
                 consoleWrite("");
index 5f41885..112a8c8 100644 (file)
@@ -1,7 +1,7 @@
 
 Test that calling play() and pause() triggers async play, timeupdate and pause events.
 
-RUN(video.play())
+RUN(handlePromise(video.play()))
 RUN(video.pause())
 SCRIPT DONE
 EVENT(play)
index 87ec781..e712544 100644 (file)
@@ -3,7 +3,7 @@
 <script src=media-file.js></script>
 <script src=video-test.js></script>
 <script>
-    video.src = findMediaFile("video", "content/test.mp4");
+    video.src = findMediaFile("video", "content/test");
 
     waitForEvent("loadstart");
     waitForEvent("ratechange");
@@ -19,7 +19,7 @@
     waitForEvent('pause', function () { testExpected("video.paused", true); endTest(); } );
 
 
-    run("video.play()");
+    run("handlePromise(video.play())");
     run("video.pause()");
     
     consoleWrite("SCRIPT DONE");
index 3444872..734a7ed 100644 (file)
@@ -1,7 +1,7 @@
 
 Video has no src. Test that the playing event is not dispatched.
 
-RUN(video.play())
+RUN(handlePromise(video.play()))
 RUN(video.pause())
 EVENT(waiting)
 EVENT(timeupdate)
index 8b129da..afab079 100644 (file)
@@ -26,7 +26,7 @@
 
     waitForEvent("pause", onpause);
 
-    run("video.play()");
+    run("handlePromise(video.play())");
     run("video.pause()");
 </script>
 </body>
index 5075013..ccf8ede 100644 (file)
@@ -4,7 +4,7 @@ Test that video play(), pause() and webkitEnterFullScreen() should not work unle
 EVENT(canplaythrough)
 
 * No user gesture initiated
-RUN(video.play())
+RUN(handlePromise(video.play()))
 TEST(video.webkitEnterFullScreen()) THROWS(DOMException.INVALID_STATE_ERR) OK
 EXPECTED (video.paused == 'true') OK
 
index a68b1e5..3d85b3e 100644 (file)
@@ -37,7 +37,7 @@
             {
                 consoleWrite("");
                 consoleWrite("* No user gesture initiated");
-                run("video.play()");
+                run("handlePromise(video.play())");
                 testDOMException("video.webkitEnterFullScreen()", "DOMException.INVALID_STATE_ERR");
                 testExpected("video.paused", true);
                 consoleWrite("");
index 71c7342..179e8dd 100644 (file)
@@ -8,7 +8,7 @@ Test 1 OK
 
 Test playing when there are no ranges
 EVENT(seeked)
-RUN(video.play())
+RUN(handlePromise(video.play()))
 RUN(video.pause())
 EVENT(pause)
 Test 2 OK
@@ -17,7 +17,7 @@ Test 4 OK
 
 Create a new range
 EVENT(seeked)
-RUN(video.play())
+RUN(handlePromise(video.play()))
 RUN(video.pause())
 EVENT(pause)
 Test 5 OK
@@ -28,7 +28,7 @@ Test 9 OK
 
 Test playing from one range into another, should collapse the two ranges
 EVENT(seeked)
-RUN(video.play())
+RUN(handlePromise(video.play()))
 RUN(video.pause())
 EVENT(pause)
 Test 10 OK
@@ -38,7 +38,7 @@ Test 12 OK
 Test looping
 RUN(video.loop = true)
 EVENT(seeked)
-RUN(video.play())
+RUN(handlePromise(video.play()))
 RUN(video.pause())
 EVENT(pause)
 Test 13 OK
index dcfaa6d..5c7d998 100644 (file)
@@ -7,7 +7,7 @@ EVENT(canplay)
 Test 1 OK
 
 Test playing when there are no ranges
-RUN(video.play())
+RUN(handlePromise(video.play()))
 RUN(video.pause())
 EVENT(pause)
 Test 2 OK
@@ -15,7 +15,7 @@ Test 3 OK
 Test 4 OK
 
 Test jumping forward into a new range and play
-RUN(video.play())
+RUN(handlePromise(video.play()))
 RUN(video.pause())
 EVENT(pause)
 Test 5 OK
@@ -25,7 +25,7 @@ Test 8 OK
 Test 9 OK
 
 Test jumping backwards into a new range and play, should insert new range
-RUN(video.play())
+RUN(handlePromise(video.play()))
 RUN(video.pause())
 EVENT(pause)
 Test 10 OK
@@ -37,7 +37,7 @@ Test 15 OK
 Test 16 OK
 
 Test playing into an existing range, should extend range start
-RUN(video.play())
+RUN(handlePromise(video.play()))
 RUN(video.pause())
 EVENT(pause)
 Test 17 OK
@@ -49,7 +49,7 @@ Test 22 OK
 Test 23 OK
 
 Test jumping into an existing range and play beyond end, should extend range end
-RUN(video.play())
+RUN(handlePromise(video.play()))
 RUN(video.pause())
 EVENT(pause)
 Test 24 OK
index 6fed7c2..8b8760d 100644 (file)
@@ -7,7 +7,7 @@ EVENT(canplay)
 Test 1 OK
 
 Test playing when there are no ranges
-RUN(video.play())
+RUN(handlePromise(video.play()))
 RUN(video.pause())
 EVENT(pause)
 Test 2 OK
@@ -22,7 +22,7 @@ EVENT(canplay)
 Test 5 OK
 
 Test jumping forward into a new range and play
-RUN(video.play())
+RUN(handlePromise(video.play()))
 RUN(video.pause())
 EVENT(pause)
 Test 6 OK
index ff5b002..dadcdc9 100644 (file)
@@ -108,7 +108,7 @@ function playForMillisecs(milliseconds)
         return;
     }
 
-    run("video.play()");
+    run("handlePromise(video.play())");
 
     var startTime = nowInSecs();
     var playedFromTime = video.currentTime;
index 54efd1f..5e29d18 100644 (file)
@@ -7,7 +7,7 @@ RUN(video.setAttribute('preload', 'none'))
 RUN(video.removeAttribute('autoplay'))
 EVENT(loadstart)
 did not buffer automatically OK
-RUN(video.play())
+RUN(handlePromise(video.play()))
 EVENT(play)
 EVENT(loadedmetadata)
 buffered automatically OK
index e264b88..4dc65a3 100644 (file)
@@ -50,7 +50,7 @@
                 
                 // start playback, which should force data to load
                 movie.shouldBuffer = true;
-                run("video.play()");
+                run("handlePromise(video.play())");
             }
 
             function loadedmetadata()
index 60b1e30..f9ae567 100644 (file)
@@ -441,3 +441,8 @@ function shouldReject(promise) {
     });
 
 }
+
+function handlePromise(promise) {
+    function handle() { }
+    return promise.then(handle, handle);
+}
index 1d905b4..4db1bc1 100644 (file)
@@ -1,3 +1,5 @@
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: undefined
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: result
 
 -----------------------------------------------------
 EXPRESSION: null
index 8771d2b..3a18df5 100644 (file)
@@ -44,46 +44,47 @@ if (self.testRunner) {
     */
     setup({"output": false, "explicit_timeout": true});
 
-    /*  Using a callback function, test results will be added to the page in a 
+    /*  Using a callback function, test results will be added to the page in a
     *   manner that allows dumpAsText to produce readable test results
     */
     add_completion_callback(function (tests, harness_status) {
-        // Wait for any other completion callbacks
-        setTimeout(function() {
-            var results = document.createElement("pre");
-            var resultStr = "\n";
+        var results = document.createElement("pre");
+        var resultStr = "\n";
 
-            // Sanitizes the given text for display in test results.
-            function sanitize(text) {
-                if (!text) {
-                    return "";
-                }
-                text = text.replace(/\0/g, "\\0");
-                return text.replace(/\r/g, "\\r");
+        // Sanitizes the given text for display in test results.
+        function sanitize(text) {
+            if (!text) {
+                return "";
             }
+            text = text.replace(/\0/g, "\\0");
+            return text.replace(/\r/g, "\\r");
+        }
 
-            if(harness_status.status != 0)
-                resultStr += "Harness Error (" + convertResult(harness_status.status) + "), message = " + harness_status.message + "\n\n";
+        if(harness_status.status != 0)
+            resultStr += "Harness Error (" + convertResult(harness_status.status) + "), message = " + harness_status.message + "\n\n";
 
-            for (var i = 0; i < tests.length; i++) {
-                var message = sanitize(tests[i].message);
-                if (tests[i].status == 1 && !tests[i].dumpStack) {
-                    // Remove stack for failed tests for proper string comparison without file paths.
-                    // For a test to dump the stack set its dumpStack attribute to true.
-                    var stackIndex = message.indexOf("(stack:");
-                    if (stackIndex > 0)
-                        message = message.substr(0, stackIndex);
-                }
-                resultStr += convertResult(tests[i].status) + " " + sanitize(tests[i].name) + " " + message + "\n";
+        for (var i = 0; i < tests.length; i++) {
+            var message = sanitize(tests[i].message);
+            if (tests[i].status == 1 && !tests[i].dumpStack) {
+                // Remove stack for failed tests for proper string comparison without file paths.
+                // For a test to dump the stack set its dumpStack attribute to true.
+                var stackIndex = message.indexOf("(stack:");
+                if (stackIndex > 0)
+                    message = message.substr(0, stackIndex);
             }
+            resultStr += convertResult(tests[i].status) + " " + sanitize(tests[i].name) + " " + message + "\n";
+        }
 
-            results.innerText = resultStr;
-            var log = document.getElementById("log");
-            if (log)
-                log.appendChild(results);
-            else
-                document.body.appendChild(results);
+        results.innerText = resultStr;
+        var log = document.getElementById("log");
+        if (log)
+            log.appendChild(results);
+        else
+            document.body.appendChild(results);
 
+        // Wait for any other completion callbacks, which may eliminate test elements
+        // from the page and therefore reduce the output.
+        setTimeout(function () {
             testRunner.notifyDone();
         }, 0);
     });
index 8822582..5689aa4 100644 (file)
@@ -1,3 +1,27 @@
+CONSOLE MESSAGE: line 2687: TypeError: undefined is not an object (evaluating 'e.message')
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: undefined
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: Error: horrible things
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: Error: horrible things
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 2687: TypeError: undefined is not an object (evaluating 'e.message')
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: undefined
+CONSOLE MESSAGE: line 2687: TypeError: undefined is not an object (evaluating 'e.message')
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: undefined
+CONSOLE MESSAGE: line 2687: TypeError: undefined is not an object (evaluating 'e.message')
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: undefined
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: Error: horrible things
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: Error: horrible things
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: Error: horrible things
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: Error: I don't like you.
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: Error: I don't like you.
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: Error: I don't like you.
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: Error: I don't like you.
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: Error: I don't like you.
+
+Harness Error (FAIL), message = I don't like you.
 
 PASS Piping from a ReadableStream from which lots of data are readable synchronously 
 PASS Piping from a ReadableStream in readable state to a WritableStream in closing state 
index 563db74..7907e7a 100644 (file)
@@ -1,3 +1,9 @@
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: Error: destination error
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: Error: destination error
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: Error: destination error
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: Error: destination error
+
+Harness Error (FAIL), message = destination error
 
 PASS Piping with no options and a destination error 
 PASS Piping with { preventCancel: false } and a destination error 
index 8764da7..1ac5257 100644 (file)
@@ -1,3 +1,21 @@
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: Error: boo!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: Error: boo!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: Error: boo!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: Error: boo!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: Error: boo!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: Error: !!!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: Error: !!!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: Error: !!!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: Error: !!!
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: cancel() called on a reader owned by no readable stream
+
+Harness Error (FAIL), message = cancel() called on a reader owned by no readable stream
 
 PASS Running templatedRSClosed with ReadableStream (closed via call in start) 
 PASS piping to a WritableStream in the writable state should close the writable stream 
index c816bac..c4ad423 100644 (file)
@@ -1,3 +1,32 @@
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: a
+CONSOLE MESSAGE: line 2687: TypeError: undefined is not an object (evaluating 'e.message')
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: undefined
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: Error: Sorry, it just wasn't meant to be.
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: Error: Sorry, it just wasn't meant to be.
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: Error: Sorry, it just wasn't meant to be.
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: Error: Sorry, it just wasn't meant to be.
+CONSOLE MESSAGE: line 2687: TypeError: undefined is not an object (evaluating 'e.message')
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: undefined
+CONSOLE MESSAGE: line 2687: TypeError: undefined is not an object (evaluating 'e.message')
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: undefined
+CONSOLE MESSAGE: line 2687: TypeError: undefined is not an object (evaluating 'e.message')
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: undefined
+CONSOLE MESSAGE: line 2687: TypeError: undefined is not an object (evaluating 'e.message')
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: undefined
+CONSOLE MESSAGE: line 2687: TypeError: undefined is not an object (evaluating 'e.message')
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: undefined
+CONSOLE MESSAGE: line 2687: TypeError: undefined is not an object (evaluating 'e.message')
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: undefined
+CONSOLE MESSAGE: line 2687: TypeError: undefined is not an object (evaluating 'e.message')
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: undefined
+CONSOLE MESSAGE: line 2687: TypeError: undefined is not an object (evaluating 'e.message')
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: undefined
+CONSOLE MESSAGE: line 2687: TypeError: undefined is not an object (evaluating 'e.message')
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: undefined
+CONSOLE MESSAGE: line 2687: TypeError: undefined is not an object (evaluating 'e.message')
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: undefined
+
+Harness Error (FAIL), message = TypeError: undefined is not an object (evaluating 'e.message')
 
 PASS Aborting a WritableStream immediately prevents future writes 
 PASS Aborting a WritableStream prevents further writes after any that are in progress 
index 69ba76f..d16863a 100644 (file)
@@ -1,3 +1,7 @@
+CONSOLE MESSAGE: line 2687: TypeError: undefined is not an object (evaluating 'e.message')
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: undefined
+
+Harness Error (FAIL), message = TypeError: undefined is not an object (evaluating 'e.message')
 
 PASS error argument is given to start method 
 PASS Underlying sink's write won't be called until start finishes 
index 8a00fdf..55ea10c 100644 (file)
@@ -31,11 +31,13 @@ test(function() {
 test(function() {
     const PromiseRejectBackup = Promise.reject;
 
+    function handle() { }
+
     try {
         Promise.reject = function() { assert_unreached("streams should not use this Promise.reject method"); };
 
-        ReadableStream.prototype.cancel.call({}, "reason");
-        WritableStream.prototype.abort.call({}, "reason");
+        ReadableStream.prototype.cancel.call({}, "reason").then(handle, handle);
+        WritableStream.prototype.abort.call({}, "reason").then(handle, handle);
     } finally {
         Promise.reject = PromiseRejectBackup;
     }
index e8eee2d..fa90afc 100644 (file)
@@ -1,3 +1,4 @@
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: Argument 1 ('candidate') to RTCPeerConnection.addIceCandidate must be an instance of RTCIceCandidate
 PASS successfullyParsed is true
 
 TEST COMPLETE
index 263ba3d..bb22eb9 100644 (file)
@@ -1,3 +1,5 @@
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: TypeError: Argument 1 ('candidate') to RTCPeerConnection.addIceCandidate must be an instance of RTCIceCandidate
+CONSOLE MESSAGE: line 1: Unhandled Promise Rejection: InvalidStateError (DOM Exception 11): The object is in an invalid state.
 PASS successfullyParsed is true
 
 TEST COMPLETE
index a6f7371..f1b8343 100644 (file)
@@ -1,3 +1,58 @@
+2017-04-27  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Support for promise rejection events (unhandledrejection)
+        https://bugs.webkit.org/show_bug.cgi?id=150358
+        <rdar://problem/28441651>
+
+        Reviewed by Saam Barati.
+
+        Patch by Joseph Pecoraro and Yusuke Suzuki.
+
+        Implement support for promise.[[PromiseIsHandled]] and the
+        HostPromiseRejectionTracker hook for HTML to track promise rejections:
+        https://tc39.github.io/ecma262/#sec-host-promise-rejection-tracker
+        https://html.spec.whatwg.org/multipage/webappapis.html#unhandled-promise-rejections
+
+        * builtins/BuiltinNames.h:
+        New private symbols.
+
+        * builtins/PromiseOperations.js:
+        (globalPrivate.newHandledRejectedPromise):
+        Utility to create a rejected promise with [[PromiseIsHandled]] to true.
+
+        (globalPrivate.rejectPromise):
+        (globalPrivate.initializePromise):
+        * builtins/PromisePrototype.js:
+        (then):
+        Implement standard behavior of [[PromiseIsHandled]] and the host hook.
+
+        * runtime/JSPromise.cpp:
+        (JSC::JSPromise::isHandled):
+        * runtime/JSPromise.h:
+        C++ accessors for the [[PromiseIsHandled]] state.
+
+        * bytecode/BytecodeIntrinsicRegistry.cpp:
+        (JSC::BytecodeIntrinsicRegistry::BytecodeIntrinsicRegistry):
+        * bytecode/BytecodeIntrinsicRegistry.h:
+        Expose private values for the Reject / Handle enum values in built-ins.
+
+        * jsc.cpp:
+        * runtime/JSGlobalObject.h:
+        (JSC::JSGlobalObject::promiseResolveFunction):
+        Add a new GlobalObjectMethodTable hook matching the promise rejection hook.
+
+        * runtime/JSGlobalObject.cpp:
+        (JSC::JSGlobalObject::init):
+        (JSC::JSGlobalObject::visitChildren):
+        * runtime/JSGlobalObjectFunctions.cpp:
+        (JSC::globalFuncHostPromiseRejectionTracker):
+        * runtime/JSGlobalObjectFunctions.h:
+        Plumb the builtin hook through to the optional GlobalObjectMethodTable hook.
+
+        * inspector/InjectedScriptSource.js:
+        (InjectedScript.prototype.createFakeValueDescriptor):
+        Silence possible rejected promises created internally via Web Inspector.
+
 2017-04-27  Saam Barati  <sbarati@apple.com>
 
         B3::FoldPathConstants does not consider the fall through case for Switch
index a2c1417..e1763e4 100644 (file)
@@ -82,6 +82,8 @@ namespace JSC {
     macro(homeObject) \
     macro(templateRegistryKey) \
     macro(enqueueJob) \
+    macro(hostPromiseRejectionTracker) \
+    macro(promiseIsHandled) \
     macro(promiseState) \
     macro(promiseReactions) \
     macro(promiseResult) \
index 61564e7..152b64c 100644 (file)
@@ -85,6 +85,14 @@ function newPromiseCapability(constructor)
 }
 
 @globalPrivate
+function newHandledRejectedPromise(error)
+{
+    let promise = @Promise.@reject(error);
+    promise.@promiseIsHandled = true;
+    return promise;
+}
+
+@globalPrivate
 function triggerPromiseReactions(state, reactions, argument)
 {
     "use strict";
@@ -105,6 +113,9 @@ function rejectPromise(promise, reason)
 
     @InspectorInstrumentation.promiseRejected(promise, reason, reactions);
 
+    if (!promise.@promiseIsHandled)
+        @hostPromiseRejectionTracker(promise, @promiseRejectionReject);
+
     @triggerPromiseReactions(@promiseStateRejected, reactions, reason);
 }
 
@@ -212,6 +223,7 @@ function initializePromise(executor)
 
     this.@promiseState = @promiseStatePending;
     this.@promiseReactions = [];
+    this.@promiseIsHandled = false;
 
     var resolvingFunctions = @createResolvingFunctions(this);
     try {
index 6065ad8..5a14ad9 100644 (file)
@@ -52,8 +52,13 @@ function then(onFulfilled, onRejected)
     var state = this.@promiseState;
     if (state === @promiseStatePending)
         @putByValDirect(this.@promiseReactions, this.@promiseReactions.length, reaction);
-    else
+    else {
+        if (state === @promiseStateRejected && !this.@promiseIsHandled)
+            @hostPromiseRejectionTracker(this, @promiseRejectionHandle);
         @enqueueJob(@promiseReactionJob, [state, reaction, this.@promiseResult]);
+    }
+
+    this.@promiseIsHandled = true;
 
     return resultCapability.@promise;
 }
index 00c9c01..0240c18 100644 (file)
@@ -32,6 +32,7 @@
 #include "BytecodeGenerator.h"
 #include "JSCJSValueInlines.h"
 #include "JSGeneratorFunction.h"
+#include "JSGlobalObject.h"
 #include "JSModuleLoader.h"
 #include "JSPromise.h"
 #include "Nodes.h"
@@ -61,6 +62,8 @@ BytecodeIntrinsicRegistry::BytecodeIntrinsicRegistry(VM& vm)
     m_ModuleSatisfy.set(m_vm, jsNumber(static_cast<unsigned>(JSModuleLoader::Status::Satisfy)));
     m_ModuleLink.set(m_vm, jsNumber(static_cast<unsigned>(JSModuleLoader::Status::Link)));
     m_ModuleReady.set(m_vm, jsNumber(static_cast<unsigned>(JSModuleLoader::Status::Ready)));
+    m_promiseRejectionReject.set(m_vm, jsNumber(static_cast<unsigned>(JSPromiseRejectionOperation::Reject)));
+    m_promiseRejectionHandle.set(m_vm, jsNumber(static_cast<unsigned>(JSPromiseRejectionOperation::Handle)));
     m_promiseStatePending.set(m_vm, jsNumber(static_cast<unsigned>(JSPromise::Status::Pending)));
     m_promiseStateFulfilled.set(m_vm, jsNumber(static_cast<unsigned>(JSPromise::Status::Fulfilled)));
     m_promiseStateRejected.set(m_vm, jsNumber(static_cast<unsigned>(JSPromise::Status::Rejected)));
index 0259bc6..76313a7 100644 (file)
@@ -73,6 +73,8 @@ class Identifier;
     macro(ModuleSatisfy) \
     macro(ModuleLink) \
     macro(ModuleReady) \
+    macro(promiseRejectionReject) \
+    macro(promiseRejectionHandle) \
     macro(promiseStatePending) \
     macro(promiseStateFulfilled) \
     macro(promiseStateRejected) \
index 1ef43eb..aaea8a6 100644 (file)
@@ -589,6 +589,9 @@ InjectedScript.prototype = {
                     descriptor.isOwn = true;
                 if (symbol)
                     descriptor.symbol = symbol;
+                // Silence any possible unhandledrejection exceptions created from accessing a native accessor with a wrong this object.
+                if (descriptor.value instanceof Promise)
+                    descriptor.value.catch(function(){});
                 return descriptor;
             } catch (e) {
                 var errorDescriptor = {name, value: e, wasThrown: true};
index 9706bc6..e256450 100644 (file)
@@ -1431,14 +1431,15 @@ const GlobalObjectMethodTable GlobalObject::s_globalObjectMethodTable = {
     &supportsRichSourceInfo,
     &shouldInterruptScript,
     &javaScriptRuntimeFlags,
-    nullptr,
+    nullptr, // queueTaskToEventLoop
     &shouldInterruptScriptBeforeTimeout,
     &moduleLoaderImportModule,
     &moduleLoaderResolve,
     &moduleLoaderFetch,
-    nullptr,
-    nullptr,
-    nullptr
+    nullptr, // moduleLoaderInstantiate
+    nullptr, // moduleLoaderEvaluate
+    nullptr, // promiseRejectionTracker
+    nullptr, // defaultLanguage
 };
 
 GlobalObject::GlobalObject(VM& vm, Structure* structure)
index dd6c8ff..a4ab87c 100644 (file)
@@ -248,14 +248,15 @@ const GlobalObjectMethodTable JSGlobalObject::s_globalObjectMethodTable = {
     &supportsRichSourceInfo,
     &shouldInterruptScript,
     &javaScriptRuntimeFlags,
-    nullptr,
+    nullptr, // queueTaskToEventLoop
     &shouldInterruptScriptBeforeTimeout,
-    nullptr,
-    nullptr,
-    nullptr,
-    nullptr,
-    nullptr,
-    nullptr
+    nullptr, // moduleLoaderImportModule
+    nullptr, // moduleLoaderResolve
+    nullptr, // moduleLoaderFetch
+    nullptr, // moduleLoaderInstantiate
+    nullptr, // moduleLoaderEvaluate
+    nullptr, // promiseRejectionTracker
+    nullptr, // defaultLanguage
 };
 
 /* Source for JSGlobalObject.lut.h
@@ -813,6 +814,7 @@ putDirectWithoutTransition(vm, vm.propertyNames-> jsName, lowerName ## Construct
         GlobalPropertyInfo(vm.propertyNames->builtinNames().MapIteratorPrivateName(), JSFunction::create(vm, this, 1, String(), privateFuncMapIterator), DontEnum | DontDelete | ReadOnly),
         GlobalPropertyInfo(vm.propertyNames->builtinNames().mapIteratorNextPrivateName(), JSFunction::create(vm, this, 0, String(), privateFuncMapIteratorNext), DontEnum | DontDelete | ReadOnly),
 
+        GlobalPropertyInfo(vm.propertyNames->builtinNames().hostPromiseRejectionTrackerPrivateName(), JSFunction::create(vm, this, 2, String(), globalFuncHostPromiseRejectionTracker), DontEnum | DontDelete | ReadOnly),
         GlobalPropertyInfo(vm.propertyNames->builtinNames().InspectorInstrumentationPrivateName(), InspectorInstrumentationObject::create(vm, this, InspectorInstrumentationObject::createStructure(vm, this, m_objectPrototype.get())), DontEnum | DontDelete | ReadOnly),
         GlobalPropertyInfo(vm.propertyNames->builtinNames().MapPrivateName(), mapConstructor, DontEnum | DontDelete | ReadOnly),
         GlobalPropertyInfo(vm.propertyNames->builtinNames().thisTimeValuePrivateName(), privateFuncThisTimeValue, DontEnum | DontDelete | ReadOnly),
index a835e3f..bbebfd0 100644 (file)
@@ -161,11 +161,10 @@ FOR_EACH_WEBASSEMBLY_CONSTRUCTOR_TYPE(DECLARE_SIMPLE_BUILTIN_TYPE)
 
 #undef DECLARE_SIMPLE_BUILTIN_TYPE
 
-class JSInternalPromise;
-class InternalPromisePrototype;
-class InternalPromiseConstructor;
-
-typedef Vector<ExecState*, 16> ExecStateStack;
+enum class JSPromiseRejectionOperation : unsigned {
+    Reject, // When a promise is rejected without any handlers.
+    Handle, // When a handler is added to a rejected promise for the first time.
+};
 
 struct GlobalObjectMethodTable {
     typedef bool (*SupportsRichSourceInfoFunctionPtr)(const JSGlobalObject*);
@@ -198,6 +197,9 @@ struct GlobalObjectMethodTable {
     typedef JSValue (*ModuleLoaderEvaluatePtr)(JSGlobalObject*, ExecState*, JSModuleLoader*, JSValue, JSValue, JSValue);
     ModuleLoaderEvaluatePtr moduleLoaderEvaluate;
 
+    typedef void (*PromiseRejectionTrackerPtr)(JSGlobalObject*, ExecState*, JSPromise*, JSPromiseRejectionOperation);
+    PromiseRejectionTrackerPtr promiseRejectionTracker;
+
     typedef String (*DefaultLanguageFunctionPtr)();
     DefaultLanguageFunctionPtr defaultLanguage;
 };
index c56a76c..6b021b4 100644 (file)
 #include "Exception.h"
 #include "IndirectEvalExecutable.h"
 #include "Interpreter.h"
+#include "JSCInlines.h"
 #include "JSFunction.h"
 #include "JSGlobalObject.h"
 #include "JSInternalPromise.h"
 #include "JSModuleLoader.h"
+#include "JSPromise.h"
 #include "JSPromiseDeferred.h"
 #include "JSString.h"
 #include "JSStringBuilder.h"
@@ -44,7 +46,6 @@
 #include "ParseInt.h"
 #include "Parser.h"
 #include "StackVisitor.h"
-#include <wtf/dtoa.h>
 #include <stdio.h>
 #include <stdlib.h>
 #include <wtf/ASCIICType.h>
@@ -52,6 +53,7 @@
 #include <wtf/HexNumber.h>
 #include <wtf/MathExtras.h>
 #include <wtf/StringExtras.h>
+#include <wtf/dtoa.h>
 #include <wtf/text/StringBuilder.h>
 #include <wtf/unicode/UTF8.h>
 
@@ -295,7 +297,7 @@ static double jsOctalIntegerLiteral(const CharType*& data, const CharType* end)
     }
     if (number >= mantissaOverflowLowerBound)
         number = parseIntOverflow(firstDigitPosition, data - firstDigitPosition, 8);
-    
+
     return number;
 }
 
@@ -372,11 +374,11 @@ static double toDouble(const CharType* characters, unsigned size)
         if (!isStrWhiteSpace(*characters))
             break;
     }
-    
+
     // Empty string.
     if (characters == endCharacters)
         return 0.0;
-    
+
     double number;
     if (characters[0] == '0' && characters + 2 < endCharacters) {
         if ((characters[1] | 0x20) == 'x' && isASCIIHexDigit(characters[2]))
@@ -389,7 +391,7 @@ static double toDouble(const CharType* characters, unsigned size)
             number = jsStrDecimalLiteral(characters, endCharacters);
     } else
         number = jsStrDecimalLiteral(characters, endCharacters);
-    
+
     // Allow trailing white space.
     for (; characters < endCharacters; ++characters) {
         if (!isStrWhiteSpace(*characters))
@@ -397,7 +399,7 @@ static double toDouble(const CharType* characters, unsigned size)
     }
     if (characters != endCharacters)
         return PNaN;
-    
+
     return number;
 }
 
@@ -489,7 +491,7 @@ EncodedJSValue JSC_HOST_CALL globalFuncEval(ExecState* exec)
     } else {
         LiteralParser<UChar> preparser(exec, s.characters16(), s.length(), NonStrictJSON);
         if (JSValue parsedObject = preparser.tryLiteralParse())
-            return JSValue::encode(parsedObject);        
+            return JSValue::encode(parsedObject);
     }
 
     SourceOrigin sourceOrigin = exec->callerSourceOrigin();
@@ -682,7 +684,7 @@ EncodedJSValue JSC_HOST_CALL globalFuncThrowTypeError(ExecState* exec)
     auto scope = DECLARE_THROW_SCOPE(vm);
     return throwVMTypeError(exec, scope);
 }
-    
+
 EncodedJSValue JSC_HOST_CALL globalFuncThrowTypeErrorArgumentsCalleeAndCaller(ExecState* exec)
 {
     VM& vm = exec->vm();
@@ -736,6 +738,29 @@ EncodedJSValue JSC_HOST_CALL globalFuncProtoSetter(ExecState* exec)
     return JSValue::encode(jsUndefined());
 }
 
+EncodedJSValue JSC_HOST_CALL globalFuncHostPromiseRejectionTracker(ExecState* exec)
+{
+    JSGlobalObject* globalObject = exec->lexicalGlobalObject();
+    VM& vm = globalObject->vm();
+    auto scope = DECLARE_THROW_SCOPE(vm);
+
+    if (!globalObject->globalObjectMethodTable()->promiseRejectionTracker)
+        return JSValue::encode(jsUndefined());
+
+    JSPromise* promise = jsCast<JSPromise*>(exec->argument(0));
+    JSValue operationValue = exec->argument(1);
+
+    ASSERT(operationValue.isNumber());
+    auto operation = static_cast<JSPromiseRejectionOperation>(operationValue.toUInt32(exec));
+    ASSERT(operation == JSPromiseRejectionOperation::Reject || operation == JSPromiseRejectionOperation::Handle);
+    ASSERT(!scope.exception());
+
+    globalObject->globalObjectMethodTable()->promiseRejectionTracker(globalObject, exec, promise, operation);
+    RETURN_IF_EXCEPTION(scope, { });
+
+    return JSValue::encode(jsUndefined());
+}
+
 EncodedJSValue JSC_HOST_CALL globalFuncBuiltinLog(ExecState* exec)
 {
     dataLog(exec->argument(0).toWTFString(exec), "\n");
index d69b4d7..ede2553 100644 (file)
@@ -49,6 +49,7 @@ EncodedJSValue JSC_HOST_CALL globalFuncThrowTypeError(ExecState*);
 EncodedJSValue JSC_HOST_CALL globalFuncThrowTypeErrorArgumentsCalleeAndCaller(ExecState*);
 EncodedJSValue JSC_HOST_CALL globalFuncProtoGetter(ExecState*);
 EncodedJSValue JSC_HOST_CALL globalFuncProtoSetter(ExecState*);
+EncodedJSValue JSC_HOST_CALL globalFuncHostPromiseRejectionTracker(ExecState*);
 EncodedJSValue JSC_HOST_CALL globalFuncBuiltinLog(ExecState*);
 EncodedJSValue JSC_HOST_CALL globalFuncImportModule(ExecState*);
 
index 958d45b..a83d2bf 100644 (file)
@@ -85,4 +85,11 @@ JSValue JSPromise::result(VM& vm) const
     return getDirect(vm, vm.propertyNames->builtinNames().promiseResultPrivateName());
 }
 
+bool JSPromise::isHandled(VM& vm) const
+{
+    JSValue value = getDirect(vm, vm.propertyNames->builtinNames().promiseIsHandledPrivateName());
+    ASSERT(value.isBoolean());
+    return value.asBoolean();
+}
+
 } // namespace JSC
index 639f650..c7d7d99 100644 (file)
@@ -45,7 +45,8 @@ public:
     };
 
     Status status(VM&) const;
-    JSValue result(VM&) const;
+    JS_EXPORT_PRIVATE JSValue result(VM&) const;
+    JS_EXPORT_PRIVATE bool isHandled(VM&) const;
 
     // Initialize the promise with the executor.
     // This may raise a JS exception.
index 44b4155..0796af5 100644 (file)
@@ -466,6 +466,7 @@ set(WebCore_NON_SVG_IDL_FILES
     dom/PopStateEvent.idl
     dom/ProcessingInstruction.idl
     dom/ProgressEvent.idl
+    dom/PromiseRejectionEvent.idl
     dom/Range.idl
     dom/RequestAnimationFrameCallback.idl
     dom/SecurityPolicyViolationEvent.idl
@@ -1536,10 +1537,12 @@ set(WebCore_SOURCES
     dom/PositionIterator.cpp
     dom/ProcessingInstruction.cpp
     dom/ProgressEvent.cpp
+    dom/PromiseRejectionEvent.cpp
     dom/PseudoElement.cpp
     dom/QualifiedName.cpp
     dom/RadioButtonGroups.cpp
     dom/Range.cpp
+    dom/RejectedPromiseTracker.cpp
     dom/ScopedEventQueue.cpp
     dom/ScriptElement.cpp
     dom/ScriptElementCachedScriptFetcher.cpp
index 3a381e1..b0898fe 100644 (file)
@@ -1,3 +1,126 @@
+2017-04-27  Joseph Pecoraro  <pecoraro@apple.com>
+
+        Support for promise rejection events (unhandledrejection)
+        https://bugs.webkit.org/show_bug.cgi?id=150358
+        <rdar://problem/28441651>
+
+        Reviewed by Saam Barati.
+
+        Patch by Joseph Pecoraro and Yusuke Suzuki.
+
+        Implement support for the `onunhandledrejection` and `rejectionhandled` events.
+        They dispatch a new PromiseRejectionEvent using the ES6 HostPromiseRejectionTracker hook:
+        https://tc39.github.io/ecma262/#sec-host-promise-rejection-tracker
+        https://html.spec.whatwg.org/multipage/webappapis.html#unhandled-promise-rejections
+
+        This is currently implemented only for Documents and not yet Web Workers.
+
+        Tests: js/dom/unhandled-promise-rejection-basic.html
+               js/dom/unhandled-promise-rejection-bindings-type-error.html
+               js/dom/unhandled-promise-rejection-console-no-report.html
+               js/dom/unhandled-promise-rejection-console-report.html
+               js/dom/unhandled-promise-rejection-handle-during-event.html
+               js/dom/unhandled-promise-rejection-handle-in-handler.html
+               js/dom/unhandled-promise-rejection-handle.html
+               js/dom/unhandled-promise-rejection-order.html
+
+        * CMakeLists.txt:
+        * DerivedSources.cpp:
+        * DerivedSources.make:
+        * WebCore.xcodeproj/project.pbxproj:
+        * dom/DOMAllInOne.cpp:
+        New files.
+
+        * bindings/scripts/CodeGenerator.pm:
+        (IsPromiseType):
+        * bindings/scripts/CodeGeneratorJS.pm:
+        (AddToIncludesForIDLType):
+        (GetBaseIDLType):
+        Binding support for Promise<T> attributes.
+
+        * bindings/js/JSDOMConvert.h:
+        * bindings/js/JSDOMConvertPromise.h: Copied from Source/JavaScriptCore/runtime/JSPromise.h.
+        (WebCore::Converter<IDLPromise<T>>::convert):
+        (WebCore::JSConverter<IDLPromise<T>>::convert):
+        Promise<T> binding conversion is currently unimplemented, which only means
+        web developers creating their own PromiseRejectionEvent will not get
+        autowrapping of values assigned to `promise` in event initialization.
+        Engine generated events will have expected behavior.
+
+        * bindings/js/JSDOMWindowBase.cpp:
+        (WebCore::JSDOMWindowBase::promiseRejectionTracker):
+        * bindings/js/JSDOMWindowBase.h:
+        * bindings/js/JSWorkerGlobalScopeBase.cpp:
+        Implement HostPromiseRejectionTracker hook for Document but not Worker.
+        Passes through to the ScriptExecutionContext's tracker.
+
+        * bindings/js/JSMainThreadExecState.cpp:
+        (WebCore::JSMainThreadExecState::didLeaveScriptContext):
+        * bindings/js/JSMainThreadExecState.h:
+        (WebCore::JSMainThreadExecState::~JSMainThreadExecState):
+        When completing script execution and performing microtasks notify
+        about rejected promises. Technically this should go inside of
+        performing a microtask checkpoint, except lacking EventLoop
+        concepts we use ScriptExecutionState.
+
+        * dom/EventNames.h:
+        * dom/EventNames.in:
+        * dom/PromiseRejectionEvent.cpp: Added.
+        (WebCore::PromiseRejectionEvent::PromiseRejectionEvent):
+        (WebCore::PromiseRejectionEvent::~PromiseRejectionEvent):
+        * dom/PromiseRejectionEvent.h: Added.
+        * dom/PromiseRejectionEvent.idl: Added.
+        New PromiseRejectionEvent event interface.
+
+        * dom/GlobalEventHandlers.idl:
+        New onunhandledrejection and onrejectionhandled.
+
+        * dom/RejectedPromiseTracker.cpp: Added.
+        (WebCore::RejectedPromise::RejectedPromise):
+        (WebCore::RejectedPromise::globalObject):
+        (WebCore::RejectedPromise::promise):
+        (WebCore::UnhandledPromise::UnhandledPromise):
+        (WebCore::UnhandledPromise::callStack):
+        (WebCore::RejectedPromiseTracker::RejectedPromiseTracker):
+        (WebCore::RejectedPromiseTracker::~RejectedPromiseTracker):
+        (WebCore::createScriptCallStackFromReason):
+        (WebCore::RejectedPromiseTracker::promiseRejected):
+        (WebCore::RejectedPromiseTracker::promiseHandled):
+        (WebCore::RejectedPromiseTracker::processQueueSoon):
+        (WebCore::RejectedPromiseTracker::reportUnhandledRejections):
+        (WebCore::RejectedPromiseTracker::reportRejectionHandled):
+        * dom/RejectedPromiseTracker.h: Added.
+        Track and report rejected promises. The promises are tracked weakly
+        allowing them to be collected before they are reported. When reporting
+        we dispatch PromiseRejectionEvent events, and if the default is not
+        prevented we log a message to the console.
+
+        * dom/ScriptExecutionContext.cpp:
+        (WebCore::ScriptExecutionContext::reportUnhandledPromiseRejection):
+        (WebCore::ScriptExecutionContext::ensureRejectedPromiseTrackerSlow):
+        * dom/ScriptExecutionContext.h:
+        (WebCore::ScriptExecutionContext::ensureRejectedPromiseTracker):
+        Each ScriptExecutionContext can own a rejected promise tracker.
+
+        * html/HTMLMediaElement.cpp:
+        (WebCore::HTMLMediaElement::pauseInternal):
+        https://html.spec.whatwg.org/multipage/embedded-content.html#internal-pause-steps
+        Internal pause steps say to timeupdate, pause, and rejecting pending play promises
+        should all happen in a queued task. Here the first two actions are already scheduled
+        on tasks, but rejecting play promises was not being done in a task, so this makes
+        that change.
+
+        * Modules/streams/ReadableStream.js:
+        (pipeThrough):
+        * Modules/streams/ReadableStreamInternals.js:
+        (readableStreamReaderGenericInitialize):
+        (readableStreamError):
+        (readableStreamReaderGenericRelease):
+        Satisfy parts of the Streams specification which state to set the
+        [[PromiseIsHandled]] internal state of promises created internally
+        by the Streams APIs. This prevents some internal promises from
+        appearing as unhandled promise rejections.
+
 2017-04-27  Chris Dumez  <cdumez@apple.com>
 
         Align colspan/rowspan limits with the latest HTML specification
index c39e622..bd8b782 100644 (file)
 #include "JSPositionOptions.cpp"
 #include "JSProcessingInstruction.cpp"
 #include "JSProgressEvent.cpp"
+#include "JSPromiseRejectionEvent.cpp"
 #include "JSRadioNodeList.cpp"
 #include "JSRange.cpp"
 #include "JSReadableByteStreamController.cpp"
index 9071350..7c57470 100644 (file)
@@ -403,7 +403,7 @@ JS_BINDING_IDLS = \
     $(WebCore)/dom/PopStateEvent.idl \
     $(WebCore)/dom/ProcessingInstruction.idl \
     $(WebCore)/dom/ProgressEvent.idl \
-    $(WebCore)/dom/ProgressEvent.idl \
+    $(WebCore)/dom/PromiseRejectionEvent.idl \
     $(WebCore)/dom/Range.idl \
     $(WebCore)/dom/RequestAnimationFrameCallback.idl \
     $(WebCore)/dom/SecurityPolicyViolationEvent.idl \
index 43c24ef..8553e35 100644 (file)
@@ -110,7 +110,9 @@ function pipeThrough(streams, options)
 
     const writable = streams.writable;
     const readable = streams.readable;
-    this.pipeTo(writable, options);
+    const promise = this.pipeTo(writable, options);
+    if (@isPromise(promise))
+        promise.@promiseIsHandled = true;
     return readable;
 }
 
index 503f365..b2be13a 100644 (file)
@@ -54,7 +54,7 @@ function readableStreamReaderGenericInitialize(reader, stream)
         reader.@closedPromiseCapability = { @promise: @Promise.@resolve() };
     else {
         @assert(stream.@state === @streamErrored);
-        reader.@closedPromiseCapability = { @promise: @Promise.@reject(stream.@storedError) };
+        reader.@closedPromiseCapability = { @promise: @newHandledRejectedPromise(stream.@storedError) };
     }
 }
 
@@ -279,6 +279,7 @@ function readableStreamError(stream, error)
         @throwTypeError("Only ReadableStreamDefaultReader is currently supported");
 
     reader.@closedPromiseCapability.@reject.@call(@undefined, error);
+    reader.@closedPromiseCapability.@promise.@promiseIsHandled = true;
 }
 
 function readableStreamDefaultControllerCallPullIfNeeded(controller)
@@ -493,10 +494,11 @@ function readableStreamReaderGenericRelease(reader)
     @assert(!!reader.@ownerReadableStream);
     @assert(reader.@ownerReadableStream.@reader === reader);
 
-    if (reader.@ownerReadableStream.@state === @streamReadable)
+    if (reader.@ownerReadableStream.@state === @streamReadable) {
         reader.@closedPromiseCapability.@reject.@call(@undefined, new @TypeError("releasing lock of reader whose stream is still in readable state"));
-    else
-        reader.@closedPromiseCapability = { @promise: @Promise.@reject(new @TypeError("reader released lock")) };
+        reader.@closedPromiseCapability.@promise.@promiseIsHandled = true;
+    } else
+        reader.@closedPromiseCapability = { @promise: @newHandledRejectedPromise(new @TypeError("reader released lock")) };
 
     reader.@ownerReadableStream.@reader = @undefined;
     reader.@ownerReadableStream = null;
index f4fb8cc..6e3fedd 100644 (file)
                A56C5B9B189F34570082D13C /* WebConsoleAgent.h in Headers */ = {isa = PBXBuildFile; fileRef = A56C5B99189F34570082D13C /* WebConsoleAgent.h */; };
                A5732B0A136A161D005C8D7C /* DateComponents.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A5732B08136A161D005C8D7C /* DateComponents.cpp */; };
                A5732B0B136A161D005C8D7C /* DateComponents.h in Headers */ = {isa = PBXBuildFile; fileRef = A5732B09136A161D005C8D7C /* DateComponents.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               A578F4351DE00EEB003DFC6A /* PromiseRejectionEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = A578F4331DE00ED6003DFC6A /* PromiseRejectionEvent.h */; };
+               A578F4361DE00EED003DFC6A /* PromiseRejectionEvent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A578F4321DE00ED6003DFC6A /* PromiseRejectionEvent.cpp */; };
+               A578F43B1DE01421003DFC6A /* JSPromiseRejectionEvent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A578F4391DE013FB003DFC6A /* JSPromiseRejectionEvent.cpp */; };
+               A578F43C1DE01423003DFC6A /* JSPromiseRejectionEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = A578F43A1DE013FB003DFC6A /* JSPromiseRejectionEvent.h */; };
+               A578F43F1DE0B630003DFC6A /* RejectedPromiseTracker.h in Headers */ = {isa = PBXBuildFile; fileRef = A578F43E1DE0B62B003DFC6A /* RejectedPromiseTracker.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               A578F4401DE0B634003DFC6A /* RejectedPromiseTracker.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A578F43D1DE0B62B003DFC6A /* RejectedPromiseTracker.cpp */; };
                A57FD71B1DB9423E006AE24B /* WorkerRuntimeAgent.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A57FD7191DB94236006AE24B /* WorkerRuntimeAgent.cpp */; };
                A57FD71C1DB9423E006AE24B /* WorkerRuntimeAgent.h in Headers */ = {isa = PBXBuildFile; fileRef = A57FD71A1DB94236006AE24B /* WorkerRuntimeAgent.h */; };
                A5840E1C187B74D500843B10 /* InspectorInstrumentationCookie.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A5840E1A187B74D500843B10 /* InspectorInstrumentationCookie.cpp */; };
                E3150EA61DA7219000194012 /* JSNodeDOMJIT.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E3AFA9641DA6E908002861BD /* JSNodeDOMJIT.cpp */; };
                E3150EA71DA7219300194012 /* DOMJITHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = E3150EA51DA7218D00194012 /* DOMJITHelpers.h */; };
                E318039D1DC40099009932C2 /* JSDynamicDowncast.h in Headers */ = {isa = PBXBuildFile; fileRef = E3A5872E1DC3F52600F607A6 /* JSDynamicDowncast.h */; settings = {ATTRIBUTES = (Private, ); }; };
+               E323CFFA1E5AF6AF00F0B4A0 /* JSDOMConvertPromise.h in Headers */ = {isa = PBXBuildFile; fileRef = E323CFF91E5AF6A500F0B4A0 /* JSDOMConvertPromise.h */; settings = {ATTRIBUTES = (Private, ); }; };
                E3565B7B1DC2D6C900217DBD /* JSEventCustom.h in Headers */ = {isa = PBXBuildFile; fileRef = E34EE49F1DC2D57500EAA9D3 /* JSEventCustom.h */; settings = {ATTRIBUTES = (Private, ); }; };
                E35802B61DC8435D00A9773C /* DOMJITIDLTypeFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = E35802B51DC8435800A9773C /* DOMJITIDLTypeFilter.h */; settings = {ATTRIBUTES = (Private, ); }; };
                E377FE4D1DADE16500CDD025 /* NodeConstants.h in Headers */ = {isa = PBXBuildFile; fileRef = E3D049931DADC04500718F3C /* NodeConstants.h */; settings = {ATTRIBUTES = (Private, ); }; };
                A56C5B99189F34570082D13C /* WebConsoleAgent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WebConsoleAgent.h; sourceTree = "<group>"; };
                A5732B08136A161D005C8D7C /* DateComponents.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DateComponents.cpp; sourceTree = "<group>"; };
                A5732B09136A161D005C8D7C /* DateComponents.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DateComponents.h; sourceTree = "<group>"; };
+               A578F4321DE00ED6003DFC6A /* PromiseRejectionEvent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PromiseRejectionEvent.cpp; sourceTree = "<group>"; };
+               A578F4331DE00ED6003DFC6A /* PromiseRejectionEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PromiseRejectionEvent.h; sourceTree = "<group>"; };
+               A578F4341DE00ED6003DFC6A /* PromiseRejectionEvent.idl */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = PromiseRejectionEvent.idl; sourceTree = "<group>"; };
+               A578F4391DE013FB003DFC6A /* JSPromiseRejectionEvent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JSPromiseRejectionEvent.cpp; sourceTree = "<group>"; };
+               A578F43A1DE013FB003DFC6A /* JSPromiseRejectionEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSPromiseRejectionEvent.h; sourceTree = "<group>"; };
+               A578F43D1DE0B62B003DFC6A /* RejectedPromiseTracker.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RejectedPromiseTracker.cpp; sourceTree = "<group>"; };
+               A578F43E1DE0B62B003DFC6A /* RejectedPromiseTracker.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RejectedPromiseTracker.h; sourceTree = "<group>"; };
                A57FD7191DB94236006AE24B /* WorkerRuntimeAgent.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = WorkerRuntimeAgent.cpp; sourceTree = "<group>"; };
                A57FD71A1DB94236006AE24B /* WorkerRuntimeAgent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = WorkerRuntimeAgent.h; sourceTree = "<group>"; };
                A5840E1A187B74D500843B10 /* InspectorInstrumentationCookie.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = InspectorInstrumentationCookie.cpp; sourceTree = "<group>"; };
                E307DED21D81E4ED00141CAF /* LoadableModuleScript.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LoadableModuleScript.cpp; sourceTree = "<group>"; };
                E307DED31D81E4ED00141CAF /* LoadableModuleScript.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = LoadableModuleScript.h; sourceTree = "<group>"; };
                E3150EA51DA7218D00194012 /* DOMJITHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DOMJITHelpers.h; sourceTree = "<group>"; };
+               E323CFF91E5AF6A500F0B4A0 /* JSDOMConvertPromise.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSDOMConvertPromise.h; sourceTree = "<group>"; };
                E334825E1DC93AA0009C9544 /* DOMJITAbstractHeapRepository.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DOMJITAbstractHeapRepository.h; sourceTree = "<group>"; };
                E34EE49F1DC2D57500EAA9D3 /* JSEventCustom.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JSEventCustom.h; sourceTree = "<group>"; };
                E35802B51DC8435800A9773C /* DOMJITIDLTypeFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DOMJITIDLTypeFilter.h; sourceTree = "<group>"; };
                                7C8E34A11E4A338E0054CE23 /* JSDOMConvertNumbers.cpp */,
                                7C8E34A21E4A338E0054CE23 /* JSDOMConvertNumbers.h */,
                                7C8E34A31E4A338E0054CE23 /* JSDOMConvertObject.h */,
+                               E323CFF91E5AF6A500F0B4A0 /* JSDOMConvertPromise.h */,
                                7C8E34A41E4A338E0054CE23 /* JSDOMConvertRecord.h */,
                                7C8E34A51E4A338E0054CE23 /* JSDOMConvertSequences.h */,
                                7C8E34A61E4A338E0054CE23 /* JSDOMConvertSerializedScriptValue.h */,
                                E1284BAF10449FFA00EAEB52 /* JSPageTransitionEvent.h */,
                                5189F01B10B37BD900F3C739 /* JSPopStateEvent.cpp */,
                                5189F01C10B37BD900F3C739 /* JSPopStateEvent.h */,
+                               A578F4391DE013FB003DFC6A /* JSPromiseRejectionEvent.cpp */,
+                               A578F43A1DE013FB003DFC6A /* JSPromiseRejectionEvent.h */,
                                CED06ACE1C77754800FDFAF1 /* JSSecurityPolicyViolationEvent.cpp */,
                                CED06ACF1C77754800FDFAF1 /* JSSecurityPolicyViolationEvent.h */,
                                A86629CE09DA2B47009633A7 /* JSStaticRange.cpp */,
                                E4B41E010CBF8EAA00AF2ECE /* ProgressEvent.cpp */,
                                E4B41E020CBF8EAA00AF2ECE /* ProgressEvent.h */,
                                E4B41E030CBF8EAA00AF2ECE /* ProgressEvent.idl */,
+                               A578F4321DE00ED6003DFC6A /* PromiseRejectionEvent.cpp */,
+                               A578F4331DE00ED6003DFC6A /* PromiseRejectionEvent.h */,
+                               A578F4341DE00ED6003DFC6A /* PromiseRejectionEvent.idl */,
                                FF945EC9161F7F3600971BC8 /* PseudoElement.cpp */,
                                FF945ECA161F7F3600971BC8 /* PseudoElement.h */,
                                550A0BC7085F6039007353D6 /* QualifiedName.cpp */,
                                93D9D53B0DA27E180077216C /* RangeBoundaryPoint.h */,
                                A84D827B11D333ED00972990 /* RawDataDocumentParser.h */,
                                85031B360A44EFC700F992E0 /* RegisteredEventListener.h */,
+                               A578F43D1DE0B62B003DFC6A /* RejectedPromiseTracker.cpp */,
+                               A578F43E1DE0B62B003DFC6A /* RejectedPromiseTracker.h */,
                                A76E5F7E135E0DCF00A69837 /* RenderedDocumentMarker.h */,
                                4998AEC413F9D0EA0090B1AA /* RequestAnimationFrameCallback.h */,
                                4998AEC513F9D0EA0090B1AA /* RequestAnimationFrameCallback.idl */,
                                BC772B3C0C4EA91E0083285F /* CSSHelper.h in Headers */,
                                BC23EE920DAED2BC009FDC91 /* CSSImageGeneratorValue.h in Headers */,
                                9393E600151A99F200066F06 /* CSSImageSetValue.h in Headers */,
+                               A578F4351DE00EEB003DFC6A /* PromiseRejectionEvent.h in Headers */,
                                A80E6CFA0A1989CA007FB8C5 /* CSSImageValue.h in Headers */,
                                A80E6CF60A1989CA007FB8C5 /* CSSImportRule.h in Headers */,
                                A80E6D010A1989CA007FB8C5 /* CSSInheritedValue.h in Headers */,
                                86D982F7125C154000AD9E3D /* DocumentTiming.h in Headers */,
                                A8185F3909765766005826D9 /* DocumentType.h in Headers */,
                                973889A1116EA9DC00ADF313 /* DocumentWriter.h in Headers */,
+                               A578F43C1DE01423003DFC6A /* JSPromiseRejectionEvent.h in Headers */,
                                1A8F6BC30DB55CDC001DB794 /* DOMApplicationCache.h in Headers */,
                                BC60D6E90D28D83400B9918F /* DOMCoreException.h in Headers */,
                                FC9A0F75164094CF003D6B8D /* DOMCSSNamespace.h in Headers */,
                                499B3EC5128CCC4700E726C2 /* PlatformCALayer.h in Headers */,
                                493E5E0912D6420500020081 /* PlatformCALayerClient.h in Headers */,
                                2D70BA1318074DDF0001908A /* PlatformCALayerCocoa.h in Headers */,
+                               A578F43F1DE0B630003DFC6A /* RejectedPromiseTracker.h in Headers */,
                                A14978711ABAF3A500CEF7E4 /* PlatformContentFilter.h in Headers */,
                                E1424C8A164B3B4E00F32D40 /* PlatformCookieJar.h in Headers */,
                                BC5C762B1497FE1400BC4775 /* PlatformEvent.h in Headers */,
                                83C1D427178D5AB400141E68 /* SVGPathSegCurvetoCubicAbs.h in Headers */,
                                83C1D428178D5AB400141E68 /* SVGPathSegCurvetoCubicRel.h in Headers */,
                                B2227A690D00BF220071B782 /* SVGPathSegCurvetoCubicSmooth.h in Headers */,
+                               E323CFFA1E5AF6AF00F0B4A0 /* JSDOMConvertPromise.h in Headers */,
                                83C1D429178D5AB400141E68 /* SVGPathSegCurvetoCubicSmoothAbs.h in Headers */,
                                83C1D42A178D5AB400141E68 /* SVGPathSegCurvetoCubicSmoothRel.h in Headers */,
                                B2227A6D0D00BF220071B782 /* SVGPathSegCurvetoQuadratic.h in Headers */,
                                BC946346107A934B00857193 /* JSBeforeLoadEvent.cpp in Sources */,
                                70F546E8B8B5D7DC54EE144E /* JSBeforeUnloadEvent.cpp in Sources */,
                                FDF09DC81399B62200688E5B /* JSBiquadFilterNode.cpp in Sources */,
+                A578F43B1DE01421003DFC6A /* JSPromiseRejectionEvent.cpp in Sources */,
                                2E2D99CD10E2BBDA00496337 /* JSBlob.cpp in Sources */,
                                A15D751A1E68F89E00A35FBC /* JSBlobCallback.cpp in Sources */,
                                8931DE5B14C44C44000DC9D2 /* JSBlobCustom.cpp in Sources */,
                                2D9BF7471DBFDC49007A7D99 /* MediaKeyMessageEvent.cpp in Sources */,
                                2D9BF7461DBFDC47007A7D99 /* MediaKeys.cpp in Sources */,
                                2D9BF7451DBFDC44007A7D99 /* MediaKeySession.cpp in Sources */,
+                               A578F4401DE0B634003DFC6A /* RejectedPromiseTracker.cpp in Sources */,
                                2D9BF7441DBFDC41007A7D99 /* MediaKeyStatusMap.cpp in Sources */,
                                2D9BF7431DBFDC3E007A7D99 /* MediaKeySystemAccess.cpp in Sources */,
                                A8EA80090A19516E00A8EF5F /* MediaList.cpp in Sources */,
                                93309E23099E64920056E581 /* WrapContentsInDummySpanCommand.cpp in Sources */,
                                A833C7CC0A2CF07400D57664 /* XLinkNames.cpp in Sources */,
                                00B9318713BA8DB30035A948 /* XMLDocumentParser.cpp in Sources */,
+                               A578F4361DE00EED003DFC6A /* PromiseRejectionEvent.cpp in Sources */,
                                00B9318913BA8DBC0035A948 /* XMLDocumentParserLibxml2.cpp in Sources */,
                                00B9318B13BA8DC90035A948 /* XMLDocumentParserScope.cpp in Sources */,
                                59C28045138DC2410079B7E2 /* XMLErrors.cpp in Sources */,
index 44afd4d..ab1b515 100644 (file)
@@ -40,6 +40,7 @@
 #include "JSDOMConvertNullable.h"
 #include "JSDOMConvertNumbers.h"
 #include "JSDOMConvertObject.h"
+#include "JSDOMConvertPromise.h"
 #include "JSDOMConvertRecord.h"
 #include "JSDOMConvertSequences.h"
 #include "JSDOMConvertSerializedScriptValue.h"
diff --git a/Source/WebCore/bindings/js/JSDOMConvertPromise.h b/Source/WebCore/bindings/js/JSDOMConvertPromise.h
new file mode 100644 (file)
index 0000000..c22370e
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Copyright (C) 2017 Yusuke Suzuki <utatane.tea@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
+ * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ * THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include "IDLTypes.h"
+#include "JSDOMConvertBase.h"
+
+namespace WebCore {
+
+// FIXME: Implement IDLPromise<> conversions.
+// https://bugs.webkit.org/show_bug.cgi?id=166752
+template<typename T>
+struct Converter<IDLPromise<T>> : DefaultConverter<IDLPromise<T>> {
+    using ReturnType = JSC::JSValue;
+
+    static JSC::JSValue convert(JSC::ExecState&, JSC::JSValue value)
+    {
+        return value;
+    }
+};
+
+template<typename T>
+struct JSConverter<IDLPromise<T>> {
+    using Type = JSC::JSValue;
+    static constexpr bool needsState = false;
+    static constexpr bool needsGlobalObject = false;
+
+    static JSC::JSValue convert(JSC::JSValue value)
+    {
+        return value;
+    }
+};
+
+} // namespace WebCore
index 1f7f86a..390fcb8 100644 (file)
@@ -38,6 +38,7 @@
 #include "Language.h"
 #include "Logging.h"
 #include "Page.h"
+#include "RejectedPromiseTracker.h"
 #include "RuntimeApplicationChecks.h"
 #include "ScriptController.h"
 #include "ScriptModuleLoader.h"
@@ -46,6 +47,7 @@
 #include "WebCoreJSClientData.h"
 #include <bytecode/CodeBlock.h>
 #include <heap/StrongInlines.h>
+#include <runtime/JSInternalPromise.h>
 #include <runtime/JSInternalPromiseDeferred.h>
 #include <runtime/Microtask.h>
 #include <wtf/MainThread.h>
@@ -69,8 +71,9 @@ const GlobalObjectMethodTable JSDOMWindowBase::s_globalObjectMethodTable = {
     &moduleLoaderImportModule,
     &moduleLoaderResolve,
     &moduleLoaderFetch,
-    nullptr,
+    nullptr, // moduleLoaderInstantiate
     &moduleLoaderEvaluate,
+    &promiseRejectionTracker,
     &defaultLanguage
 };
 
@@ -355,7 +358,6 @@ void JSDOMWindowBase::fireFrameClearedWatchpointsForWindow(DOMWindow* window)
     }
 }
 
-
 JSC::JSInternalPromise* JSDOMWindowBase::moduleLoaderResolve(JSC::JSGlobalObject* globalObject, JSC::ExecState* exec, JSC::JSModuleLoader* moduleLoader, JSC::JSValue moduleName, JSC::JSValue importerModuleKey, JSC::JSValue scriptFetcher)
 {
     JSDOMWindowBase* thisObject = JSC::jsCast<JSDOMWindowBase*>(globalObject);
@@ -391,4 +393,31 @@ JSC::JSInternalPromise* JSDOMWindowBase::moduleLoaderImportModule(JSC::JSGlobalO
     return deferred->reject(exec, jsUndefined());
 }
 
+void JSDOMWindowBase::promiseRejectionTracker(JSGlobalObject* jsGlobalObject, ExecState* exec, JSPromise* promise, JSPromiseRejectionOperation operation)
+{
+    // https://html.spec.whatwg.org/multipage/webappapis.html#the-hostpromiserejectiontracker-implementation
+
+    VM& vm = exec->vm();
+    auto& globalObject = *JSC::jsCast<JSDOMWindowBase*>(jsGlobalObject);
+    auto* context = globalObject.scriptExecutionContext();
+    if (!context)
+        return;
+
+    // InternalPromises should not be exposed to user scripts.
+    if (JSC::jsDynamicCast<JSC::JSInternalPromise*>(vm, promise))
+        return;
+
+    // FIXME: If script has muted errors (cross origin), terminate these steps.
+    // <https://webkit.org/b/171415> Implement the `muted-errors` property of Scripts to avoid onerror/onunhandledrejection for cross-origin scripts
+
+    switch (operation) {
+    case JSPromiseRejectionOperation::Reject:
+        context->ensureRejectedPromiseTracker().promiseRejected(*exec, globalObject, *promise);
+        break;
+    case JSPromiseRejectionOperation::Handle:
+        context->ensureRejectedPromiseTracker().promiseHandled(*exec, globalObject, *promise);
+        break;
+    }
+}
+
 } // namespace WebCore
index f16abe1..0256227 100644 (file)
@@ -80,6 +80,7 @@ namespace WebCore {
         static JSC::JSInternalPromise* moduleLoaderFetch(JSC::JSGlobalObject*, JSC::ExecState*, JSC::JSModuleLoader*, JSC::JSValue, JSC::JSValue);
         static JSC::JSValue moduleLoaderEvaluate(JSC::JSGlobalObject*, JSC::ExecState*, JSC::JSModuleLoader*, JSC::JSValue, JSC::JSValue, JSC::JSValue);
         static JSC::JSInternalPromise* moduleLoaderImportModule(JSC::JSGlobalObject*, JSC::ExecState*, JSC::JSModuleLoader*, JSC::JSString*, const JSC::SourceOrigin&);
+        static void promiseRejectionTracker(JSC::JSGlobalObject*, JSC::ExecState*, JSC::JSPromise*, JSC::JSPromiseRejectionOperation);
 
         RefPtr<DOMWindow> m_wrapped;
         JSDOMWindowShell* m_shell;
index f49f4a8..3f4b06d 100644 (file)
 #include "JSMainThreadExecState.h"
 
 #include "Microtasks.h"
-#include "MutationObserver.h"
+#include "RejectedPromiseTracker.h"
+#include "ScriptExecutionContext.h"
+#include "ScriptState.h"
 
 namespace WebCore {
 
 JSC::ExecState* JSMainThreadExecState::s_mainThreadState = 0;
 
-void JSMainThreadExecState::didLeaveScriptContext()
+void JSMainThreadExecState::didLeaveScriptContext(JSC::ExecState* exec)
 {
     MicrotaskQueue::mainThreadQueue().performMicrotaskCheckpoint();
+    scriptExecutionContextFromExecState(exec)->ensureRejectedPromiseTracker().processQueueSoon();
 }
 
 JSC::JSValue functionCallHandlerFromAnyThread(JSC::ExecState* exec, JSC::JSValue functionObject, JSC::CallType callType, const JSC::CallData& callData, JSC::JSValue thisValue, const JSC::ArgList& args, NakedPtr<JSC::Exception>& returnedException)
index d244efe..617e65a 100644 (file)
 #include <runtime/Microtask.h>
 #include <wtf/MainThread.h>
 
-#if PLATFORM(IOS)
-#include "WebCoreThread.h"
-#endif
-
 namespace WebCore {
 
 class InspectorInstrumentationCookie;
@@ -139,12 +135,13 @@ private:
         ASSERT(isMainThread());
         ASSERT_UNUSED(scope, !scope.exception());
 
+        JSC::ExecState* state = s_mainThreadState;
         bool didExitJavaScript = s_mainThreadState && !m_previousState;
 
         s_mainThreadState = m_previousState;
 
         if (didExitJavaScript)
-            didLeaveScriptContext();
+            didLeaveScriptContext(state);
     }
 
     template<typename Type, Type jsType, typename DataType> static InspectorInstrumentationCookie instrumentFunctionInternal(ScriptExecutionContext*, Type, const DataType&);
@@ -153,7 +150,7 @@ private:
     JSC::ExecState* m_previousState;
     JSC::JSLockHolder m_lock;
 
-    static void didLeaveScriptContext();
+    static void didLeaveScriptContext(JSC::ExecState*);
 };
 
 // Null state prevents origin security checks.
index f1e6d8e..eec4452 100644 (file)
@@ -52,11 +52,12 @@ const GlobalObjectMethodTable JSWorkerGlobalScopeBase::s_globalObjectMethodTable
     &javaScriptRuntimeFlags,
     &queueTaskToEventLoop,
     &shouldInterruptScriptBeforeTimeout,
-    nullptr,
-    nullptr,
-    nullptr,
-    nullptr,
-    nullptr,
+    nullptr, // moduleLoaderImportModule
+    nullptr, // moduleLoaderResolve
+    nullptr, // moduleLoaderFetch
+    nullptr, // moduleLoaderInstantiate
+    nullptr, // moduleLoaderEvaluate
+    nullptr, // promiseRejectionTracker
     &defaultLanguage
 };
 
index 2130a09..c9ec596 100644 (file)
@@ -688,6 +688,15 @@ sub IsRecordType
     return $type->name eq "record";
 }
 
+sub IsPromiseType
+{
+    my ($object, $type) = @_;
+
+    assert("Not a type") if ref($type) ne "IDLType";
+
+    return $type->name eq "Promise";
+}
+
 # These match WK_lcfirst and WK_ucfirst defined in builtins_generator.py.
 # Uppercase the first letter while respecting WebKit style guidelines.
 # E.g., xmlEncoding becomes XMLEncoding, but xmlllang becomes Xmllang.
index b13c4c7..33bdce7 100644 (file)
@@ -303,6 +303,12 @@ sub AddToIncludesForIDLType
         return;
     }
 
+    if ($codeGenerator->IsPromiseType($type)) {
+        AddToIncludes("JSDOMPromise.h", $includesRef, $conditional);
+        AddToIncludesForIDLType(@{$type->subtypes}[0], $includesRef, $conditional);
+        return;
+    }
+
     if ($codeGenerator->IsWrapperType($type) || $codeGenerator->IsExternalDictionaryType($type) || $codeGenerator->IsExternalEnumType($type) || $type->name eq "EventListener") {
         AddToIncludes("JS" . $type->name . ".h", $includesRef, $conditional);
         return;
@@ -5432,6 +5438,7 @@ sub GetBaseIDLType
     return "IDLSequence<" . GetIDLType($interface, @{$type->subtypes}[0]) . ">" if $codeGenerator->IsSequenceType($type);
     return "IDLFrozenArray<" . GetIDLType($interface, @{$type->subtypes}[0]) . ">" if $codeGenerator->IsFrozenArrayType($type);
     return "IDLRecord<" . GetIDLType($interface, @{$type->subtypes}[0]) . ", " . GetIDLType($interface, @{$type->subtypes}[1]) . ">" if $codeGenerator->IsRecordType($type);
+    return "IDLPromise<" . GetIDLType($interface, @{$type->subtypes}[0]) . ">" if $codeGenerator->IsPromiseType($type);
     return "IDLUnion<" . join(", ", GetIDLUnionMemberTypes($interface, $type)) . ">" if $type->isUnion;
     return "IDLCallbackFunction<" . GetCallbackClassName($type->name) . ">" if $codeGenerator->IsCallbackFunction($type);
     return "IDLCallbackInterface<" . GetCallbackClassName($type->name) . ">" if $codeGenerator->IsCallbackInterface($type);
index a9760ff..270e416 100644 (file)
 // #include "QualifiedName.cpp"
 #include "RadioButtonGroups.cpp"
 #include "Range.cpp"
+#include "RejectedPromiseTracker.cpp"
 #include "ScopedEventQueue.cpp"
 #include "ScriptElement.cpp"
 #include "ScriptExecutionContext.cpp"
index 3b36882..d4f3d7f 100644 (file)
@@ -182,6 +182,7 @@ namespace WebCore {
     macro(progress) \
     macro(ratechange) \
     macro(readystatechange) \
+    macro(rejectionhandled) \
     macro(removesourcebuffer) \
     macro(removestream) \
     macro(removetrack) \
@@ -230,6 +231,7 @@ namespace WebCore {
     macro(touchstart) \
     macro(track) \
     macro(transitionend) \
+    macro(unhandledrejection) \
     macro(unload) \
     macro(unmute) \
     macro(update) \
index 15404f5..373141f 100644 (file)
@@ -26,6 +26,7 @@ OverflowEvent
 PageTransitionEvent
 PopStateEvent
 ProgressEvent
+PromiseRejectionEvent
 TextEvent
 TransitionEvent
 UIEvent
index 01849d1..cf5fd78 100644 (file)
@@ -79,6 +79,7 @@
     attribute EventHandler onplaying;
     attribute EventHandler onprogress;
     attribute EventHandler onratechange;
+    attribute EventHandler onrejectionhandled;
     attribute EventHandler onreset;
     attribute EventHandler onresize;
     attribute EventHandler onscroll;
@@ -92,6 +93,7 @@
     attribute EventHandler onsuspend;
     attribute EventHandler ontimeupdate;
     attribute EventHandler ontoggle;
+    attribute EventHandler onunhandledrejection;
     attribute EventHandler onvolumechange;
     attribute EventHandler onwaiting;
 
diff --git a/Source/WebCore/dom/PromiseRejectionEvent.cpp b/Source/WebCore/dom/PromiseRejectionEvent.cpp
new file mode 100644 (file)
index 0000000..a368cc5
--- /dev/null
@@ -0,0 +1,48 @@
+/*
+* Copyright (C) 2017 Apple Inc. All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions
+* are met:
+* 1. Redistributions of source code must retain the above copyright
+*    notice, this list of conditions and the following disclaimer.
+* 2. Redistributions in binary form must reproduce the above copyright
+*    notice, this list of conditions and the following disclaimer in the
+*    documentation and/or other materials provided with the distribution.
+*
+* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+* PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#include "config.h"
+#include "PromiseRejectionEvent.h"
+
+#include "DOMWrapperWorld.h"
+#include <heap/HeapInlines.h>
+#include <heap/StrongInlines.h>
+
+using namespace JSC;
+
+namespace WebCore {
+
+PromiseRejectionEvent::PromiseRejectionEvent(ExecState& state, const AtomicString& type, const Init& initializer, IsTrusted isTrusted)
+    : Event(type, initializer, isTrusted)
+    , m_promise(state.vm(), initializer.promise)
+    , m_reason(state.vm(), initializer.reason)
+{
+}
+
+PromiseRejectionEvent::~PromiseRejectionEvent()
+{
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/dom/PromiseRejectionEvent.h b/Source/WebCore/dom/PromiseRejectionEvent.h
new file mode 100644 (file)
index 0000000..0fb1a4c
--- /dev/null
@@ -0,0 +1,59 @@
+/*
+* Copyright (C) 2017 Apple Inc. All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions
+* are met:
+* 1. Redistributions of source code must retain the above copyright
+*    notice, this list of conditions and the following disclaimer.
+* 2. Redistributions in binary form must reproduce the above copyright
+*    notice, this list of conditions and the following disclaimer in the
+*    documentation and/or other materials provided with the distribution.
+*
+* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+* PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+#pragma once
+
+#include "Event.h"
+#include <heap/Strong.h>
+
+namespace WebCore {
+
+class PromiseRejectionEvent final : public Event {
+public:
+    struct Init : EventInit {
+        JSC::JSValue promise;
+        JSC::JSValue reason;
+    };
+
+    static Ref<PromiseRejectionEvent> create(JSC::ExecState& state, const AtomicString& type, const Init& initializer, IsTrusted isTrusted = IsTrusted::No)
+    {
+        return adoptRef(*new PromiseRejectionEvent(state, type, initializer, isTrusted));
+    }
+
+    virtual ~PromiseRejectionEvent();
+
+    JSC::JSValue promise() const { return m_promise.get(); }
+    JSC::JSValue reason() const { return m_reason.get(); }
+
+    EventInterface eventInterface() const override { return PromiseRejectionEventInterfaceType; }
+
+private:
+    PromiseRejectionEvent(JSC::ExecState&, const AtomicString&, const Init&, IsTrusted);
+
+    JSC::Strong<JSC::Unknown> m_promise;
+    JSC::Strong<JSC::Unknown> m_reason;
+};
+
+} // namespace WebCore
diff --git a/Source/WebCore/dom/PromiseRejectionEvent.idl b/Source/WebCore/dom/PromiseRejectionEvent.idl
new file mode 100644 (file)
index 0000000..7a6f58f
--- /dev/null
@@ -0,0 +1,38 @@
+/*
+* Copyright (C) 2017 Apple Inc. All rights reserved.
+*
+* Redistribution and use in source and binary forms, with or without
+* modification, are permitted provided that the following conditions
+* are met:
+* 1. Redistributions of source code must retain the above copyright
+*    notice, this list of conditions and the following disclaimer.
+* 2. Redistributions in binary form must reproduce the above copyright
+*    notice, this list of conditions and the following disclaimer in the
+*    documentation and/or other materials provided with the distribution.
+*
+* THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+* PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+* OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+*/
+
+[
+    Constructor(DOMString type, PromiseRejectionEventInit eventInitDict),
+    ConstructorCallWith=ScriptState,
+    Exposed=(Window),
+] interface PromiseRejectionEvent : Event {
+    readonly attribute Promise<any> promise;
+    readonly attribute any reason;
+};
+
+dictionary PromiseRejectionEventInit : EventInit {
+    required Promise<any> promise;
+    any reason;
+};
diff --git a/Source/WebCore/dom/RejectedPromiseTracker.cpp b/Source/WebCore/dom/RejectedPromiseTracker.cpp
new file mode 100644 (file)
index 0000000..5d1f924
--- /dev/null
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ * Copyright (C) 2017 Yusuke Suzuki <utatane.tea@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "RejectedPromiseTracker.h"
+
+#include "EventNames.h"
+#include "EventTarget.h"
+#include "JSDOMGlobalObject.h"
+#include "PromiseRejectionEvent.h"
+#include "ScriptExecutionContext.h"
+#include <heap/HeapInlines.h>
+#include <heap/Strong.h>
+#include <heap/StrongInlines.h>
+#include <heap/Weak.h>
+#include <heap/WeakInlines.h>
+#include <inspector/ScriptCallStack.h>
+#include <inspector/ScriptCallStackFactory.h>
+#include <runtime/Exception.h>
+#include <runtime/JSCJSValueInlines.h>
+#include <runtime/JSGlobalObject.h>
+#include <runtime/JSPromise.h>
+#include <runtime/WeakGCMapInlines.h>
+
+using namespace JSC;
+using namespace Inspector;
+
+namespace WebCore {
+
+class RejectedPromise {
+    WTF_MAKE_NONCOPYABLE(RejectedPromise);
+public:
+    RejectedPromise(VM& vm, JSDOMGlobalObject& globalObject, JSPromise& promise)
+        : m_globalObject(vm, &globalObject)
+        , m_promise(vm, &promise)
+    {
+    }
+
+    RejectedPromise(RejectedPromise&&) = default;
+
+    JSDOMGlobalObject& globalObject()
+    {
+        return *m_globalObject.get();
+    }
+
+    JSPromise& promise()
+    {
+        return *m_promise.get();
+    }
+
+private:
+    Strong<JSDOMGlobalObject> m_globalObject;
+    Strong<JSPromise> m_promise;
+};
+
+class UnhandledPromise : public RejectedPromise {
+public:
+    UnhandledPromise(VM& vm, JSDOMGlobalObject& globalObject, JSPromise& promise, Ref<ScriptCallStack>&& stack)
+        : RejectedPromise(vm, globalObject, promise)
+        , m_stack(WTFMove(stack))
+    {
+    }
+
+    ScriptCallStack& callStack()
+    {
+        return m_stack.get();
+    }
+
+private:
+    Ref<ScriptCallStack> m_stack;
+};
+
+
+RejectedPromiseTracker::RejectedPromiseTracker(ScriptExecutionContext& context, JSC::VM& vm)
+    : m_context(context)
+    , m_outstandingRejectedPromises(vm)
+{
+}
+
+RejectedPromiseTracker::~RejectedPromiseTracker()
+{
+}
+
+static Ref<ScriptCallStack> createScriptCallStackFromReason(ExecState& state, JSValue reason)
+{
+    VM& vm = state.vm();
+    if (auto* exception = jsDynamicCast<JSC::Exception*>(vm, reason))
+        return createScriptCallStackFromException(&state, exception, ScriptCallStack::maxCallStackSizeToCapture);
+    return createScriptCallStack(&state, ScriptCallStack::maxCallStackSizeToCapture);
+}
+
+void RejectedPromiseTracker::promiseRejected(ExecState& state, JSDOMGlobalObject& globalObject, JSPromise& promise)
+{
+    // https://html.spec.whatwg.org/multipage/webappapis.html#the-hostpromiserejectiontracker-implementation
+
+    VM& vm = state.vm();
+    JSValue reason = promise.result(vm);
+    m_aboutToBeNotifiedRejectedPromises.append(UnhandledPromise { vm, globalObject, promise, createScriptCallStackFromReason(state, reason) });
+}
+
+void RejectedPromiseTracker::promiseHandled(ExecState& state, JSDOMGlobalObject& globalObject, JSPromise& promise)
+{
+    // https://html.spec.whatwg.org/multipage/webappapis.html#the-hostpromiserejectiontracker-implementation
+
+    bool removed = m_aboutToBeNotifiedRejectedPromises.removeFirstMatching([&] (UnhandledPromise& unhandledPromise) {
+        return &unhandledPromise.promise() == &promise;
+    });
+    if (removed)
+        return;
+
+    if (!m_outstandingRejectedPromises.remove(&promise))
+        return;
+
+    VM& vm = state.vm();
+
+    m_context.postTask([this, rejectedPromise = RejectedPromise { vm, globalObject, promise }] (ScriptExecutionContext&) mutable {
+        reportRejectionHandled(WTFMove(rejectedPromise));
+    });
+}
+
+void RejectedPromiseTracker::processQueueSoon()
+{
+    // https://html.spec.whatwg.org/multipage/webappapis.html#notify-about-rejected-promises
+
+    if (m_aboutToBeNotifiedRejectedPromises.isEmpty())
+        return;
+
+    Vector<UnhandledPromise> items = WTFMove(m_aboutToBeNotifiedRejectedPromises);
+    m_context.postTask([this, items = WTFMove(items)] (ScriptExecutionContext&) mutable {
+        reportUnhandledRejections(WTFMove(items));
+    });
+}
+
+void RejectedPromiseTracker::reportUnhandledRejections(Vector<UnhandledPromise>&& unhandledPromises)
+{
+    // https://html.spec.whatwg.org/multipage/webappapis.html#unhandled-promise-rejections
+
+    VM& vm = m_context.vm();
+    JSC::JSLockHolder lock(vm);
+
+    for (auto& unhandledPromise : unhandledPromises) {
+        ExecState& state = *unhandledPromise.globalObject().globalExec();
+        auto& promise = unhandledPromise.promise();
+
+        if (promise.isHandled(vm))
+            continue;
+
+        PromiseRejectionEvent::Init initializer;
+        initializer.cancelable = true;
+        initializer.promise = &promise;
+        initializer.reason = promise.result(vm);
+
+        auto event = PromiseRejectionEvent::create(state, eventNames().unhandledrejectionEvent, initializer);
+        auto target = m_context.errorEventTarget();
+        bool needsDefaultAction = target->dispatchEvent(event);
+
+        if (needsDefaultAction)
+            m_context.reportUnhandledPromiseRejection(state, unhandledPromise.promise(), unhandledPromise.callStack());
+
+        if (!promise.isHandled(vm))
+            m_outstandingRejectedPromises.set(&promise, &promise);
+    }
+}
+
+void RejectedPromiseTracker::reportRejectionHandled(RejectedPromise&& rejectedPromise)
+{
+    // https://html.spec.whatwg.org/multipage/webappapis.html#the-hostpromiserejectiontracker-implementation
+
+    VM& vm = m_context.vm();
+    JSC::JSLockHolder lock(vm);
+
+    ExecState& state = *rejectedPromise.globalObject().globalExec();
+
+    PromiseRejectionEvent::Init initializer;
+    initializer.promise = &rejectedPromise.promise();
+    initializer.reason = rejectedPromise.promise().result(state.vm());
+
+    auto event = PromiseRejectionEvent::create(state, eventNames().rejectionhandledEvent, initializer);
+    auto target = m_context.errorEventTarget();
+    target->dispatchEvent(event);
+}
+
+} // namespace WebCore
diff --git a/Source/WebCore/dom/RejectedPromiseTracker.h b/Source/WebCore/dom/RejectedPromiseTracker.h
new file mode 100644 (file)
index 0000000..d58ff5f
--- /dev/null
@@ -0,0 +1,66 @@
+/*
+ * Copyright (C) 2017 Apple Inc. All rights reserved.
+ * Copyright (C) 2017 Yusuke Suzuki <utatane.tea@gmail.com>
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#pragma once
+
+#include <runtime/JSPromise.h>
+#include <runtime/WeakGCMap.h>
+#include <wtf/Forward.h>
+#include <wtf/Noncopyable.h>
+#include <wtf/Vector.h>
+
+namespace JSC {
+class VM;
+}
+
+namespace WebCore {
+
+class JSDOMGlobalObject;
+class RejectedPromise;
+class ScriptExecutionContext;
+class UnhandledPromise;
+
+class RejectedPromiseTracker {
+    WTF_MAKE_NONCOPYABLE(RejectedPromiseTracker);
+public:
+    explicit RejectedPromiseTracker(ScriptExecutionContext&, JSC::VM&);
+    ~RejectedPromiseTracker();
+
+    void promiseRejected(JSC::ExecState&, JSDOMGlobalObject&, JSC::JSPromise&);
+    void promiseHandled(JSC::ExecState&, JSDOMGlobalObject&, JSC::JSPromise&);
+
+    void processQueueSoon();
+
+private:
+    void reportUnhandledRejections(Vector<UnhandledPromise>&&);
+    void reportRejectionHandled(RejectedPromise&&);
+
+    ScriptExecutionContext& m_context;
+    Vector<UnhandledPromise> m_aboutToBeNotifiedRejectedPromises;
+    JSC::WeakGCMap<JSC::JSPromise*, JSC::JSPromise> m_outstandingRejectedPromises;
+};
+
+} // namespace WebCore
index f3aaf60..8c9c09b 100644 (file)
 #include "DatabaseContext.h"
 #include "Document.h"
 #include "ErrorEvent.h"
+#include "JSDOMExceptionHandling.h"
 #include "JSDOMWindow.h"
 #include "MessagePort.h"
 #include "NoEventDispatchAssertion.h"
 #include "PublicURLManager.h"
+#include "RejectedPromiseTracker.h"
 #include "ResourceRequest.h"
 #include "ScriptState.h"
 #include "Settings.h"
@@ -46,6 +48,7 @@
 #include <heap/StrongInlines.h>
 #include <inspector/ScriptCallStack.h>
 #include <runtime/Exception.h>
+#include <runtime/JSPromise.h>
 #include <wtf/MainThread.h>
 #include <wtf/Ref.h>
 
@@ -379,6 +382,27 @@ void ScriptExecutionContext::reportException(const String& errorMessage, int lin
         logExceptionToConsole(exception->m_errorMessage, exception->m_sourceURL, exception->m_lineNumber, exception->m_columnNumber, WTFMove(exception->m_callStack));
 }
 
+void ScriptExecutionContext::reportUnhandledPromiseRejection(JSC::ExecState& state, JSC::JSPromise& promise, Inspector::ScriptCallStack& callStack)
+{
+    JSC::VM& vm = state.vm();
+    auto scope = DECLARE_CATCH_SCOPE(vm);
+
+    int lineNumber = 0;
+    int columnNumber = 0;
+    String sourceURL;
+
+    JSC::JSValue result = promise.result(vm);
+    String resultMessage = retrieveErrorMessage(state, vm, result, scope);
+    String errorMessage = makeString("Unhandled Promise Rejection: ", resultMessage);
+    if (const ScriptCallFrame* callFrame = callStack.firstNonNativeCallFrame()) {
+        lineNumber = callFrame->lineNumber();
+        columnNumber = callFrame->columnNumber();
+        sourceURL = callFrame->sourceURL();
+    }
+
+    logExceptionToConsole(errorMessage, sourceURL, lineNumber, columnNumber, &callStack);
+}
+
 void ScriptExecutionContext::addConsoleMessage(MessageSource source, MessageLevel level, const String& message, const String& sourceURL, unsigned lineNumber, unsigned columnNumber, JSC::ExecState* state, unsigned long requestIdentifier)
 {
     addMessage(source, level, message, sourceURL, lineNumber, columnNumber, 0, state, requestIdentifier);
@@ -464,6 +488,16 @@ JSC::VM& ScriptExecutionContext::vm()
     return downcast<WorkerGlobalScope>(*this).script()->vm();
 }
 
+RejectedPromiseTracker& ScriptExecutionContext::ensureRejectedPromiseTrackerSlow()
+{
+    // ScriptExecutionContext::vm() in Worker is only available after WorkerGlobalScope initialization is done.
+    // When initializing ScriptExecutionContext, vm() is not ready.
+
+    ASSERT(!m_rejectedPromiseTracker);
+    m_rejectedPromiseTracker = std::make_unique<RejectedPromiseTracker>(*this, vm());
+    return *m_rejectedPromiseTracker.get();
+}
+
 void ScriptExecutionContext::setDatabaseContext(DatabaseContext* databaseContext)
 {
     m_databaseContext = databaseContext;
index 7f2f5e0..ea31e21 100644 (file)
@@ -40,6 +40,7 @@
 namespace JSC {
 class Exception;
 class ExecState;
+class JSPromise;
 class VM;
 template<typename> class Strong;
 }
@@ -56,6 +57,7 @@ class EventQueue;
 class EventTarget;
 class MessagePort;
 class PublicURLManager;
+class RejectedPromiseTracker;
 class ResourceRequest;
 class SecurityOrigin;
 class SocketProvider;
@@ -94,6 +96,7 @@ public:
 
     bool sanitizeScriptError(String& errorMessage, int& lineNumber, int& columnNumber, String& sourceURL, JSC::Strong<JSC::Unknown>& error, CachedScript* = nullptr);
     void reportException(const String& errorMessage, int lineNumber, int columnNumber, const String& sourceURL, JSC::Exception*, RefPtr<Inspector::ScriptCallStack>&&, CachedScript* = nullptr);
+    void reportUnhandledPromiseRejection(JSC::ExecState&, JSC::JSPromise&, Inspector::ScriptCallStack&);
 
     void addConsoleMessage(MessageSource, MessageLevel, const String& message, const String& sourceURL, unsigned lineNumber, unsigned columnNumber, JSC::ExecState* = nullptr, unsigned long requestIdentifier = 0);
     virtual void addConsoleMessage(MessageSource, MessageLevel, const String& message, unsigned long requestIdentifier = 0) = 0;
@@ -196,6 +199,7 @@ public:
     virtual Seconds domTimerAlignmentInterval(bool hasReachedMaxNestingLevel) const;
 
     virtual EventQueue& eventQueue() const = 0;
+    virtual EventTarget* errorEventTarget() = 0;
 
     DatabaseContext* databaseContext() { return m_databaseContext.get(); }
     void setDatabaseContext(DatabaseContext*);
@@ -208,6 +212,13 @@ public:
     int timerNestingLevel() const { return m_timerNestingLevel; }
     void setTimerNestingLevel(int timerNestingLevel) { m_timerNestingLevel = timerNestingLevel; }
 
+    RejectedPromiseTracker& ensureRejectedPromiseTracker()
+    {
+        if (m_rejectedPromiseTracker)
+            return *m_rejectedPromiseTracker.get();
+        return ensureRejectedPromiseTrackerSlow();
+    }
+
     JSC::ExecState* execState();
 
 protected:
@@ -227,13 +238,14 @@ protected:
 
 private:
     virtual void addMessage(MessageSource, MessageLevel, const String& message, const String& sourceURL, unsigned lineNumber, unsigned columnNumber, RefPtr<Inspector::ScriptCallStack>&&, JSC::ExecState* = nullptr, unsigned long requestIdentifier = 0) = 0;
-    virtual EventTarget* errorEventTarget() = 0;
     virtual void logExceptionToConsole(const String& errorMessage, const String& sourceURL, int lineNumber, int columnNumber, RefPtr<Inspector::ScriptCallStack>&&) = 0;
     bool dispatchErrorEvent(const String& errorMessage, int lineNumber, int columnNumber, const String& sourceURL, JSC::Exception*, CachedScript*);
 
     virtual void refScriptExecutionContext() = 0;
     virtual void derefScriptExecutionContext() = 0;
 
+    RejectedPromiseTracker& ensureRejectedPromiseTrackerSlow();
+
     void checkConsistency() const;
 
     HashSet<MessagePort*> m_messagePorts;
@@ -244,6 +256,7 @@ private:
 
     struct PendingException;
     std::unique_ptr<Vector<std::unique_ptr<PendingException>>> m_pendingExceptions;
+    std::unique_ptr<RejectedPromiseTracker> m_rejectedPromiseTracker;
 
     ActiveDOMObject::ReasonForSuspension m_reasonForSuspendingActiveDOMObjects { static_cast<ActiveDOMObject::ReasonForSuspension>(-1) };
 
index a8fb206..d0a5633 100644 (file)
@@ -3297,8 +3297,9 @@ void HTMLMediaElement::pauseInternal()
         m_paused = true;
         scheduleTimeupdateEvent(false);
         scheduleEvent(eventNames().pauseEvent);
-        rejectPendingPlayPromises(DOMError::create("AbortError", "The operation was aborted."));
-
+        m_promiseTaskQueue.enqueueTask([this]() {
+            rejectPendingPlayPromises(DOMError::create("AbortError", "The operation was aborted."));
+        });
         if (MemoryPressureHandler::singleton().isUnderMemoryPressure())
             purgeBufferedDataIfPossible();
     }