blob: 1d88131c21f7facd1b407880573a48ccab5f2266 [file] [log] [blame]
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "bindings/core/v8/RejectedPromises.h"
#include "bindings/core/v8/ScopedPersistent.h"
#include "bindings/core/v8/ScriptState.h"
#include "bindings/core/v8/ScriptValue.h"
#include "bindings/core/v8/V8Binding.h"
#include "bindings/core/v8/V8PerIsolateData.h"
#include "core/dom/ExecutionContext.h"
#include "core/events/EventTarget.h"
#include "core/events/PromiseRejectionEvent.h"
#include "core/inspector/ThreadDebugger.h"
#include "public/platform/Platform.h"
#include "public/platform/WebScheduler.h"
#include "public/platform/WebTaskRunner.h"
#include "public/platform/WebThread.h"
#include "wtf/Functional.h"
#include "wtf/PtrUtil.h"
#include <memory>
namespace blink {
static const unsigned maxReportedHandlersPendingResolution = 1000;
class RejectedPromises::Message final {
public:
static std::unique_ptr<Message> create(
ScriptState* scriptState,
v8::Local<v8::Promise> promise,
v8::Local<v8::Value> exception,
const String& errorMessage,
std::unique_ptr<SourceLocation> location,
AccessControlStatus corsStatus) {
return wrapUnique(new Message(scriptState, promise, exception, errorMessage,
std::move(location), corsStatus));
}
bool isCollected() { return m_collected || !m_scriptState->contextIsValid(); }
bool hasPromise(v8::Local<v8::Value> promise) {
ScriptState::Scope scope(m_scriptState);
return promise == m_promise.newLocal(m_scriptState->isolate());
}
void report() {
if (!m_scriptState->contextIsValid())
return;
// If execution termination has been triggered, quietly bail out.
if (m_scriptState->isolate()->IsExecutionTerminating())
return;
ExecutionContext* executionContext = m_scriptState->getExecutionContext();
if (!executionContext)
return;
ScriptState::Scope scope(m_scriptState);
v8::Local<v8::Value> value = m_promise.newLocal(m_scriptState->isolate());
v8::Local<v8::Value> reason =
m_exception.newLocal(m_scriptState->isolate());
// Either collected or https://crbug.com/450330
if (value.IsEmpty() || !value->IsPromise())
return;
ASSERT(!hasHandler());
EventTarget* target = executionContext->errorEventTarget();
if (target &&
!executionContext->shouldSanitizeScriptError(m_resourceName,
m_corsStatus)) {
PromiseRejectionEventInit init;
init.setPromise(ScriptPromise(m_scriptState, value));
init.setReason(ScriptValue(m_scriptState, reason));
init.setCancelable(true);
PromiseRejectionEvent* event = PromiseRejectionEvent::create(
m_scriptState, EventTypeNames::unhandledrejection, init);
// Log to console if event was not canceled.
m_shouldLogToConsole =
target->dispatchEvent(event) == DispatchEventResult::NotCanceled;
}
if (m_shouldLogToConsole) {
V8PerIsolateData* data = V8PerIsolateData::from(m_scriptState->isolate());
if (data->threadDebugger())
m_promiseRejectionId = data->threadDebugger()->promiseRejected(
m_scriptState->context(), m_errorMessage, reason,
std::move(m_location));
}
m_location.reset();
}
void revoke() {
ExecutionContext* executionContext = m_scriptState->getExecutionContext();
if (!executionContext)
return;
ScriptState::Scope scope(m_scriptState);
v8::Local<v8::Value> value = m_promise.newLocal(m_scriptState->isolate());
v8::Local<v8::Value> reason =
m_exception.newLocal(m_scriptState->isolate());
// Either collected or https://crbug.com/450330
if (value.IsEmpty() || !value->IsPromise())
return;
EventTarget* target = executionContext->errorEventTarget();
if (target &&
!executionContext->shouldSanitizeScriptError(m_resourceName,
m_corsStatus)) {
PromiseRejectionEventInit init;
init.setPromise(ScriptPromise(m_scriptState, value));
init.setReason(ScriptValue(m_scriptState, reason));
PromiseRejectionEvent* event = PromiseRejectionEvent::create(
m_scriptState, EventTypeNames::rejectionhandled, init);
target->dispatchEvent(event);
}
if (m_shouldLogToConsole && m_promiseRejectionId) {
V8PerIsolateData* data = V8PerIsolateData::from(m_scriptState->isolate());
if (data->threadDebugger())
data->threadDebugger()->promiseRejectionRevoked(
m_scriptState->context(), m_promiseRejectionId);
}
}
void makePromiseWeak() {
ASSERT(!m_promise.isEmpty() && !m_promise.isWeak());
m_promise.setWeak(this, &Message::didCollectPromise);
m_exception.setWeak(this, &Message::didCollectException);
}
void makePromiseStrong() {
ASSERT(!m_promise.isEmpty() && m_promise.isWeak());
m_promise.clearWeak();
m_exception.clearWeak();
}
bool hasHandler() {
ASSERT(!isCollected());
ScriptState::Scope scope(m_scriptState);
v8::Local<v8::Value> value = m_promise.newLocal(m_scriptState->isolate());
return v8::Local<v8::Promise>::Cast(value)->HasHandler();
}
private:
Message(ScriptState* scriptState,
v8::Local<v8::Promise> promise,
v8::Local<v8::Value> exception,
const String& errorMessage,
std::unique_ptr<SourceLocation> location,
AccessControlStatus corsStatus)
: m_scriptState(scriptState),
m_promise(scriptState->isolate(), promise),
m_exception(scriptState->isolate(), exception),
m_errorMessage(errorMessage),
m_resourceName(location->url()),
m_location(std::move(location)),
m_promiseRejectionId(0),
m_collected(false),
m_shouldLogToConsole(true),
m_corsStatus(corsStatus) {}
static void didCollectPromise(const v8::WeakCallbackInfo<Message>& data) {
data.GetParameter()->m_collected = true;
data.GetParameter()->m_promise.clear();
}
static void didCollectException(const v8::WeakCallbackInfo<Message>& data) {
data.GetParameter()->m_exception.clear();
}
ScriptState* m_scriptState;
ScopedPersistent<v8::Promise> m_promise;
ScopedPersistent<v8::Value> m_exception;
String m_errorMessage;
String m_resourceName;
std::unique_ptr<SourceLocation> m_location;
unsigned m_promiseRejectionId;
bool m_collected;
bool m_shouldLogToConsole;
AccessControlStatus m_corsStatus;
};
RejectedPromises::RejectedPromises() {}
RejectedPromises::~RejectedPromises() {}
void RejectedPromises::rejectedWithNoHandler(
ScriptState* scriptState,
v8::PromiseRejectMessage data,
const String& errorMessage,
std::unique_ptr<SourceLocation> location,
AccessControlStatus corsStatus) {
m_queue.append(Message::create(scriptState, data.GetPromise(),
data.GetValue(), errorMessage,
std::move(location), corsStatus));
}
void RejectedPromises::handlerAdded(v8::PromiseRejectMessage data) {
// First look it up in the pending messages and fast return, it'll be covered
// by processQueue().
for (auto it = m_queue.begin(); it != m_queue.end(); ++it) {
if (!(*it)->isCollected() && (*it)->hasPromise(data.GetPromise())) {
m_queue.remove(it);
return;
}
}
// Then look it up in the reported errors.
for (size_t i = 0; i < m_reportedAsErrors.size(); ++i) {
std::unique_ptr<Message>& message = m_reportedAsErrors.at(i);
if (!message->isCollected() && message->hasPromise(data.GetPromise())) {
message->makePromiseStrong();
Platform::current()
->currentThread()
->scheduler()
->timerTaskRunner()
->postTask(BLINK_FROM_HERE, WTF::bind(&RejectedPromises::revokeNow,
RefPtr<RejectedPromises>(this),
passed(std::move(message))));
m_reportedAsErrors.remove(i);
return;
}
}
}
std::unique_ptr<RejectedPromises::MessageQueue>
RejectedPromises::createMessageQueue() {
return wrapUnique(new MessageQueue());
}
void RejectedPromises::dispose() {
if (m_queue.isEmpty())
return;
std::unique_ptr<MessageQueue> queue = createMessageQueue();
queue->swap(m_queue);
processQueueNow(std::move(queue));
}
void RejectedPromises::processQueue() {
if (m_queue.isEmpty())
return;
std::unique_ptr<MessageQueue> queue = createMessageQueue();
queue->swap(m_queue);
Platform::current()
->currentThread()
->scheduler()
->timerTaskRunner()
->postTask(BLINK_FROM_HERE, WTF::bind(&RejectedPromises::processQueueNow,
PassRefPtr<RejectedPromises>(this),
passed(std::move(queue))));
}
void RejectedPromises::processQueueNow(std::unique_ptr<MessageQueue> queue) {
// Remove collected handlers.
for (size_t i = 0; i < m_reportedAsErrors.size();) {
if (m_reportedAsErrors.at(i)->isCollected())
m_reportedAsErrors.remove(i);
else
++i;
}
while (!queue->isEmpty()) {
std::unique_ptr<Message> message = queue->takeFirst();
if (message->isCollected())
continue;
if (!message->hasHandler()) {
message->report();
message->makePromiseWeak();
m_reportedAsErrors.append(std::move(message));
if (m_reportedAsErrors.size() > maxReportedHandlersPendingResolution)
m_reportedAsErrors.remove(0, maxReportedHandlersPendingResolution / 10);
}
}
}
void RejectedPromises::revokeNow(std::unique_ptr<Message> message) {
message->revoke();
}
} // namespace blink