blob: 0b5dd7af8f00192d0a066a196eefe12f30c2662d [file] [log] [blame]
/*
* 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 "JSDOMPromise.h"
#include "PromiseRejectionEvent.h"
#include "ScriptExecutionContext.h"
#include <JavaScriptCore/Exception.h>
#include <JavaScriptCore/HeapInlines.h>
#include <JavaScriptCore/JSCJSValueInlines.h>
#include <JavaScriptCore/JSGlobalObject.h>
#include <JavaScriptCore/JSPromise.h>
#include <JavaScriptCore/ScriptCallStack.h>
#include <JavaScriptCore/ScriptCallStackFactory.h>
#include <JavaScriptCore/Strong.h>
#include <JavaScriptCore/StrongInlines.h>
#include <JavaScriptCore/Weak.h>
#include <JavaScriptCore/WeakGCMapInlines.h>
#include <JavaScriptCore/WeakInlines.h>
namespace WebCore {
using namespace JSC;
using namespace Inspector;
class UnhandledPromise {
WTF_MAKE_NONCOPYABLE(UnhandledPromise);
public:
UnhandledPromise(JSDOMGlobalObject& globalObject, JSPromise& promise, RefPtr<ScriptCallStack>&& stack)
: m_promise(DOMPromise::create(globalObject, promise))
, m_stack(WTFMove(stack))
{
}
UnhandledPromise(UnhandledPromise&&) = default;
ScriptCallStack* callStack()
{
return m_stack.get();
}
DOMPromise& promise()
{
return m_promise.get();
}
private:
Ref<DOMPromise> m_promise;
RefPtr<ScriptCallStack> m_stack;
};
RejectedPromiseTracker::RejectedPromiseTracker(ScriptExecutionContext& context, JSC::VM& vm)
: m_context(context)
, m_outstandingRejectedPromises(vm)
{
}
RejectedPromiseTracker::~RejectedPromiseTracker() = default;
static RefPtr<ScriptCallStack> createScriptCallStackFromReason(JSGlobalObject& lexicalGlobalObject, JSValue reason)
{
VM& vm = lexicalGlobalObject.vm();
// Always capture a stack from the exception if this rejection was an exception.
if (auto* exception = vm.lastException()) {
if (exception->value() == reason)
return createScriptCallStackFromException(&lexicalGlobalObject, exception);
}
// Otherwise, only capture a stack if a debugger is open.
if (lexicalGlobalObject.debugger())
return createScriptCallStack(&lexicalGlobalObject);
return nullptr;
}
void RejectedPromiseTracker::promiseRejected(JSDOMGlobalObject& globalObject, JSPromise& promise)
{
// https://html.spec.whatwg.org/multipage/webappapis.html#the-hostpromiserejectiontracker-implementation
JSValue reason = promise.result(globalObject.vm());
m_aboutToBeNotifiedRejectedPromises.append(UnhandledPromise { globalObject, promise, createScriptCallStackFromReason(globalObject, reason) });
}
void RejectedPromiseTracker::promiseHandled(JSDOMGlobalObject& globalObject, JSPromise& promise)
{
// https://html.spec.whatwg.org/multipage/webappapis.html#the-hostpromiserejectiontracker-implementation
bool removed = m_aboutToBeNotifiedRejectedPromises.removeFirstMatching([&] (UnhandledPromise& unhandledPromise) {
auto& domPromise = unhandledPromise.promise();
if (domPromise.isSuspended())
return false;
return domPromise.promise() == &promise;
});
if (removed)
return;
if (!m_outstandingRejectedPromises.remove(&promise))
return;
m_context.postTask([this, rejectedPromise = DOMPromise::create(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) {
auto& domPromise = unhandledPromise.promise();
if (domPromise.isSuspended())
continue;
auto& lexicalGlobalObject = *domPromise.globalObject();
auto& promise = *domPromise.promise();
if (promise.isHandled(vm))
continue;
PromiseRejectionEvent::Init initializer;
initializer.cancelable = true;
initializer.promise = &domPromise;
initializer.reason = promise.result(vm);
auto event = PromiseRejectionEvent::create(eventNames().unhandledrejectionEvent, initializer);
auto target = m_context.errorEventTarget();
target->dispatchEvent(event);
if (!event->defaultPrevented())
m_context.reportUnhandledPromiseRejection(lexicalGlobalObject, promise, unhandledPromise.callStack());
if (!promise.isHandled(vm))
m_outstandingRejectedPromises.set(&promise, &promise);
}
}
void RejectedPromiseTracker::reportRejectionHandled(Ref<DOMPromise>&& rejectedPromise)
{
// https://html.spec.whatwg.org/multipage/webappapis.html#the-hostpromiserejectiontracker-implementation
VM& vm = m_context.vm();
JSC::JSLockHolder lock(vm);
if (rejectedPromise->isSuspended())
return;
auto& promise = *rejectedPromise->promise();
PromiseRejectionEvent::Init initializer;
initializer.promise = rejectedPromise.ptr();
initializer.reason = promise.result(vm);
auto event = PromiseRejectionEvent::create(eventNames().rejectionhandledEvent, initializer);
auto target = m_context.errorEventTarget();
target->dispatchEvent(event);
}
} // namespace WebCore