| // 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 |