Promise-inl.h (13423B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this file, 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #ifndef mozilla_dom_Promise_inl_h 8 #define mozilla_dom_Promise_inl_h 9 10 #include <type_traits> 11 #include <utility> 12 13 #include "mozilla/AlreadyAddRefed.h" 14 #include "mozilla/dom/BindingUtils.h" 15 #include "mozilla/dom/Promise.h" 16 #include "mozilla/dom/PromiseNativeHandler.h" 17 #include "nsCycleCollectionParticipant.h" 18 19 namespace mozilla::dom { 20 21 class PromiseNativeThenHandlerBase : public PromiseNativeHandler { 22 NS_DECL_CYCLE_COLLECTING_ISUPPORTS 23 NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PromiseNativeThenHandlerBase) 24 25 PromiseNativeThenHandlerBase(Promise* aPromise) : mPromise(aPromise) {} 26 27 virtual bool HasResolvedCallback() = 0; 28 virtual bool HasRejectedCallback() = 0; 29 30 MOZ_CAN_RUN_SCRIPT void ResolvedCallback(JSContext* aCx, 31 JS::Handle<JS::Value> aValue, 32 ErrorResult& aRv) override; 33 34 MOZ_CAN_RUN_SCRIPT void RejectedCallback(JSContext* aCx, 35 JS::Handle<JS::Value> aValue, 36 ErrorResult& aRv) override; 37 38 protected: 39 virtual ~PromiseNativeThenHandlerBase() = default; 40 41 MOZ_CAN_RUN_SCRIPT virtual already_AddRefed<Promise> CallResolveCallback( 42 JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) = 0; 43 MOZ_CAN_RUN_SCRIPT virtual already_AddRefed<Promise> CallRejectCallback( 44 JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) = 0; 45 46 virtual void Traverse(nsCycleCollectionTraversalCallback&) = 0; 47 virtual void Unlink() = 0; 48 virtual void Trace(const TraceCallbacks& aCallbacks, void* aClosure) = 0; 49 50 RefPtr<Promise> mPromise; 51 }; 52 53 namespace { 54 55 template <typename T, bool = IsRefcounted<std::remove_pointer_t<T>>::value, 56 bool = (std::is_convertible_v<T, nsISupports*> || 57 std::is_convertible_v<T*, nsISupports*>)> 58 struct StorageTypeHelper { 59 using Type = T; 60 }; 61 62 template <typename T> 63 struct StorageTypeHelper<T, true, true> { 64 using Type = nsCOMPtr<T>; 65 }; 66 67 template <typename T> 68 struct StorageTypeHelper<nsCOMPtr<T>, true, true> { 69 using Type = nsCOMPtr<T>; 70 }; 71 72 template <typename T> 73 struct StorageTypeHelper<T*, true, false> { 74 using Type = RefPtr<T>; 75 }; 76 77 template <typename T> 78 struct StorageTypeHelper<JS::Handle<T>, false, false> { 79 using Type = JS::Heap<T>; 80 }; 81 82 template <template <typename> class SmartPtr, typename T> 83 struct StorageTypeHelper<SmartPtr<T>, true, false> 84 : std::enable_if<std::is_convertible_v<SmartPtr<T>, T*>, RefPtr<T>> { 85 using Type = typename StorageTypeHelper::enable_if::type; 86 }; 87 88 template <typename T> 89 using StorageType = typename StorageTypeHelper<std::decay_t<T>>::Type; 90 91 // Helpers to choose the correct argument type based on the storage type. Smart 92 // pointers are converted to the corresponding raw pointer type. Everything else 93 // is passed by move reference. 94 // 95 // Note: We can't just use std::forward for this because the input type may be a 96 // raw pointer which does not match the argument type, and while the 97 // spec-compliant behavior there should still give us the expected results, MSVC 98 // considers it an illegal use of std::forward. 99 template <template <typename> class SmartPtr, typename T> 100 decltype(std::declval<SmartPtr<T>>().get()) ArgType(SmartPtr<T>& aVal) { 101 return aVal.get(); 102 } 103 104 template <typename T> 105 T&& ArgType(T& aVal) { 106 return std::move(aVal); 107 } 108 109 using ::ImplCycleCollectionUnlink; 110 111 template <typename ResolveCallback, typename RejectCallback, typename ArgsTuple, 112 typename JSArgsTuple> 113 class NativeThenHandler; 114 115 template <typename ResolveCallback, typename RejectCallback, typename... Args, 116 typename... JSArgs> 117 class NativeThenHandler<ResolveCallback, RejectCallback, std::tuple<Args...>, 118 std::tuple<JSArgs...>> 119 final : public PromiseNativeThenHandlerBase { 120 public: 121 /** 122 * @param aPromise A promise that will be settled by the result of the 123 * callbacks. Any thrown value to ErrorResult passed to those callbacks will 124 * be used to reject the promise, otherwise the promise will be resolved with 125 * the return value. 126 * @param aOnResolve A resolve callback 127 * @param aOnReject A reject callback 128 * @param aArgs The custom arguments to be passed to the both callbacks. The 129 * handler class will grab them to make them live long enough and to allow 130 * cycle collection. 131 * @param aJSArgs The JS arguments to be passed to the both callbacks, after 132 * native arguments. The handler will also grab them and allow garbage 133 * collection. 134 * 135 * XXX(krosylight): ideally there should be two signatures, with or without a 136 * promise parameter. Unfortunately doing so confuses the compiler and errors 137 * out, because nothing prevents promise from being ResolveCallback. 138 */ 139 NativeThenHandler(Promise* aPromise, Maybe<ResolveCallback>&& aOnResolve, 140 Maybe<RejectCallback>&& aOnReject, 141 std::tuple<std::remove_reference_t<Args>...>&& aArgs, 142 std::tuple<std::remove_reference_t<JSArgs>...>&& aJSArgs) 143 : PromiseNativeThenHandlerBase(aPromise), 144 mOnResolve(std::forward<Maybe<ResolveCallback>>(aOnResolve)), 145 mOnReject(std::forward<Maybe<RejectCallback>>(aOnReject)), 146 mArgs(std::forward<decltype(aArgs)>(aArgs)), 147 mJSArgs(std::forward<decltype(aJSArgs)>(aJSArgs)) { 148 if constexpr (std::tuple_size<decltype(mJSArgs)>::value > 0) { 149 mozilla::HoldJSObjects(this); 150 } 151 } 152 153 protected: 154 ~NativeThenHandler() override { 155 if constexpr (std::tuple_size<decltype(mJSArgs)>::value > 0) { 156 mozilla::DropJSObjects(this); 157 } 158 } 159 160 void Traverse(nsCycleCollectionTraversalCallback& cb) override { 161 auto* tmp = this; 162 NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mArgs) 163 } 164 165 void Unlink() override { 166 auto* tmp = this; 167 NS_IMPL_CYCLE_COLLECTION_UNLINK(mArgs) 168 NS_IMPL_CYCLE_COLLECTION_UNLINK(mJSArgs) 169 } 170 171 void Trace(const TraceCallbacks& aCallbacks, void* aClosure) override { 172 std::apply( 173 [&aCallbacks, aClosure](auto&&... aArgs) { 174 (aCallbacks.Trace(&aArgs, "mJSArgs[]", aClosure), ...); 175 }, 176 mJSArgs); 177 } 178 179 bool HasResolvedCallback() override { return mOnResolve.isSome(); } 180 bool HasRejectedCallback() override { return mOnReject.isSome(); } 181 182 MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> CallResolveCallback( 183 JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) override { 184 return CallCallback(aCx, *mOnResolve, aValue, aRv); 185 } 186 MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> CallRejectCallback( 187 JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv) override { 188 return CallCallback(aCx, *mOnReject, aValue, aRv); 189 } 190 191 // mJSArgs are marked with Trace() above, so they can be safely converted to 192 // Handles. But we should not circumvent the read barrier, so call 193 // exposeToActiveJS explicitly. 194 template <typename T> 195 static JS::Handle<T> GetJSArgHandleForCall(JS::Heap<T>& aArg) { 196 aArg.exposeToActiveJS(); 197 return JS::Handle<T>::fromMarkedLocation(aArg.unsafeAddress()); 198 } 199 200 template <typename TCallback, size_t... Indices, size_t... JSIndices> 201 MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> CallCallback( 202 JSContext* aCx, const TCallback& aHandler, JS::Handle<JS::Value> aValue, 203 ErrorResult& aRv, std::index_sequence<Indices...>, 204 std::index_sequence<JSIndices...>) { 205 return aHandler(aCx, aValue, aRv, ArgType(std::get<Indices>(mArgs))..., 206 GetJSArgHandleForCall(std::get<JSIndices>(mJSArgs))...); 207 } 208 209 template <typename TCallback> 210 MOZ_CAN_RUN_SCRIPT already_AddRefed<Promise> CallCallback( 211 JSContext* aCx, const TCallback& aHandler, JS::Handle<JS::Value> aValue, 212 ErrorResult& aRv) { 213 return CallCallback(aCx, aHandler, aValue, aRv, 214 std::index_sequence_for<Args...>{}, 215 std::index_sequence_for<JSArgs...>{}); 216 } 217 218 Maybe<ResolveCallback> mOnResolve; 219 Maybe<RejectCallback> mOnReject; 220 221 std::tuple<StorageType<Args>...> mArgs; 222 std::tuple<StorageType<JSArgs>...> mJSArgs; 223 }; 224 225 } // anonymous namespace 226 227 template <typename ResolveCallback, typename RejectCallback, typename... Args, 228 typename... JSArgs> 229 Result<RefPtr<Promise>, nsresult> 230 Promise::ThenCatchWithCycleCollectedArgsJSImpl( 231 Maybe<ResolveCallback>&& aOnResolve, Maybe<RejectCallback>&& aOnReject, 232 std::tuple<Args...>&& aArgs, std::tuple<JSArgs...>&& aJSArgs) { 233 using HandlerType = 234 NativeThenHandler<ResolveCallback, RejectCallback, std::tuple<Args...>, 235 std::tuple<JSArgs...>>; 236 237 ErrorResult rv; 238 RefPtr<Promise> promise = Promise::Create(GetParentObject(), rv); 239 if (rv.Failed()) { 240 return Err(rv.StealNSResult()); 241 } 242 243 auto* handler = new (fallible) 244 HandlerType(promise, std::forward<Maybe<ResolveCallback>>(aOnResolve), 245 std::forward<Maybe<RejectCallback>>(aOnReject), 246 std::forward<std::tuple<Args...>>(aArgs), 247 std::forward<std::tuple<JSArgs...>>(aJSArgs)); 248 249 if (!handler) { 250 return Err(NS_ERROR_OUT_OF_MEMORY); 251 } 252 253 AppendNativeHandler(handler); 254 return std::move(promise); 255 } 256 257 template <typename ResolveCallback, typename RejectCallback, typename... Args> 258 Promise::ThenResult<ResolveCallback, Args...> 259 Promise::ThenCatchWithCycleCollectedArgsImpl( 260 Maybe<ResolveCallback>&& aOnResolve, Maybe<RejectCallback>&& aOnReject, 261 Args&&... aArgs) { 262 return ThenCatchWithCycleCollectedArgsJSImpl( 263 std::forward<Maybe<ResolveCallback>>(aOnResolve), 264 std::forward<Maybe<RejectCallback>>(aOnReject), 265 std::make_tuple(std::forward<Args>(aArgs)...), std::make_tuple()); 266 } 267 268 template <typename ResolveCallback, typename RejectCallback, typename... Args> 269 Promise::ThenResult<ResolveCallback, Args...> 270 Promise::ThenCatchWithCycleCollectedArgs(ResolveCallback&& aOnResolve, 271 RejectCallback&& aOnReject, 272 Args&&... aArgs) { 273 return ThenCatchWithCycleCollectedArgsImpl(Some(aOnResolve), Some(aOnReject), 274 std::forward<Args>(aArgs)...); 275 } 276 277 template <typename Callback, typename... Args> 278 Promise::ThenResult<Callback, Args...> Promise::ThenWithCycleCollectedArgs( 279 Callback&& aOnResolve, Args&&... aArgs) { 280 return ThenCatchWithCycleCollectedArgsImpl(Some(aOnResolve), 281 Maybe<Callback>(Nothing()), 282 std::forward<Args>(aArgs)...); 283 } 284 285 template <typename Callback, typename... Args> 286 Promise::ThenResult<Callback, Args...> Promise::CatchWithCycleCollectedArgs( 287 Callback&& aOnReject, Args&&... aArgs) { 288 return ThenCatchWithCycleCollectedArgsImpl(Maybe<Callback>(Nothing()), 289 Some(aOnReject), 290 std::forward<Args>(aArgs)...); 291 } 292 293 template <typename ResolveCallback, typename RejectCallback, typename ArgsTuple, 294 typename JSArgsTuple> 295 Result<RefPtr<Promise>, nsresult> Promise::ThenCatchWithCycleCollectedArgsJS( 296 ResolveCallback&& aOnResolve, RejectCallback&& aOnReject, ArgsTuple&& aArgs, 297 JSArgsTuple&& aJSArgs) { 298 return ThenCatchWithCycleCollectedArgsJSImpl( 299 Some(aOnResolve), Some(aOnReject), std::forward<ArgsTuple>(aArgs), 300 std::forward<JSArgsTuple>(aJSArgs)); 301 } 302 303 template <typename Callback, typename ArgsTuple, typename JSArgsTuple> 304 Result<RefPtr<Promise>, nsresult> Promise::ThenWithCycleCollectedArgsJS( 305 Callback&& aOnResolve, ArgsTuple&& aArgs, JSArgsTuple&& aJSArgs) { 306 return ThenCatchWithCycleCollectedArgsJSImpl( 307 Some(aOnResolve), Maybe<Callback>(Nothing()), 308 std::forward<ArgsTuple>(aArgs), std::forward<JSArgsTuple>(aJSArgs)); 309 } 310 311 template <typename ResolveCallback, typename RejectCallback, typename... Args> 312 void Promise::AddCallbacksWithCycleCollectedArgs(ResolveCallback&& aOnResolve, 313 RejectCallback&& aOnReject, 314 Args&&... aArgs) { 315 auto onResolve = 316 [aOnResolve](JSContext* aCx, JS::Handle<JS::Value> value, 317 ErrorResult& aRv, 318 StorageType<Args>&&... aArgs) -> already_AddRefed<Promise> { 319 aOnResolve(aCx, value, aRv, aArgs...); 320 return nullptr; 321 }; 322 auto onReject = 323 [aOnReject](JSContext* aCx, JS::Handle<JS::Value> value, ErrorResult& aRv, 324 StorageType<Args>&&... aArgs) -> already_AddRefed<Promise> { 325 aOnReject(aCx, value, aRv, aArgs...); 326 return nullptr; 327 }; 328 329 // Note: explicit template parameters for clang<7/gcc<8 without "Template 330 // argument deduction for class templates" support 331 AppendNativeHandler( 332 new NativeThenHandler<decltype(onResolve), decltype(onReject), 333 std::tuple<Args...>, std::tuple<>>( 334 nullptr, Some(onResolve), Some(onReject), 335 std::make_tuple(std::forward<Args>(aArgs)...), std::make_tuple())); 336 } 337 338 } // namespace mozilla::dom 339 340 #endif // mozilla_dom_Promise_inl_h