1/*
2 * Copyright (C) 2016 Apple Inc. All rights reserved.
3 * Copyright (C) 2017 Yusuke Suzuki <utatane.tea@gmail.com>
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions
7 * are met:
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 *
14 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
18 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25 */
26
27#include "config.h"
28#include "RejectedPromiseTracker.h"
29
30#include "EventNames.h"
31#include "EventTarget.h"
32#include "JSDOMGlobalObject.h"
33#include "PromiseRejectionEvent.h"
34#include "ScriptExecutionContext.h"
35#include <heap/HeapInlines.h>
36#include <heap/Strong.h>
37#include <heap/StrongInlines.h>
38#include <heap/Weak.h>
39#include <heap/WeakInlines.h>
40#include <inspector/ScriptCallStack.h>
41#include <inspector/ScriptCallStackFactory.h>
42#include <runtime/Exception.h>
43#include <runtime/JSCJSValueInlines.h>
44#include <runtime/JSGlobalObject.h>
45#include <runtime/JSPromise.h>
46#include <runtime/WeakGCMapInlines.h>
47
48using namespace JSC;
49using namespace Inspector;
50
51namespace WebCore {
52
53class RejectedPromise {
54 WTF_MAKE_NONCOPYABLE(RejectedPromise);
55public:
56 RejectedPromise(VM& vm, JSDOMGlobalObject& globalObject, JSPromise& promise)
57 : m_globalObject(vm, &globalObject)
58 , m_promise(vm, &promise)
59 {
60 }
61
62 RejectedPromise(RejectedPromise&&) = default;
63
64 JSDOMGlobalObject& globalObject()
65 {
66 return *m_globalObject.get();
67 }
68
69 JSPromise& promise()
70 {
71 return *m_promise.get();
72 }
73
74private:
75 Strong<JSDOMGlobalObject> m_globalObject;
76 Strong<JSPromise> m_promise;
77};
78
79class UnhandledPromise : public RejectedPromise {
80public:
81 UnhandledPromise(VM& vm, JSDOMGlobalObject& globalObject, JSPromise& promise, Ref<ScriptCallStack>&& stack)
82 : RejectedPromise(vm, globalObject, promise)
83 , m_stack(WTFMove(stack))
84 {
85 }
86
87 ScriptCallStack& callStack()
88 {
89 return m_stack.get();
90 }
91
92private:
93 Ref<ScriptCallStack> m_stack;
94};
95
96
97RejectedPromiseTracker::RejectedPromiseTracker(ScriptExecutionContext& context, JSC::VM& vm)
98 : m_context(context)
99 , m_outstandingRejectedPromises(vm)
100{
101}
102
103RejectedPromiseTracker::~RejectedPromiseTracker()
104{
105}
106
107static Ref<ScriptCallStack> createScriptCallStackFromReason(ExecState& state, JSValue reason)
108{
109 VM& vm = state.vm();
110 if (auto* exception = jsDynamicCast<JSC::Exception*>(vm, reason))
111 return createScriptCallStackFromException(&state, exception, ScriptCallStack::maxCallStackSizeToCapture);
112 return createScriptCallStack(&state, ScriptCallStack::maxCallStackSizeToCapture);
113}
114
115void RejectedPromiseTracker::promiseRejected(ExecState& state, JSDOMGlobalObject& globalObject, JSPromise& promise)
116{
117 // https://html.spec.whatwg.org/multipage/webappapis.html#the-hostpromiserejectiontracker-implementation
118
119 VM& vm = state.vm();
120 JSValue reason = promise.result(vm);
121 m_aboutToBeNotifiedRejectedPromises.append(UnhandledPromise { vm, globalObject, promise, createScriptCallStackFromReason(state, reason) });
122}
123
124void RejectedPromiseTracker::promiseHandled(ExecState& state, JSDOMGlobalObject& globalObject, JSPromise& promise)
125{
126 // https://html.spec.whatwg.org/multipage/webappapis.html#the-hostpromiserejectiontracker-implementation
127
128 bool removed = m_aboutToBeNotifiedRejectedPromises.removeFirstMatching([&] (UnhandledPromise& unhandledPromise) {
129 return &unhandledPromise.promise() == &promise;
130 });
131 if (removed)
132 return;
133
134 if (!m_outstandingRejectedPromises.remove(&promise))
135 return;
136
137 VM& vm = state.vm();
138
139 m_context.postTask([this, rejectedPromise = RejectedPromise { vm, globalObject, promise }] (ScriptExecutionContext&) mutable {
140 reportRejectionHandled(WTFMove(rejectedPromise));
141 });
142}
143
144void RejectedPromiseTracker::processQueueSoon()
145{
146 // https://html.spec.whatwg.org/multipage/webappapis.html#unhandled-promise-rejections
147
148 if (m_aboutToBeNotifiedRejectedPromises.isEmpty())
149 return;
150
151 Vector<UnhandledPromise> items;
152 items.swap(m_aboutToBeNotifiedRejectedPromises);
153
154 m_context.postTask([this, items = WTFMove(items)] (ScriptExecutionContext&) mutable {
155 reportUnhandledRejection(WTFMove(items));
156 });
157}
158
159void RejectedPromiseTracker::reportUnhandledRejection(Vector<UnhandledPromise>&& unhandledPromises)
160{
161 // https://html.spec.whatwg.org/multipage/webappapis.html#unhandled-promise-rejections
162
163 VM& vm = m_context.vm();
164 JSC::JSLockHolder lock(vm);
165
166 for (auto& unhandledPromise : unhandledPromises) {
167 ExecState& state = *unhandledPromise.globalObject().globalExec();
168 auto& promise = unhandledPromise.promise();
169
170 if (promise.isHandled(vm))
171 continue;
172
173 PromiseRejectionEvent::Init initializer;
174 initializer.cancelable = true;
175 initializer.promise = &promise;
176 initializer.reason = promise.result(vm);
177
178 auto event = PromiseRejectionEvent::create(state, eventNames().unhandledrejectionEvent, initializer);
179 auto target = m_context.errorEventTarget();
180 bool needsDefaultAction = target->dispatchEvent(event);
181
182 if (needsDefaultAction)
183 m_context.reportUnhandledPromiseRejection(state, unhandledPromise.promise(), unhandledPromise.callStack());
184
185 if (!promise.isHandled(vm))
186 m_outstandingRejectedPromises.set(&promise, &promise);
187 }
188}
189
190void RejectedPromiseTracker::reportRejectionHandled(RejectedPromise&& rejectedPromise)
191{
192 // https://html.spec.whatwg.org/multipage/webappapis.html#the-hostpromiserejectiontracker-implementation
193
194 VM& vm = m_context.vm();
195 JSC::JSLockHolder lock(vm);
196
197 ExecState& state = *rejectedPromise.globalObject().globalExec();
198
199 PromiseRejectionEvent::Init initializer;
200 initializer.cancelable = false;
201 initializer.promise = &rejectedPromise.promise();
202 initializer.reason = rejectedPromise.promise().result(state.vm());
203
204 auto event = PromiseRejectionEvent::create(state, eventNames().rejectionhandledEvent, initializer);
205 auto target = m_context.errorEventTarget();
206 target->dispatchEvent(event);
207}
208
209} // namespace WebCore