|
6 | 6 |
|
7 | 7 | #include "SpeechRecognitionBackend.h" |
8 | 8 |
|
| 9 | +#include <utility> |
| 10 | + |
9 | 11 | #include "SpeechRecognition.h" |
10 | 12 | #include "SpeechTrackListener.h" |
11 | 13 | #include "mozilla/AbstractThread.h" |
12 | 14 | #include "mozilla/Assertions.h" |
| 15 | +#include "mozilla/ClearOnShutdown.h" |
13 | 16 | #include "mozilla/dom/AudioStreamTrack.h" |
14 | 17 | #include "mozilla/dom/ContentChild.h" |
15 | 18 | #include "mozilla/dom/Promise.h" |
| 19 | +#include "mozilla/dom/SpeechRecognitionBinding.h" |
| 20 | +#include "mozilla/hwinference/PSpeechRecognition.h" |
| 21 | +#include "mozilla/hwinference/SpeechRecognitionChild.h" |
| 22 | +#include "mozilla/ipc/MessageChannel.h" |
| 23 | +#include "mozilla/ipc/ProtocolUtils.h" |
16 | 24 |
|
17 | 25 | namespace mozilla::dom { |
18 | 26 |
|
| 27 | +using namespace mozilla::ipc; |
| 28 | + |
| 29 | +StaticAutoPtr<mozilla::EventTargetCapability<nsISerialEventTarget>> |
| 30 | + SpeechRecognitionBackend::sIPCCapability; |
| 31 | +int32_t SpeechRecognitionBackend::sIPCActorUsers = 0; |
| 32 | + |
19 | 33 | static LazyLogModule gSpeechRecognitionBackendLog("SpeechRecognitionBackend"); |
20 | 34 |
|
21 | | -#define LOG(...) \ |
22 | | - MOZ_LOG_FMT(gSpeechRecognitionBackendLog, LogLevel::Debug, __VA_ARGS__) |
23 | | -#define LOGV(...) \ |
24 | | - MOZ_LOG_FMT(gSpeechRecognitionBackendLog, LogLevel::Verbose, __VA_ARGS__) |
25 | | -#define LOGE(...) \ |
26 | | - MOZ_LOG_FMT(gSpeechRecognitionBackendLog, LogLevel::Error, __VA_ARGS__) |
| 35 | +#define LOG(fmt, ...) \ |
| 36 | + MOZ_LOG_FMT(gSpeechRecognitionBackendLog, mozilla::LogLevel::Debug, fmt, \ |
| 37 | + ##__VA_ARGS__) |
| 38 | +#define LOGV(fmt, ...) \ |
| 39 | + MOZ_LOG_FMT(gSpeechRecognitionBackendLog, mozilla::LogLevel::Verbose, fmt, \ |
| 40 | + ##__VA_ARGS__) |
| 41 | +#define LOGE(fmt, ...) \ |
| 42 | + MOZ_LOG_FMT(gSpeechRecognitionBackendLog, mozilla::LogLevel::Error, fmt, \ |
| 43 | + ##__VA_ARGS__) |
| 44 | + |
| 45 | +// How long the shared IPC thread stays idle before its backing OS thread is |
| 46 | +// released. The serial event target itself lives for the process lifetime. |
| 47 | +static constexpr uint32_t IPC_THREAD_IDLE_TIMEOUT_MS = 5000; |
| 48 | + |
| 49 | +SpeechRecognitionBackend::SpeechRecognitionIPCActorUserGuard:: |
| 50 | + ~SpeechRecognitionIPCActorUserGuard() { |
| 51 | + if (NS_IsMainThread()) { |
| 52 | + SpeechRecognitionBackend::ReleaseIPCActorUser(); |
| 53 | + } else { |
| 54 | + NS_DispatchToMainThread(NS_NewRunnableFunction( |
| 55 | + "SpeechRecognitionIPCActorUserGuard::Release", [] { |
| 56 | + AssertIsOnMainThread(); |
| 57 | + SpeechRecognitionBackend::ReleaseIPCActorUser(); |
| 58 | + })); |
| 59 | + } |
| 60 | +} |
| 61 | + |
| 62 | +/* static */ |
| 63 | +void SpeechRecognitionBackend::AcquireIPCActorUser() { |
| 64 | + AssertIsOnMainThread(); |
| 65 | + ++sIPCActorUsers; |
| 66 | +} |
| 67 | + |
| 68 | +/* static */ |
| 69 | +void SpeechRecognitionBackend::ReleaseIPCActorUser() { |
| 70 | + AssertIsOnMainThread(); |
| 71 | + MOZ_ASSERT(sIPCActorUsers > 0); |
| 72 | + if (--sIPCActorUsers || !sIPCCapability) { |
| 73 | + return; |
| 74 | + } |
| 75 | + |
| 76 | + // Close the HWInference connection once no session needs it, so the utility |
| 77 | + // process isn't kept alive by an idle connection. The serial event target is |
| 78 | + // kept (its backing OS thread is released on idle by the LazyIdleThread); |
| 79 | + // the next EnsureIPC() reopens the connection on that same target. |
| 80 | + nsCOMPtr<nsIRunnable> close = NS_NewRunnableFunction( |
| 81 | + "SpeechRecognitionBackend::CloseHWInferenceChildIfAny", |
| 82 | + [] { CloseHWInferenceChildIfAny(); }); |
| 83 | + sIPCCapability->Dispatch(close.forget()); |
| 84 | +} |
27 | 85 |
|
28 | 86 | SpeechRecognitionBackend::SpeechRecognitionBackend( |
29 | 87 | SpeechRecognition* aParent, uint32_t aGraphRate, const nsString& aLanguage, |
@@ -56,14 +114,221 @@ void SpeechRecognitionBackend::DataCallback(TrackTime aTime, |
56 | 114 |
|
57 | 115 | void SpeechRecognitionBackend::NotifyTrackEnded() {} |
58 | 116 |
|
| 117 | +/* static */ |
| 118 | +void SpeechRecognitionBackend::EnsureIPCThread() { |
| 119 | + AssertIsOnMainThread(); |
| 120 | + |
| 121 | + if (!sIPCCapability) { |
| 122 | + // IPC actors are bound to the event target they were opened on, so the |
| 123 | + // target has to outlive them: a LazyIdleThread keeps a single one for the |
| 124 | + // process lifetime, and only releases its backing OS thread when idle. |
| 125 | + RefPtr<LazyIdleThread> thread = |
| 126 | + new LazyIdleThread(IPC_THREAD_IDLE_TIMEOUT_MS, "SpeechIPC"); |
| 127 | + sIPCCapability = new EventTargetCapability<nsISerialEventTarget>(thread); |
| 128 | + LOG("Created shared IPC thread for speech recognition"); |
| 129 | + ClearOnShutdown(&sIPCCapability); |
| 130 | + } |
| 131 | +} |
| 132 | + |
| 133 | +/* static */ |
| 134 | +void SpeechRecognitionBackend::AssertOnIPCThread() { |
| 135 | + sIPCCapability->AssertOnCurrentThread(); |
| 136 | +} |
| 137 | + |
| 138 | +/* static */ |
| 139 | +template <typename SendFunc> |
| 140 | +auto SpeechRecognitionBackend::RunWithTransientSession(SendFunc&& aSendFunc) { |
| 141 | + AssertIsOnMainThread(); |
| 142 | + |
| 143 | + using SendPromise = typename decltype(aSendFunc( |
| 144 | + std::declval<hwinference::SpeechRecognitionChild*>()))::element_type; |
| 145 | + using ResolveValueType = typename SendPromise::ResolveValueType; |
| 146 | + using OperationPromise = MozPromise<ResolveValueType, nsresult, true>; |
| 147 | + |
| 148 | + MozPromiseHolder<OperationPromise> holder; |
| 149 | + RefPtr<OperationPromise> operation = holder.Ensure(__func__); |
| 150 | + CreateSession([holder = std::move(holder), |
| 151 | + aSendFunc = std::forward<SendFunc>(aSendFunc)]( |
| 152 | + hwinference::SpeechRecognitionChild* aChild) mutable { |
| 153 | + AssertOnIPCThread(); |
| 154 | + if (!aChild) { |
| 155 | + holder.Reject(NS_ERROR_FAILURE, __func__); |
| 156 | + return; |
| 157 | + } |
| 158 | + RefPtr child = aChild; |
| 159 | + aSendFunc(aChild)->Then( |
| 160 | + GetCurrentSerialEventTarget(), __func__, |
| 161 | + [holder = std::move(holder), |
| 162 | + child](typename SendPromise::ResolveOrRejectValue&& aValue) mutable { |
| 163 | + AssertOnIPCThread(); |
| 164 | + child->Close(); |
| 165 | + if (aValue.IsReject()) { |
| 166 | + holder.Reject(NS_ERROR_FAILURE, __func__); |
| 167 | + return; |
| 168 | + } |
| 169 | + holder.Resolve(std::move(aValue.ResolveValue()), __func__); |
| 170 | + }); |
| 171 | + }); |
| 172 | + return operation; |
| 173 | +} |
| 174 | + |
| 175 | +/* static */ |
59 | 176 | already_AddRefed<Promise> SpeechRecognitionBackend::Available( |
60 | 177 | nsIGlobalObject* aGlobal, const nsTArray<nsCString>& aLanguages) { |
61 | | - return nullptr; |
| 178 | + AssertIsOnMainThread(); |
| 179 | + |
| 180 | + if (!aGlobal) { |
| 181 | + return nullptr; |
| 182 | + } |
| 183 | + |
| 184 | + ErrorResult rv; |
| 185 | + RefPtr<Promise> promise = Promise::Create(aGlobal, rv); |
| 186 | + if (rv.Failed()) { |
| 187 | + return nullptr; |
| 188 | + } |
| 189 | + |
| 190 | + nsTArray<nsCString> languages = aLanguages.Clone(); |
| 191 | + if (languages.IsEmpty()) { |
| 192 | + languages.AppendElement("en-US"_ns); |
| 193 | + } |
| 194 | + |
| 195 | + LOG("SpeechRecognitionBackend::Available - Starting availability check for " |
| 196 | + "{} languages", |
| 197 | + languages.Length()); |
| 198 | + |
| 199 | + if (MOZ_LOG_TEST(gSpeechRecognitionBackendLog, LogLevel::Debug)) { |
| 200 | + for (const auto& lang : languages) { |
| 201 | + LOG("SpeechRecognitionBackend::Available - Language requested: {}", |
| 202 | + lang.get()); |
| 203 | + } |
| 204 | + } |
| 205 | + |
| 206 | + using IsModelInstalledPromise = |
| 207 | + hwinference::PSpeechRecognitionChild::IsModelInstalledPromise; |
| 208 | + using AvailabilityPromise = MozPromise<AvailabilityStatus, nsresult, true>; |
| 209 | + // RunWithTransientSession turns any rejection into NS_ERROR_FAILURE, so the |
| 210 | + // IPC reject reason is forwarded verbatim rather than converted at each step. |
| 211 | + using SendAvailabilityPromise = |
| 212 | + MozPromise<AvailabilityStatus, ResponseRejectReason, true>; |
| 213 | + RunWithTransientSession( |
| 214 | + [languages = std::move(languages)]( |
| 215 | + hwinference::SpeechRecognitionChild* aChild) mutable |
| 216 | + -> RefPtr<SendAvailabilityPromise> { |
| 217 | + RefPtr<hwinference::SpeechRecognitionChild> child = aChild; |
| 218 | + return IsModelInstalledNative(child, languages) |
| 219 | + ->Then(GetCurrentSerialEventTarget(), __func__, |
| 220 | + [child, languages = std::move(languages)]( |
| 221 | + IsModelInstalledPromise::ResolveOrRejectValue&& aValue) |
| 222 | + -> RefPtr<SendAvailabilityPromise> { |
| 223 | + if (aValue.IsReject()) { |
| 224 | + return SendAvailabilityPromise::CreateAndReject( |
| 225 | + aValue.RejectValue(), __func__); |
| 226 | + } |
| 227 | + if (aValue.ResolveValue()) { |
| 228 | + return SendAvailabilityPromise::CreateAndResolve( |
| 229 | + AvailabilityStatus::Available, __func__); |
| 230 | + } |
| 231 | + return child->SendIsModelAvailable(languages) |
| 232 | + ->Map(GetCurrentSerialEventTarget(), __func__, |
| 233 | + [](bool aAvailable) { |
| 234 | + return aAvailable |
| 235 | + ? AvailabilityStatus::Downloadable |
| 236 | + : AvailabilityStatus::Unavailable; |
| 237 | + }); |
| 238 | + }); |
| 239 | + }) |
| 240 | + ->Then(GetMainThreadSerialEventTarget(), __func__, |
| 241 | + [promise](AvailabilityPromise::ResolveOrRejectValue&& aValue) { |
| 242 | + promise->MaybeResolve(aValue.IsResolve() |
| 243 | + ? aValue.ResolveValue() |
| 244 | + : AvailabilityStatus::Unavailable); |
| 245 | + }); |
| 246 | + |
| 247 | + return promise.forget(); |
| 248 | +} |
| 249 | + |
| 250 | +/* static */ |
| 251 | +RefPtr<hwinference::PSpeechRecognitionChild::IsModelInstalledPromise> |
| 252 | +SpeechRecognitionBackend::IsModelInstalledNative( |
| 253 | + hwinference::SpeechRecognitionChild* aChild, |
| 254 | + const nsTArray<nsCString>& aLanguages) { |
| 255 | + AssertOnIPCThread(); |
| 256 | + |
| 257 | + LOG("SpeechRecognitionBackend::IsModelInstalledNative - Starting installed " |
| 258 | + "check for {} languages", |
| 259 | + aLanguages.Length()); |
| 260 | + |
| 261 | + return aChild->SendIsModelInstalled(aLanguages); |
| 262 | +} |
| 263 | + |
| 264 | +/* static */ |
| 265 | +void SpeechRecognitionBackend::CreateSession( |
| 266 | + MoveOnlyFunction<void(hwinference::SpeechRecognitionChild*)> aCallback) { |
| 267 | + AssertIsOnMainThread(); |
| 268 | + |
| 269 | + RefPtr<SpeechRecognitionIPCActorUserGuard> guard = |
| 270 | + MakeRefPtr<SpeechRecognitionIPCActorUserGuard>(); |
| 271 | + EnsureIPCThread(); |
| 272 | + |
| 273 | + Endpoint<hwinference::PSpeechRecognitionParent> parentEndpoint; |
| 274 | + Endpoint<hwinference::PSpeechRecognitionChild> childEndpoint; |
| 275 | + MOZ_ALWAYS_SUCCEEDS(hwinference::PSpeechRecognition::CreateEndpoints( |
| 276 | + &parentEndpoint, &childEndpoint)); |
| 277 | + ContentChild::GetSingleton()->SendCreateSpeechRecognition( |
| 278 | + std::move(parentEndpoint)); |
| 279 | + |
| 280 | + sIPCCapability->Dispatch(NS_NewRunnableFunction( |
| 281 | + "SpeechRecognitionBackend::CreateSession", |
| 282 | + [guard = std::move(guard), endpoint = std::move(childEndpoint), |
| 283 | + callback = std::move(aCallback)]() mutable { |
| 284 | + AssertOnIPCThread(); |
| 285 | + RefPtr child = new hwinference::SpeechRecognitionChild(guard.forget()); |
| 286 | + if (!endpoint.Bind(child)) { |
| 287 | + callback(nullptr); |
| 288 | + return; |
| 289 | + } |
| 290 | + callback(child); |
| 291 | + })); |
62 | 292 | } |
63 | 293 |
|
| 294 | +/* static */ |
64 | 295 | already_AddRefed<Promise> SpeechRecognitionBackend::Install( |
65 | 296 | nsIGlobalObject* aGlobal, const nsTArray<nsCString>& aLanguages) { |
66 | | - return nullptr; |
| 297 | + AssertIsOnMainThread(); |
| 298 | + |
| 299 | + if (!aGlobal) { |
| 300 | + return nullptr; |
| 301 | + } |
| 302 | + |
| 303 | + ErrorResult rv; |
| 304 | + RefPtr<Promise> promise = Promise::Create(aGlobal, rv); |
| 305 | + if (rv.Failed()) { |
| 306 | + return nullptr; |
| 307 | + } |
| 308 | + |
| 309 | + if (aLanguages.IsEmpty()) { |
| 310 | + promise->MaybeResolve(false); |
| 311 | + return promise.forget(); |
| 312 | + } |
| 313 | + |
| 314 | + LOG("SpeechRecognitionBackend::Install - Starting install for {} languages", |
| 315 | + aLanguages.Length()); |
| 316 | + |
| 317 | + using InstallPromise = MozPromise<bool, nsresult, true>; |
| 318 | + RunWithTransientSession( |
| 319 | + [languages = aLanguages.Clone()]( |
| 320 | + hwinference::SpeechRecognitionChild* aChild) mutable { |
| 321 | + return aChild->SendInstallModels(std::move(languages)); |
| 322 | + }) |
| 323 | + ->Then(GetMainThreadSerialEventTarget(), __func__, |
| 324 | + [promise](InstallPromise::ResolveOrRejectValue&& aValue) { |
| 325 | + bool success = aValue.IsResolve() && aValue.ResolveValue(); |
| 326 | + LOG("SpeechRecognitionBackend::Install - Install completed: {}", |
| 327 | + success ? "success" : "failed"); |
| 328 | + promise->MaybeResolve(success); |
| 329 | + }); |
| 330 | + |
| 331 | + return promise.forget(); |
67 | 332 | } |
68 | 333 |
|
69 | 334 | } // namespace mozilla::dom |
|
0 commit comments