blob: 3ed8d247ee5d1659fbe25554de1e4a079984cff1 [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 "config.h"
#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 "core/dom/ExecutionContext.h"
#include "core/events/EventTarget.h"
#include "core/events/PromiseRejectionEvent.h"
#include "core/inspector/ConsoleMessage.h"
#include "core/inspector/ScriptArguments.h"
#include "platform/RuntimeEnabledFeatures.h"
namespace blink {
static const unsigned maxReportedHandlersPendingResolution = 1000;
class RejectedPromises::Message final : public NoBaseWillBeGarbageCollectedFinalized<RejectedPromises::Message> {
public:
static PassOwnPtrWillBeRawPtr<Message> create(ScriptState* scriptState, v8::Local<v8::Promise> promise, const ScriptValue& exception, const String& errorMessage, const String& resourceName, int scriptId, int lineNumber, int columnNumber, PassRefPtrWillBeRawPtr<ScriptCallStack> callStack)
{
return adoptPtrWillBeNoop(new Message(scriptState, promise, exception, errorMessage, resourceName, scriptId, lineNumber, columnNumber, callStack));
}
DEFINE_INLINE_TRACE()
{
visitor->trace(m_callStack);
}
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 (v8::V8::IsExecutionTerminating(m_scriptState->isolate()))
return;
ExecutionContext* executionContext = m_scriptState->executionContext();
if (!executionContext)
return;
ScriptState::Scope scope(m_scriptState);
v8::Local<v8::Value> value = m_promise.newLocal(m_scriptState->isolate());
// Either collected or https://crbug.com/450330
if (value.IsEmpty() || !value->IsPromise())
return;
ASSERT(!v8::Local<v8::Promise>::Cast(value)->HasHandler());
EventTarget* target = executionContext->errorEventTarget();
if (RuntimeEnabledFeatures::promiseRejectionEventEnabled() && target) {
PromiseRejectionEventInit init;
init.setPromise(ScriptPromise(m_scriptState, value));
init.setReason(m_exception);
init.setCancelable(true);
RefPtrWillBeRawPtr<PromiseRejectionEvent> event = PromiseRejectionEvent::create(m_scriptState, EventTypeNames::unhandledrejection, init);
// Log to console if event was not preventDefault()'ed.
m_shouldLogToConsole = target->dispatchEvent(event);
}
if (m_shouldLogToConsole) {
const String errorMessage = "Uncaught (in promise)";
Vector<ScriptValue> args;
args.append(ScriptValue(m_scriptState, v8String(m_scriptState->isolate(), errorMessage)));
args.append(m_exception);
RefPtrWillBeRawPtr<ScriptArguments> arguments = ScriptArguments::create(m_scriptState, args);
String embedderErrorMessage = m_errorMessage;
if (embedderErrorMessage.isEmpty())
embedderErrorMessage = errorMessage;
else if (embedderErrorMessage.startsWith("Uncaught "))
embedderErrorMessage.insert(" (in promise)", 8);
RefPtrWillBeRawPtr<ConsoleMessage> consoleMessage = ConsoleMessage::create(JSMessageSource, ErrorMessageLevel, embedderErrorMessage, m_resourceName, m_lineNumber, m_columnNumber);
consoleMessage->setScriptArguments(arguments);
consoleMessage->setCallStack(m_callStack);
consoleMessage->setScriptId(m_scriptId);
m_consoleMessageId = consoleMessage->assignMessageId();
executionContext->addConsoleMessage(consoleMessage.release());
}
m_callStack.clear();
}
void revoke()
{
ExecutionContext* executionContext = m_scriptState->executionContext();
if (!executionContext)
return;
ScriptState::Scope scope(m_scriptState);
v8::Local<v8::Value> value = m_promise.newLocal(m_scriptState->isolate());
// Either collected or https://crbug.com/450330
if (value.IsEmpty() || !value->IsPromise())
return;
EventTarget* target = executionContext->errorEventTarget();
if (RuntimeEnabledFeatures::promiseRejectionEventEnabled() && target) {
PromiseRejectionEventInit init;
init.setPromise(ScriptPromise(m_scriptState, value));
init.setReason(m_exception);
RefPtrWillBeRawPtr<PromiseRejectionEvent> event = PromiseRejectionEvent::create(m_scriptState, EventTypeNames::rejectionhandled, init);
target->dispatchEvent(event);
}
if (m_shouldLogToConsole) {
RefPtrWillBeRawPtr<ConsoleMessage> consoleMessage = ConsoleMessage::create(JSMessageSource, RevokedErrorMessageLevel, "Handler added to rejected promise");
consoleMessage->setRelatedMessageId(m_consoleMessageId);
executionContext->addConsoleMessage(consoleMessage.release());
}
}
private:
Message(ScriptState* scriptState, v8::Local<v8::Promise> promise, const ScriptValue& exception, const String& errorMessage, const String& resourceName, int scriptId, int lineNumber, int columnNumber, PassRefPtrWillBeRawPtr<ScriptCallStack> callStack)
: m_scriptState(scriptState)
, m_promise(scriptState->isolate(), promise)
, m_exception(exception)
, m_errorMessage(errorMessage)
, m_resourceName(resourceName)
, m_scriptId(scriptId)
, m_lineNumber(lineNumber)
, m_columnNumber(columnNumber)
, m_callStack(callStack)
, m_consoleMessageId(0)
, m_collected(false)
, m_shouldLogToConsole(true)
{
m_promise.setWeak(this, &Message::didCollectPromise);
}
static void didCollectPromise(const v8::WeakCallbackInfo<Message>& data)
{
data.GetParameter()->m_collected = true;
data.GetParameter()->m_promise.clear();
}
ScriptState* m_scriptState;
ScopedPersistent<v8::Promise> m_promise;
ScriptValue m_exception;
String m_errorMessage;
String m_resourceName;
int m_scriptId;
int m_lineNumber;
int m_columnNumber;
RefPtrWillBeMember<ScriptCallStack> m_callStack;
unsigned m_consoleMessageId;
bool m_collected;
bool m_shouldLogToConsole;
};
RejectedPromises::RejectedPromises()
{
}
DEFINE_EMPTY_DESTRUCTOR_WILL_BE_REMOVED(RejectedPromises);
DEFINE_TRACE(RejectedPromises)
{
visitor->trace(m_queue);
visitor->trace(m_reportedAsErrors);
}
void RejectedPromises::rejectedWithNoHandler(ScriptState* scriptState, v8::PromiseRejectMessage data, const String& errorMessage, const String& resourceName, int scriptId, int lineNumber, int columnNumber, PassRefPtrWillBeRawPtr<ScriptCallStack> callStack)
{
m_queue.append(Message::create(scriptState, data.GetPromise(), ScriptValue(scriptState, data.GetValue()), errorMessage, resourceName, scriptId, lineNumber, columnNumber, callStack));
}
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) {
auto& message = m_reportedAsErrors.at(i);
if (!message->isCollected() && message->hasPromise(data.GetPromise())) {
message->revoke();
m_reportedAsErrors.remove(i);
return;
}
}
}
void RejectedPromises::dispose()
{
processQueue();
}
void RejectedPromises::processQueue()
{
// 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 (!m_queue.isEmpty()) {
OwnPtrWillBeRawPtr<Message> message = m_queue.takeFirst();
if (message->isCollected())
continue;
message->report();
m_reportedAsErrors.append(message.release());
if (m_reportedAsErrors.size() > maxReportedHandlersPendingResolution)
m_reportedAsErrors.remove(0, maxReportedHandlersPendingResolution / 10);
}
}
} // namespace blink