CacheOpChild.cpp (12673B)
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 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 #include "mozilla/dom/cache/CacheOpChild.h" 8 9 #include "mozilla/dom/Promise.h" 10 #include "mozilla/dom/Request.h" 11 #include "mozilla/dom/Response.h" 12 #include "mozilla/dom/cache/BoundStorageKey.h" 13 #include "mozilla/dom/cache/BoundStorageKeyCache.h" 14 #include "mozilla/dom/cache/Cache.h" 15 #include "mozilla/dom/cache/CacheChild.h" 16 #include "mozilla/dom/cache/CacheStreamControlChild.h" 17 #include "mozilla/dom/cache/CacheWorkerRef.h" 18 19 namespace mozilla::dom { 20 // XXX Move this to ToJSValue.h 21 template <typename T> 22 [[nodiscard]] bool ToJSValue(JSContext* aCx, const SafeRefPtr<T>& aArgument, 23 JS::MutableHandle<JS::Value> aValue) { 24 return ToJSValue(aCx, *aArgument.unsafeGetRawPtr(), aValue); 25 } 26 27 namespace cache { 28 29 using mozilla::ipc::PBackgroundChild; 30 using Promise = mozilla::dom::Promise; 31 32 namespace { 33 34 void AddWorkerRefToStreamChild(const CacheReadStream& aReadStream, 35 const SafeRefPtr<CacheWorkerRef>& aWorkerRef) { 36 MOZ_ASSERT_IF(!NS_IsMainThread(), aWorkerRef); 37 CacheStreamControlChild* cacheControl = 38 static_cast<CacheStreamControlChild*>(aReadStream.control().AsChild()); 39 if (cacheControl) { 40 cacheControl->SetWorkerRef(aWorkerRef.clonePtr()); 41 } 42 } 43 44 void AddWorkerRefToStreamChild(const CacheResponse& aResponse, 45 const SafeRefPtr<CacheWorkerRef>& aWorkerRef) { 46 MOZ_ASSERT_IF(!NS_IsMainThread(), aWorkerRef); 47 48 if (aResponse.body().isNothing()) { 49 return; 50 } 51 52 AddWorkerRefToStreamChild(aResponse.body().ref(), aWorkerRef); 53 } 54 55 void AddWorkerRefToStreamChild(const CacheRequest& aRequest, 56 const SafeRefPtr<CacheWorkerRef>& aWorkerRef) { 57 MOZ_ASSERT_IF(!NS_IsMainThread(), aWorkerRef); 58 59 if (aRequest.body().isNothing()) { 60 return; 61 } 62 63 AddWorkerRefToStreamChild(aRequest.body().ref(), aWorkerRef); 64 } 65 66 } // namespace 67 68 CacheOpChild::CacheOpChild(SafeRefPtr<CacheWorkerRef> aWorkerRef, 69 nsIGlobalObject* aGlobal, nsISupports* aParent, 70 RefPtr<Promise>& aPromise, ActorChild* aParentActor) 71 : mGlobal(aGlobal), 72 mParent(aParent), 73 mPromise(aPromise), 74 mParentActor(aParentActor) { 75 MOZ_DIAGNOSTIC_ASSERT(mGlobal); 76 MOZ_DIAGNOSTIC_ASSERT(mParent); 77 MOZ_DIAGNOSTIC_ASSERT(aPromise); 78 79 MOZ_ASSERT_IF(!NS_IsMainThread(), aWorkerRef); 80 81 SetWorkerRef(CacheWorkerRef::PreferBehavior( 82 std::move(aWorkerRef), CacheWorkerRef::eStrongWorkerRef)); 83 } 84 85 CacheOpChild::CacheOpChild(SafeRefPtr<CacheWorkerRef> aWorkerRef, 86 nsIGlobalObject* aGlobal, nsISupports* aParent, 87 RefPtr<CacheStoragePromise>& aPromise, 88 ActorChild* aParentActor) 89 : mGlobal(aGlobal), 90 mParent(aParent), 91 mPromise(aPromise), 92 mParentActor(aParentActor) { 93 MOZ_DIAGNOSTIC_ASSERT(mGlobal); 94 MOZ_DIAGNOSTIC_ASSERT(mParent); 95 MOZ_DIAGNOSTIC_ASSERT(aPromise); 96 97 MOZ_ASSERT_IF(!NS_IsMainThread(), aWorkerRef); 98 99 SetWorkerRef(CacheWorkerRef::PreferBehavior( 100 std::move(aWorkerRef), CacheWorkerRef::eStrongWorkerRef)); 101 } 102 103 CacheOpChild::~CacheOpChild() { 104 NS_ASSERT_OWNINGTHREAD(CacheOpChild); 105 MOZ_DIAGNOSTIC_ASSERT(!mPromise); 106 } 107 108 void CacheOpChild::ActorDestroy(ActorDestroyReason aReason) { 109 NS_ASSERT_OWNINGTHREAD(CacheOpChild); 110 111 // If the actor was terminated for some unknown reason, then indicate the 112 // operation is dead. 113 if (mPromise) { 114 HandleAndSettle<CacheOpResult::Tvoid_t>(ErrorResult(NS_ERROR_FAILURE)); 115 mPromise.destroy(); 116 } 117 mParentActor->NoteDeletedActor(); 118 RemoveWorkerRef(); 119 } 120 121 using StorageOpenResultType = std::pair<CacheChild*, Namespace>; 122 123 template <CacheOpResult::Type OP_TYPE, typename ResultType> 124 void CacheOpChild::SettlePromise( 125 ResultType&& aRes, ErrorResult&& aRv, 126 const RefPtr<CacheStoragePromise>& aThePromise) { 127 // picks the correct promise type using traits defined in BoundStorageKey.h 128 // and BoundStorageKeyCache.h 129 using TargetPromiseType = 130 typename dom::cachestorage_traits<OP_TYPE>::PromiseType; 131 auto* target = static_cast<TargetPromiseType*>(aThePromise.get()); 132 auto&& res = std::forward<ResultType>(aRes); 133 134 if (aRv.Failed()) { 135 target->Reject(std::move(aRv), __func__); 136 return; 137 } 138 139 using ValueType = typename std::decay_t<decltype(aRes)>; 140 if constexpr (std::is_same_v<ValueType, JS::HandleValue>) { 141 // Not serializing JS types into MozPromise. Need to collapse JS values 142 // into their raw types here. Since this is internal private method and 143 // based on it's usage yet; just expecting Undefined or null values here. 144 MOZ_ASSERT(res.isNullOrUndefined()); 145 target->Resolve(nullptr /*implicitly converts to false for boolean types */, 146 __func__); 147 } else if constexpr (std::is_same_v<ValueType, StorageOpenResultType>) { 148 // result would be of type CacheChild here and we need to properly wrap into 149 // holder class BoundStorageKeyCache before resolving promise 150 auto [cacheChild, ns] = res; 151 auto* cache = new BoundStorageKeyCache(mGlobal, cacheChild, ns); 152 target->Resolve(RefPtr(cache), __func__); 153 } else { 154 target->Resolve(std::forward<ResultType>(aRes), __func__); 155 } 156 } 157 158 template <CacheOpResult::Type OP_TYPE, typename ResultType> 159 void CacheOpChild::SettlePromise(ResultType&& aRes, ErrorResult&& aRv, 160 const RefPtr<Promise>& aThePromise) { 161 if (aRv.Failed()) { 162 aThePromise->MaybeReject(aRv.StealNSResult()); 163 } else { 164 using ValueType = typename std::decay_t<decltype(aRes)>; 165 166 if constexpr (std::is_same_v<ValueType, StorageOpenResultType>) { 167 // result would be of type CacheChild here and we need to properly wrap 168 // into holder class Cache before resolving promise 169 auto&& res = std::forward<ResultType>(aRes); 170 auto [cacheChild, ns] = res; 171 auto* cache = new Cache(mGlobal, cacheChild, ns); 172 aThePromise->MaybeResolve(RefPtr(cache)); 173 } else { 174 aThePromise->MaybeResolve(std::forward<ResultType>(aRes)); 175 } 176 } 177 } 178 179 template <CacheOpResult::Type OP_TYPE, typename ResultType> 180 void CacheOpChild::Settle(ResultType&& aRes, ErrorResult&& aRv) { 181 if (mPromise->is<RefPtr<mozilla::dom::Promise>>()) { 182 auto targetPromise = mPromise->as<RefPtr<mozilla::dom::Promise>>(); 183 MOZ_ASSERT(targetPromise); 184 185 SettlePromise<OP_TYPE>(std::forward<ResultType>(aRes), std::move(aRv), 186 targetPromise); 187 } else if (mPromise->is<RefPtr<CacheStoragePromise>>()) { 188 auto targetPromise = mPromise->as<RefPtr<CacheStoragePromise>>(); 189 MOZ_ASSERT(targetPromise); 190 191 SettlePromise<OP_TYPE>(std::forward<ResultType>(aRes), std::move(aRv), 192 targetPromise); 193 } 194 } 195 196 template <CacheOpResult::Type OP_TYPE, typename TResponse> 197 void CacheOpChild::HandleAndSettle(TResponse&& aRes) { 198 using ValueType = typename std::decay_t<decltype(aRes)>; 199 auto&& res = std::forward<TResponse>(aRes); 200 201 if constexpr (std::is_same_v<ValueType, Maybe<CacheResponse>>) { 202 if (res.isNothing()) { 203 Settle<OP_TYPE>(JS::UndefinedHandleValue); 204 return; 205 } 206 207 const CacheResponse& cacheResponse = res.ref(); 208 209 AddWorkerRefToStreamChild(cacheResponse, GetWorkerRefPtr()); 210 RefPtr<Response> response = ToResponse(cacheResponse); 211 Settle<OP_TYPE>(response); 212 213 } else if constexpr (std::is_same_v<ValueType, nsTArray<CacheResponse>>) { 214 AutoTArray<RefPtr<Response>, 256> responses; 215 responses.SetCapacity(res.Length()); 216 217 for (uint32_t i = 0; i < res.Length(); ++i) { 218 AddWorkerRefToStreamChild(res[i], GetWorkerRefPtr()); 219 responses.AppendElement(ToResponse(res[i])); 220 } 221 222 Settle<OP_TYPE>(std::move(responses)); 223 224 } else if constexpr (std::is_same_v<ValueType, nsTArray<CacheRequest>>) { 225 AutoTArray<SafeRefPtr<Request>, 256> requests; 226 requests.SetCapacity(res.Length()); 227 228 for (uint32_t i = 0; i < res.Length(); ++i) { 229 AddWorkerRefToStreamChild(res[i], GetWorkerRefPtr()); 230 requests.AppendElement(ToRequest(res[i])); 231 } 232 233 Settle<OP_TYPE>(std::move(requests)); 234 235 } else if constexpr (std::is_same_v<ValueType, bool> || 236 std::is_same_v<ValueType, nsTArray<nsString>> || 237 std::is_same_v<ValueType, JS::Handle<JS::Value>>) { 238 Settle<OP_TYPE>(std::forward<TResponse>(aRes)); 239 } else if constexpr (std::is_same_v<ValueType, ErrorResult>) { 240 Settle<OP_TYPE>(false /* valid response does not exist */, std::move(res)); 241 } else if constexpr (std::is_same_v<ValueType, StorageOpenResult>) { 242 auto* actor = static_cast<CacheChild*>(res.actor().AsChild()); 243 244 // If we have a success status then we should have an actor. Gracefully 245 // reject instead of crashing, though, if we get a nullptr here. 246 MOZ_DIAGNOSTIC_ASSERT(actor); 247 if (!actor) { 248 ErrorResult errRes; 249 errRes.ThrowTypeError( 250 "CacheStorage.open() failed to access the storage system."); 251 Settle<OP_TYPE>(StorageOpenResultType{} /* dummy */, std::move(errRes)); 252 } 253 254 actor->SetWorkerRef(CacheWorkerRef::PreferBehavior( 255 GetWorkerRefPtr().clonePtr(), CacheWorkerRef::eIPCWorkerRef)); 256 Settle<OP_TYPE>(StorageOpenResultType{actor, res.ns()}); 257 } else { 258 static_assert(std::is_same_v<ValueType, void>, 259 "There should be a block handling this type"); 260 } 261 } 262 263 mozilla::ipc::IPCResult CacheOpChild::Recv__delete__( 264 ErrorResult&& aRv, const CacheOpResult& aResult) { 265 NS_ASSERT_OWNINGTHREAD(CacheOpChild); 266 267 if (NS_WARN_IF(aRv.Failed())) { 268 MOZ_DIAGNOSTIC_ASSERT(aResult.type() == CacheOpResult::Tvoid_t); 269 HandleAndSettle<CacheOpResult::Tvoid_t>(aRv); 270 mPromise.destroy(); 271 return IPC_OK(); 272 } 273 274 switch (aResult.type()) { 275 case CacheOpResult::TCacheMatchResult: { 276 const auto& response = aResult.get_CacheMatchResult().maybeResponse(); 277 HandleAndSettle<CacheOpResult::TCacheMatchResult>(response); 278 break; 279 } 280 case CacheOpResult::TCacheMatchAllResult: { 281 const auto& response = aResult.get_CacheMatchAllResult().responseList(); 282 HandleAndSettle<CacheOpResult::TCacheMatchAllResult>(response); 283 break; 284 } 285 case CacheOpResult::TCachePutAllResult: { 286 // resolve with undefined 287 HandleAndSettle<CacheOpResult::TCachePutAllResult>( 288 JS::UndefinedHandleValue); 289 break; 290 } 291 case CacheOpResult::TCacheDeleteResult: { 292 const auto& response = aResult.get_CacheDeleteResult().success(); 293 HandleAndSettle<CacheOpResult::TCacheDeleteResult>(response); 294 break; 295 } 296 case CacheOpResult::TCacheKeysResult: { 297 const auto& response = aResult.get_CacheKeysResult().requestList(); 298 HandleAndSettle<CacheOpResult::TCacheKeysResult>(response); 299 break; 300 } 301 case CacheOpResult::TStorageMatchResult: { 302 const auto& response = aResult.get_StorageMatchResult().maybeResponse(); 303 HandleAndSettle<CacheOpResult::TStorageMatchResult>(response); 304 break; 305 } 306 case CacheOpResult::TStorageHasResult: { 307 const auto& response = aResult.get_StorageHasResult().success(); 308 HandleAndSettle<CacheOpResult::TStorageHasResult>(response); 309 break; 310 } 311 case CacheOpResult::TStorageOpenResult: { 312 const auto& response = aResult.get_StorageOpenResult(); 313 HandleAndSettle<CacheOpResult::TStorageOpenResult>(response); 314 break; 315 } 316 case CacheOpResult::TStorageDeleteResult: { 317 const auto& response = aResult.get_StorageDeleteResult().success(); 318 HandleAndSettle<CacheOpResult::TStorageDeleteResult>(response); 319 break; 320 } 321 case CacheOpResult::TStorageKeysResult: { 322 const auto& response = aResult.get_StorageKeysResult().keyList(); 323 HandleAndSettle<CacheOpResult::TStorageKeysResult>(response); 324 break; 325 } 326 default: 327 MOZ_CRASH("Unknown Cache op result type!"); 328 } 329 330 mPromise.destroy(); 331 return IPC_OK(); 332 } 333 334 void CacheOpChild::StartDestroy() { 335 NS_ASSERT_OWNINGTHREAD(CacheOpChild); 336 337 // Do not cancel on-going operations when WorkerRef calls this. Instead, 338 // keep the Worker alive until we are done. 339 } 340 341 nsIGlobalObject* CacheOpChild::GetGlobalObject() const { return mGlobal; } 342 343 #ifdef DEBUG 344 void CacheOpChild::AssertOwningThread() const { 345 NS_ASSERT_OWNINGTHREAD(CacheOpChild); 346 } 347 #endif 348 349 } // namespace cache 350 } // namespace mozilla::dom