| /* |
| * Copyright (C) 2013 Google Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions are |
| * met: |
| * |
| * * Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * * 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. |
| * * Neither the name of Google Inc. nor the names of its |
| * contributors may be used to endorse or promote products derived from |
| * this software without specific prior written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
| * "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 THE COPYRIGHT |
| * OWNER 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 "third_party/blink/renderer/bindings/core/v8/script_promise.h" |
| |
| #include "base/macros.h" |
| #include "third_party/blink/renderer/bindings/core/v8/script_function.h" |
| #include "third_party/blink/renderer/bindings/core/v8/to_v8_for_core.h" |
| #include "third_party/blink/renderer/core/dom/dom_exception.h" |
| #include "third_party/blink/renderer/platform/bindings/exception_state.h" |
| #include "third_party/blink/renderer/platform/bindings/v8_throw_exception.h" |
| #include "v8/include/v8.h" |
| |
| namespace blink { |
| |
| namespace { |
| |
| class PromiseAllHandler final : public GarbageCollected<PromiseAllHandler> { |
| public: |
| static ScriptPromise All(ScriptState* script_state, |
| const HeapVector<ScriptPromise>& promises) { |
| if (promises.IsEmpty()) |
| return ScriptPromise::Cast(script_state, |
| v8::Array::New(script_state->GetIsolate())); |
| return (MakeGarbageCollected<PromiseAllHandler>(script_state, promises)) |
| ->resolver_.Promise(); |
| } |
| |
| PromiseAllHandler(ScriptState* script_state, |
| HeapVector<ScriptPromise> promises) |
| : number_of_pending_promises_(promises.size()), resolver_(script_state) { |
| DCHECK(!promises.IsEmpty()); |
| values_.resize(promises.size()); |
| for (wtf_size_t i = 0; i < promises.size(); ++i) { |
| promises[i].Then(CreateFulfillFunction(script_state, i), |
| CreateRejectFunction(script_state)); |
| } |
| } |
| |
| virtual void Trace(Visitor* visitor) const { |
| visitor->Trace(resolver_); |
| visitor->Trace(values_); |
| } |
| |
| private: |
| class AdapterFunction : public ScriptFunction { |
| public: |
| enum ResolveType { |
| kFulfilled, |
| kRejected, |
| }; |
| |
| static v8::Local<v8::Function> Create(ScriptState* script_state, |
| ResolveType resolve_type, |
| wtf_size_t index, |
| PromiseAllHandler* handler) { |
| AdapterFunction* self = MakeGarbageCollected<AdapterFunction>( |
| script_state, resolve_type, index, handler); |
| return self->BindToV8Function(); |
| } |
| |
| AdapterFunction(ScriptState* script_state, |
| ResolveType resolve_type, |
| wtf_size_t index, |
| PromiseAllHandler* handler) |
| : ScriptFunction(script_state), |
| resolve_type_(resolve_type), |
| index_(index), |
| handler_(handler) {} |
| |
| void Trace(Visitor* visitor) const override { |
| visitor->Trace(handler_); |
| ScriptFunction::Trace(visitor); |
| } |
| |
| private: |
| ScriptValue Call(ScriptValue value) override { |
| if (resolve_type_ == kFulfilled) |
| handler_->OnFulfilled(index_, value); |
| else |
| handler_->OnRejected(value); |
| // This return value is never used. |
| return ScriptValue(); |
| } |
| |
| const ResolveType resolve_type_; |
| const wtf_size_t index_; |
| Member<PromiseAllHandler> handler_; |
| }; |
| |
| v8::Local<v8::Function> CreateFulfillFunction(ScriptState* script_state, |
| wtf_size_t index) { |
| return AdapterFunction::Create(script_state, AdapterFunction::kFulfilled, |
| index, this); |
| } |
| |
| v8::Local<v8::Function> CreateRejectFunction(ScriptState* script_state) { |
| return AdapterFunction::Create(script_state, AdapterFunction::kRejected, 0, |
| this); |
| } |
| |
| void OnFulfilled(wtf_size_t index, const ScriptValue& value) { |
| if (is_settled_) |
| return; |
| |
| DCHECK_LT(index, values_.size()); |
| values_[index] = value; |
| if (--number_of_pending_promises_ > 0) |
| return; |
| |
| v8::Local<v8::Value> values = ToV8(values_, resolver_.GetScriptState()); |
| MarkPromiseSettled(); |
| resolver_.Resolve(values); |
| } |
| |
| void OnRejected(const ScriptValue& value) { |
| if (is_settled_) |
| return; |
| MarkPromiseSettled(); |
| resolver_.Reject(value.V8Value()); |
| } |
| |
| void MarkPromiseSettled() { |
| DCHECK(!is_settled_); |
| is_settled_ = true; |
| values_.clear(); |
| } |
| |
| size_t number_of_pending_promises_; |
| ScriptPromise::InternalResolver resolver_; |
| bool is_settled_ = false; |
| |
| // This is cleared when owners of this handler, that is, given promises are |
| // settled. |
| HeapVector<ScriptValue> values_; |
| |
| DISALLOW_COPY_AND_ASSIGN(PromiseAllHandler); |
| }; |
| |
| } // namespace |
| |
| ScriptPromise::InternalResolver::InternalResolver(ScriptState* script_state) |
| : script_state_(script_state), |
| resolver_(script_state->GetIsolate(), |
| v8::Promise::Resolver::New(script_state->GetContext())) { |
| // |resolver| can be empty when the thread is being terminated. We ignore such |
| // errors. |
| } |
| |
| v8::Local<v8::Promise> ScriptPromise::InternalResolver::V8Promise() const { |
| if (resolver_.IsEmpty()) |
| return v8::Local<v8::Promise>(); |
| return resolver_.V8Value().As<v8::Promise::Resolver>()->GetPromise(); |
| } |
| |
| ScriptPromise ScriptPromise::InternalResolver::Promise() const { |
| if (resolver_.IsEmpty()) |
| return ScriptPromise(); |
| return ScriptPromise(script_state_, V8Promise()); |
| } |
| |
| void ScriptPromise::InternalResolver::Resolve(v8::Local<v8::Value> value) { |
| if (resolver_.IsEmpty()) |
| return; |
| v8::Maybe<bool> result = |
| resolver_.V8Value().As<v8::Promise::Resolver>()->Resolve( |
| script_state_->GetContext(), value); |
| // |result| can be empty when the thread is being terminated. We ignore such |
| // errors. |
| ALLOW_UNUSED_LOCAL(result); |
| |
| Clear(); |
| } |
| |
| void ScriptPromise::InternalResolver::Reject(v8::Local<v8::Value> value) { |
| if (resolver_.IsEmpty()) |
| return; |
| v8::Maybe<bool> result = |
| resolver_.V8Value().As<v8::Promise::Resolver>()->Reject( |
| script_state_->GetContext(), value); |
| // |result| can be empty when the thread is being terminated. We ignore such |
| // errors. |
| ALLOW_UNUSED_LOCAL(result); |
| |
| Clear(); |
| } |
| |
| ScriptPromise::ScriptPromise(ScriptState* script_state, |
| v8::Local<v8::Value> value) |
| : script_state_(script_state) { |
| if (value.IsEmpty()) |
| return; |
| |
| if (!value->IsPromise()) { |
| promise_ = ScriptValue(); |
| V8ThrowException::ThrowTypeError(script_state->GetIsolate(), |
| "the given value is not a Promise"); |
| return; |
| } |
| promise_ = ScriptValue(script_state->GetIsolate(), value); |
| } |
| |
| ScriptPromise::ScriptPromise(const ScriptPromise& other) { |
| this->script_state_ = other.script_state_; |
| this->promise_ = other.promise_; |
| } |
| |
| ScriptPromise ScriptPromise::Then(v8::Local<v8::Function> on_fulfilled, |
| v8::Local<v8::Function> on_rejected) { |
| if (promise_.IsEmpty()) |
| return ScriptPromise(); |
| |
| v8::Local<v8::Promise> promise = promise_.V8Value().As<v8::Promise>(); |
| |
| if (on_fulfilled.IsEmpty() && on_rejected.IsEmpty()) |
| return *this; |
| |
| v8::Local<v8::Promise> result_promise; |
| if (on_rejected.IsEmpty()) { |
| if (!promise->Then(script_state_->GetContext(), on_fulfilled) |
| .ToLocal(&result_promise)) { |
| return ScriptPromise(); |
| } |
| return ScriptPromise(script_state_, result_promise); |
| } |
| |
| if (on_fulfilled.IsEmpty()) { |
| if (!promise->Catch(script_state_->GetContext(), on_rejected) |
| .ToLocal(&result_promise)) { |
| return ScriptPromise(); |
| } |
| return ScriptPromise(script_state_, result_promise); |
| } |
| |
| if (!promise->Then(script_state_->GetContext(), on_fulfilled, on_rejected) |
| .ToLocal(&result_promise)) { |
| return ScriptPromise(); |
| } |
| return ScriptPromise(script_state_, result_promise); |
| } |
| |
| ScriptPromise ScriptPromise::Then(NewScriptFunction* on_fulfilled, |
| NewScriptFunction* on_rejected) { |
| const v8::Local<v8::Function> empty; |
| return Then(on_fulfilled ? on_fulfilled->V8Function() : empty, |
| on_rejected ? on_rejected->V8Function() : empty); |
| } |
| |
| ScriptPromise ScriptPromise::CastUndefined(ScriptState* script_state) { |
| return ScriptPromise::Cast(script_state, |
| v8::Undefined(script_state->GetIsolate())); |
| } |
| |
| ScriptPromise ScriptPromise::Cast(ScriptState* script_state, |
| const ScriptValue& value) { |
| return ScriptPromise::Cast(script_state, value.V8Value()); |
| } |
| |
| ScriptPromise ScriptPromise::Cast(ScriptState* script_state, |
| v8::Local<v8::Value> value) { |
| if (value.IsEmpty()) |
| return ScriptPromise(); |
| if (value->IsPromise()) { |
| return ScriptPromise(script_state, value); |
| } |
| InternalResolver resolver(script_state); |
| ScriptPromise promise = resolver.Promise(); |
| resolver.Resolve(value); |
| return promise; |
| } |
| |
| ScriptPromise ScriptPromise::Reject(ScriptState* script_state, |
| const ScriptValue& value) { |
| return ScriptPromise::Reject(script_state, value.V8Value()); |
| } |
| |
| ScriptPromise ScriptPromise::Reject(ScriptState* script_state, |
| v8::Local<v8::Value> value) { |
| if (value.IsEmpty()) |
| return ScriptPromise(); |
| InternalResolver resolver(script_state); |
| ScriptPromise promise = resolver.Promise(); |
| resolver.Reject(value); |
| return promise; |
| } |
| |
| ScriptPromise ScriptPromise::Reject(ScriptState* script_state, |
| ExceptionState& exception_state) { |
| DCHECK(exception_state.HadException()); |
| ScriptPromise promise = Reject(script_state, exception_state.GetException()); |
| exception_state.ClearException(); |
| return promise; |
| } |
| |
| ScriptPromise ScriptPromise::RejectWithDOMException(ScriptState* script_state, |
| DOMException* exception) { |
| DCHECK(script_state->GetIsolate()->InContext()); |
| return Reject(script_state, |
| ToV8(exception, script_state->GetContext()->Global(), |
| script_state->GetIsolate())); |
| } |
| |
| v8::Local<v8::Promise> ScriptPromise::RejectRaw(ScriptState* script_state, |
| v8::Local<v8::Value> value) { |
| if (value.IsEmpty()) |
| return v8::Local<v8::Promise>(); |
| v8::Local<v8::Promise::Resolver> resolver; |
| if (!v8::Promise::Resolver::New(script_state->GetContext()) |
| .ToLocal(&resolver)) |
| return v8::Local<v8::Promise>(); |
| v8::Local<v8::Promise> promise = resolver->GetPromise(); |
| resolver->Reject(script_state->GetContext(), value).ToChecked(); |
| return promise; |
| } |
| |
| void ScriptPromise::MarkAsHandled() { |
| if (promise_.IsEmpty()) |
| return; |
| promise_.V8Value().As<v8::Promise>()->MarkAsHandled(); |
| } |
| |
| ScriptPromise ScriptPromise::All(ScriptState* script_state, |
| const HeapVector<ScriptPromise>& promises) { |
| return PromiseAllHandler::All(script_state, promises); |
| } |
| |
| } // namespace blink |