WaitFor.h (9618B)
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=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 5 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ 6 7 #ifndef TESTING_GTEST_MOZILLA_WAITFOR_H_ 8 #define TESTING_GTEST_MOZILLA_WAITFOR_H_ 9 10 #include "MediaEventSource.h" 11 #include "mozilla/media/MediaUtils.h" 12 #include "mozilla/Maybe.h" 13 #include "mozilla/MozPromise.h" 14 #include "mozilla/SpinEventLoopUntil.h" 15 16 namespace mozilla { 17 18 /** 19 * Waits for an occurrence of aEvent on the current thread (by blocking it, 20 * except tasks added to the event loop may run) and returns the event's 21 * templated value, if it's non-void. 22 * 23 * The caller must be wary of eventloop issues, in 24 * particular cases where we rely on a stable state runnable, but there is never 25 * a task to trigger stable state. In such cases it is the responsibility of the 26 * caller to create the needed tasks, as JS would. A noteworthy API that relies 27 * on stable state is MediaTrackGraph::GetInstance. 28 */ 29 template <ListenerPolicy Lp, typename First, typename... Rest> 30 inline auto WaitFor(MediaEventSourceImpl<Lp, First, Rest...>& aEvent) { 31 constexpr size_t num_params = 1 + sizeof...(Rest); 32 using Storage = 33 std::conditional_t<num_params == 1, First, std::tuple<First, Rest...>>; 34 Maybe<Storage> value; 35 if constexpr (Lp == ListenerPolicy::NonExclusive) { 36 MediaEventListener listener = 37 aEvent.Connect(AbstractThread::GetCurrent(), 38 [&value](const First& aFirst, const Rest&... aRest) { 39 if constexpr (num_params == 1) { 40 value = Some(aFirst); 41 } else { 42 value = Some<Storage>({aFirst, aRest...}); 43 } 44 }); 45 SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>( 46 "WaitFor(MediaEventSource<T>& aEvent)"_ns, 47 [&] { return value.isSome(); }); 48 listener.Disconnect(); 49 return value.value(); 50 } else { 51 MediaEventListener listener = aEvent.Connect( 52 AbstractThread::GetCurrent(), 53 [&value](First&& aFirst, Rest&&... aRest) { 54 if constexpr (num_params == 1) { 55 value = Some<Storage>(std::forward<First>(aFirst)); 56 } else { 57 value = Some<Storage>( 58 {std::forward<First>(aFirst), std::forward<Rest...>(aRest...)}); 59 } 60 }); 61 SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>( 62 "WaitFor(MediaEventSource<T>& aEvent)"_ns, 63 [&] { return value.isSome(); }); 64 listener.Disconnect(); 65 return value.value(); 66 } 67 } 68 69 /** 70 * Specialization of WaitFor<T> for void. 71 */ 72 template <ListenerPolicy Lp> 73 inline void WaitFor(MediaEventSourceImpl<Lp, void>& aEvent) { 74 bool done = false; 75 MediaEventListener listener = 76 aEvent.Connect(AbstractThread::GetCurrent(), [&] { done = true; }); 77 SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>( 78 "WaitFor(MediaEventSource<void>& aEvent)"_ns, [&] { return done; }); 79 listener.Disconnect(); 80 } 81 82 /** 83 * Variant of WaitFor that spins the event loop until a MozPromise has either 84 * been resolved or rejected. Result accepts R and E only if their types 85 * differ. Consider also WaitForResolve() and WaitForReject(), which are 86 * suitable even when resolve and reject types are the same. 87 */ 88 template <typename R, typename E, bool Exc> 89 inline Result<R, E> WaitFor(const RefPtr<MozPromise<R, E, Exc>>& aPromise) { 90 Maybe<R> success; 91 Maybe<E> error; 92 // Use r-value reference for exclusive promises to support move-only types. 93 using RRef = typename std::conditional_t<Exc, R&&, const R&>; 94 using ERef = typename std::conditional_t<Exc, E&&, const E&>; 95 aPromise->Then( 96 GetCurrentSerialEventTarget(), __func__, 97 [&](RRef aResult) { success.emplace(std::forward<RRef>(aResult)); }, 98 [&](ERef aError) { error.emplace(std::forward<ERef>(aError)); }); 99 SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>( 100 "WaitFor(const RefPtr<MozPromise<R, E, Exc>>& aPromise)"_ns, 101 [&] { return success.isSome() || error.isSome(); }); 102 if (success.isSome()) { 103 return success.extract(); 104 } 105 return Err(error.extract()); 106 } 107 108 /** 109 * Variation on WaitFor that spins the event loop until a MozPromise has been 110 * resolved. 111 */ 112 template <typename R, typename E, bool Exc> 113 inline R WaitForResolve(const RefPtr<MozPromise<R, E, Exc>>& aPromise) { 114 Maybe<R> success; 115 // Use r-value reference for exclusive promises to support move-only types. 116 using RRef = typename std::conditional_t<Exc, R&&, const R&>; 117 using ERef = typename std::conditional_t<Exc, E&&, const E&>; 118 aPromise->Then( 119 GetCurrentSerialEventTarget(), __func__, 120 [&](RRef aResult) { success.emplace(std::forward<RRef>(aResult)); }, 121 [&](ERef aError) { MOZ_CRASH("rejection was not expected"); }); 122 SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>( 123 "WaitForResolve(const RefPtr<MozPromise<R, E, Exc>>& aPromise)"_ns, 124 [&] { return success.isSome(); }); 125 return success.extract(); 126 } 127 128 /** 129 * Variation on WaitFor that spins the event loop until a MozPromise has been 130 * rejected. 131 */ 132 template <typename R, typename E, bool Exc> 133 inline E WaitForReject(const RefPtr<MozPromise<R, E, Exc>>& aPromise) { 134 Maybe<E> error; 135 // Use r-value reference for exclusive promises to support move-only types. 136 using RRef = typename std::conditional_t<Exc, R&&, const R&>; 137 using ERef = typename std::conditional_t<Exc, E&&, const E&>; 138 aPromise->Then( 139 GetCurrentSerialEventTarget(), __func__, 140 [&](RRef aResult) { MOZ_CRASH("resolution was not expected"); }, 141 [&](ERef aError) { error.emplace(std::forward<ERef>(aError)); }); 142 SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>( 143 "WaitForReject(const RefPtr<MozPromise<R, E, Exc>>& aPromise)"_ns, 144 [&] { return error.isSome(); }); 145 return error.extract(); 146 } 147 148 /** 149 * A variation of WaitFor that takes a callback to be called each time aEvent is 150 * raised. Blocks the caller until the callback function returns true. 151 */ 152 template <ListenerPolicy Lp, typename... Args, typename CallbackFunction> 153 inline void WaitUntil(MediaEventSourceImpl<Lp, Args...>& aEvent, 154 CallbackFunction&& aF) { 155 bool done = false; 156 MediaEventListener listener = 157 aEvent.Connect(AbstractThread::GetCurrent(), [&](Args... aValue) { 158 if (!done) { 159 done = aF(std::forward<Args>(aValue)...); 160 } 161 }); 162 SpinEventLoopUntil<ProcessFailureBehavior::IgnoreAndContinue>( 163 "WaitUntil(MediaEventSource<Args...>& aEvent, CallbackFunction&& aF)"_ns, 164 [&] { return done; }); 165 listener.Disconnect(); 166 } 167 168 template <typename... Args> 169 using TakeNPromise = MozPromise<std::vector<std::tuple<Args...>>, bool, true>; 170 171 template <ListenerPolicy Lp, typename... Args> 172 inline auto TakeN(MediaEventSourceImpl<Lp, Args...>& aEvent, size_t aN) 173 -> RefPtr<TakeNPromise<Args...>> { 174 using Storage = std::vector<std::tuple<Args...>>; 175 using Promise = TakeNPromise<Args...>; 176 using Holder = media::Refcountable<MozPromiseHolder<Promise>>; 177 using Values = media::Refcountable<Storage>; 178 using Listener = media::Refcountable<MediaEventListener>; 179 auto values = MakeRefPtr<Values>(); 180 values->reserve(aN); 181 auto listener = MakeRefPtr<Listener>(); 182 auto holder = MakeRefPtr<Holder>(); 183 *listener = aEvent.Connect(AbstractThread::GetCurrent(), 184 [values, listener, aN, holder](Args... aValue) { 185 values->push_back({aValue...}); 186 if (values->size() == aN) { 187 listener->Disconnect(); 188 holder->Resolve(std::move(*values), 189 "TakeN listener callback"); 190 } 191 }); 192 return holder->Ensure(__func__); 193 } 194 195 using TakeNVoidPromise = MozPromise<size_t, bool, true>; 196 197 template <ListenerPolicy Lp> 198 inline auto TakeN(MediaEventSourceImpl<Lp, void>& aEvent, size_t aN) 199 -> RefPtr<TakeNVoidPromise> { 200 using Storage = Maybe<size_t>; 201 using Promise = TakeNVoidPromise; 202 using Holder = media::Refcountable<MozPromiseHolder<Promise>>; 203 using Values = media::Refcountable<Storage>; 204 using Listener = media::Refcountable<MediaEventListener>; 205 auto values = MakeRefPtr<Values>(); 206 *values = Some(0); 207 auto listener = MakeRefPtr<Listener>(); 208 auto holder = MakeRefPtr<Holder>(); 209 *listener = aEvent.Connect( 210 AbstractThread::GetCurrent(), [values, listener, aN, holder]() { 211 if (++(values->ref()) == aN) { 212 listener->Disconnect(); 213 holder->Resolve(**values, "TakeN (void) listener callback"); 214 } 215 }); 216 return holder->Ensure(__func__); 217 } 218 219 /** 220 * Helper that, given that canonicals have just been updated on the current 221 * thread, will block its execution until mirrors and their watchers have 222 * executed on aTarget. 223 */ 224 inline void WaitForMirrors(const RefPtr<nsISerialEventTarget>& aTarget) { 225 (void)WaitFor(InvokeAsync(aTarget, __func__, [] { 226 return GenericPromise::CreateAndResolve(true, "WaitForMirrors resolver"); 227 })); 228 } 229 230 /** 231 * Short form of WaitForMirrors that assumes mirrors are on the current thread 232 * (like canonicals). 233 */ 234 inline void WaitForMirrors() { WaitForMirrors(GetCurrentSerialEventTarget()); } 235 236 } // namespace mozilla 237 238 #endif // TESTING_GTEST_MOZILLA_WAITFOR_H_