// Copyright (c) 2018 GitHub, Inc. // Use of this source code is governed by the MIT license that can be // found in the LICENSE file. #ifndef ELECTRON_SHELL_COMMON_GIN_HELPER_PROMISE_H_ #define ELECTRON_SHELL_COMMON_GIN_HELPER_PROMISE_H_ #include #include #include #include #include "base/memory/raw_ptr.h" #include "base/strings/string_piece.h" #include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_thread.h" #include "shell/common/gin_converters/std_converter.h" #include "shell/common/gin_helper/locker.h" #include "shell/common/gin_helper/microtasks_scope.h" #include "shell/common/process_util.h" namespace gin_helper { // A wrapper around the v8::Promise. // // This is the non-template base class to share code between templates // instances. // // This is a move-only type that should always be `std::move`d when passed to // callbacks, and it should be destroyed on the same thread of creation. class PromiseBase { public: explicit PromiseBase(v8::Isolate* isolate); PromiseBase(v8::Isolate* isolate, v8::Local handle); PromiseBase(); ~PromiseBase(); // disable copy PromiseBase(const PromiseBase&) = delete; PromiseBase& operator=(const PromiseBase&) = delete; // Support moving. PromiseBase(PromiseBase&&); PromiseBase& operator=(PromiseBase&&); // Helper for rejecting promise with error message. // // Note: The parameter type is PromiseBase&& so it can take the instances of // Promise type. static void RejectPromise(PromiseBase&& promise, base::StringPiece errmsg) { if (electron::IsBrowserProcess() && !content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) { content::GetUIThreadTaskRunner({})->PostTask( FROM_HERE, base::BindOnce( // Note that this callback can not take StringPiece, // as StringPiece only references string internally and // will blow when a temporary string is passed. [](PromiseBase&& promise, std::string str) { promise.RejectWithErrorMessage(str); }, std::move(promise), std::string(errmsg.data(), errmsg.size()))); } else { promise.RejectWithErrorMessage(errmsg); } } v8::Maybe Reject(); v8::Maybe Reject(v8::Local except); v8::Maybe RejectWithErrorMessage(base::StringPiece message); v8::Local GetContext() const; v8::Local GetHandle() const; v8::Isolate* isolate() const { return isolate_; } protected: v8::Local GetInner() const; private: raw_ptr isolate_; v8::Global context_; v8::Global resolver_; }; // Template implementation that returns values. template class Promise : public PromiseBase { public: using PromiseBase::PromiseBase; // Helper for resolving the promise with |result|. static void ResolvePromise(Promise promise, RT result) { if (electron::IsBrowserProcess() && !content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) { content::GetUIThreadTaskRunner({})->PostTask( FROM_HERE, base::BindOnce([](Promise promise, RT result) { promise.Resolve(result); }, std::move(promise), std::move(result))); } else { promise.Resolve(result); } } // Returns an already-resolved promise. static v8::Local ResolvedPromise(v8::Isolate* isolate, RT result) { Promise resolved(isolate); resolved.Resolve(result); return resolved.GetHandle(); } // Convert to another type. template Promise As() { return Promise(isolate(), GetInner()); } // Promise resolution is a microtask // We use the MicrotasksRunner to trigger the running of pending microtasks v8::Maybe Resolve(const RT& value) { gin_helper::Locker locker(isolate()); v8::HandleScope handle_scope(isolate()); gin_helper::MicrotasksScope microtasks_scope( isolate(), GetContext()->GetMicrotaskQueue()); v8::Context::Scope context_scope(GetContext()); return GetInner()->Resolve(GetContext(), gin::ConvertToV8(isolate(), value)); } template v8::MaybeLocal Then( base::OnceCallback cb) { static_assert(sizeof...(ResolveType) <= 1, "A promise's 'Then' callback should only receive at most one " "parameter"); static_assert( std::is_same>>(), "A promises's 'Then' callback must handle the same type as the " "promises resolve type"); gin_helper::Locker locker(isolate()); v8::HandleScope handle_scope(isolate()); v8::Context::Scope context_scope(GetContext()); v8::Local value = gin::ConvertToV8(isolate(), std::move(cb)); v8::Local handler = value.As(); return GetHandle()->Then(GetContext(), handler); } }; // Template implementation that returns nothing. template <> class Promise : public PromiseBase { public: using PromiseBase::PromiseBase; // Helper for resolving the empty promise. static void ResolvePromise(Promise promise); // Returns an already-resolved promise. static v8::Local ResolvedPromise(v8::Isolate* isolate); v8::Maybe Resolve(); }; } // namespace gin_helper namespace gin { template struct Converter> { static v8::Local ToV8(v8::Isolate* isolate, const gin_helper::Promise& val) { return val.GetHandle(); } // TODO(MarshallOfSound): Implement FromV8 to allow promise chaining // in native land // static bool FromV8(v8::Isolate* isolate, // v8::Local val, // Promise* out); }; } // namespace gin #endif // ELECTRON_SHELL_COMMON_GIN_HELPER_PROMISE_H_