PushSubscription.cpp (10801B)
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/PushSubscription.h" 8 9 #include "mozilla/Base64.h" 10 #include "mozilla/dom/Promise.h" 11 #include "mozilla/dom/PromiseWorkerProxy.h" 12 #include "mozilla/dom/PushSubscriptionOptions.h" 13 #include "mozilla/dom/TypedArray.h" 14 #include "mozilla/dom/WorkerCommon.h" 15 #include "mozilla/dom/WorkerPrivate.h" 16 #include "mozilla/dom/WorkerRunnable.h" 17 #include "mozilla/dom/WorkerScope.h" 18 #include "nsGlobalWindowInner.h" 19 #include "nsIPushService.h" 20 #include "nsIScriptObjectPrincipal.h" 21 #include "nsServiceManagerUtils.h" 22 23 namespace mozilla::dom { 24 25 namespace { 26 27 class UnsubscribeResultCallback final : public nsIUnsubscribeResultCallback { 28 public: 29 NS_DECL_ISUPPORTS 30 31 explicit UnsubscribeResultCallback(Promise* aPromise) : mPromise(aPromise) { 32 AssertIsOnMainThread(); 33 } 34 35 NS_IMETHOD 36 OnUnsubscribe(nsresult aStatus, bool aSuccess) override { 37 if (NS_SUCCEEDED(aStatus)) { 38 mPromise->MaybeResolve(aSuccess); 39 } else { 40 mPromise->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE); 41 } 42 43 return NS_OK; 44 } 45 46 private: 47 ~UnsubscribeResultCallback() = default; 48 49 RefPtr<Promise> mPromise; 50 }; 51 52 NS_IMPL_ISUPPORTS(UnsubscribeResultCallback, nsIUnsubscribeResultCallback) 53 54 class UnsubscribeResultRunnable final : public WorkerThreadRunnable { 55 public: 56 UnsubscribeResultRunnable(WorkerPrivate* aWorkerPrivate, 57 RefPtr<PromiseWorkerProxy>&& aProxy, 58 nsresult aStatus, bool aSuccess) 59 : WorkerThreadRunnable("UnsubscribeResultRunnable"), 60 mProxy(std::move(aProxy)), 61 mStatus(aStatus), 62 mSuccess(aSuccess) { 63 AssertIsOnMainThread(); 64 } 65 66 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override { 67 MOZ_ASSERT(aWorkerPrivate); 68 aWorkerPrivate->AssertIsOnWorkerThread(); 69 70 RefPtr<Promise> promise = mProxy->GetWorkerPromise(); 71 // Once Worker had already started shutdown, workerPromise would be nullptr 72 if (!promise) { 73 return true; 74 } 75 if (NS_SUCCEEDED(mStatus)) { 76 promise->MaybeResolve(mSuccess); 77 } else { 78 promise->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE); 79 } 80 81 mProxy->CleanUp(); 82 83 return true; 84 } 85 86 private: 87 ~UnsubscribeResultRunnable() = default; 88 89 RefPtr<PromiseWorkerProxy> mProxy; 90 nsresult mStatus; 91 bool mSuccess; 92 }; 93 94 class WorkerUnsubscribeResultCallback final 95 : public nsIUnsubscribeResultCallback { 96 public: 97 NS_DECL_ISUPPORTS 98 99 explicit WorkerUnsubscribeResultCallback(PromiseWorkerProxy* aProxy) 100 : mProxy(aProxy) { 101 AssertIsOnMainThread(); 102 } 103 104 NS_IMETHOD 105 OnUnsubscribe(nsresult aStatus, bool aSuccess) override { 106 AssertIsOnMainThread(); 107 MOZ_ASSERT(mProxy, "OnUnsubscribe() called twice?"); 108 109 MutexAutoLock lock(mProxy->Lock()); 110 if (mProxy->CleanedUp()) { 111 return NS_OK; 112 } 113 114 WorkerPrivate* worker = mProxy->GetWorkerPrivate(); 115 RefPtr<UnsubscribeResultRunnable> r = new UnsubscribeResultRunnable( 116 worker, std::move(mProxy), aStatus, aSuccess); 117 MOZ_ALWAYS_TRUE(r->Dispatch(worker)); 118 119 return NS_OK; 120 } 121 122 private: 123 ~WorkerUnsubscribeResultCallback() = default; 124 125 RefPtr<PromiseWorkerProxy> mProxy; 126 }; 127 128 NS_IMPL_ISUPPORTS(WorkerUnsubscribeResultCallback, nsIUnsubscribeResultCallback) 129 130 class UnsubscribeRunnable final : public Runnable { 131 public: 132 UnsubscribeRunnable(PromiseWorkerProxy* aProxy, const nsAString& aScope) 133 : Runnable("dom::UnsubscribeRunnable"), mProxy(aProxy), mScope(aScope) { 134 MOZ_ASSERT(aProxy); 135 MOZ_ASSERT(!aScope.IsEmpty()); 136 } 137 138 NS_IMETHOD 139 Run() override { 140 AssertIsOnMainThread(); 141 142 nsCOMPtr<nsIPrincipal> principal; 143 144 { 145 MutexAutoLock lock(mProxy->Lock()); 146 if (mProxy->CleanedUp()) { 147 return NS_OK; 148 } 149 principal = mProxy->GetWorkerPrivate()->GetPrincipal(); 150 } 151 152 MOZ_ASSERT(principal); 153 154 RefPtr<WorkerUnsubscribeResultCallback> callback = 155 new WorkerUnsubscribeResultCallback(mProxy); 156 157 nsCOMPtr<nsIPushService> service = 158 do_GetService("@mozilla.org/push/Service;1"); 159 if (NS_WARN_IF(!service)) { 160 callback->OnUnsubscribe(NS_ERROR_FAILURE, false); 161 return NS_OK; 162 } 163 164 if (NS_WARN_IF( 165 NS_FAILED(service->Unsubscribe(mScope, principal, callback)))) { 166 callback->OnUnsubscribe(NS_ERROR_FAILURE, false); 167 return NS_OK; 168 } 169 170 return NS_OK; 171 } 172 173 private: 174 ~UnsubscribeRunnable() = default; 175 176 RefPtr<PromiseWorkerProxy> mProxy; 177 nsString mScope; 178 }; 179 180 } // anonymous namespace 181 182 PushSubscription::PushSubscription(nsIGlobalObject* aGlobal, 183 const nsAString& aEndpoint, 184 const nsAString& aScope, 185 Nullable<EpochTimeStamp>&& aExpirationTime, 186 nsTArray<uint8_t>&& aRawP256dhKey, 187 nsTArray<uint8_t>&& aAuthSecret, 188 nsTArray<uint8_t>&& aAppServerKey) 189 : mEndpoint(aEndpoint), 190 mScope(aScope), 191 mExpirationTime(std::move(aExpirationTime)), 192 mRawP256dhKey(std::move(aRawP256dhKey)), 193 mAuthSecret(std::move(aAuthSecret)) { 194 if (NS_IsMainThread()) { 195 mGlobal = aGlobal; 196 } else { 197 #ifdef DEBUG 198 // There's only one global on a worker, so we don't need to pass a global 199 // object to the constructor. 200 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); 201 MOZ_ASSERT(worker); 202 worker->AssertIsOnWorkerThread(); 203 #endif 204 } 205 mOptions = new PushSubscriptionOptions(mGlobal, std::move(aAppServerKey)); 206 } 207 208 PushSubscription::~PushSubscription() = default; 209 210 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushSubscription, mGlobal, mOptions) 211 NS_IMPL_CYCLE_COLLECTING_ADDREF(PushSubscription) 212 NS_IMPL_CYCLE_COLLECTING_RELEASE(PushSubscription) 213 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushSubscription) 214 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY 215 NS_INTERFACE_MAP_ENTRY(nsISupports) 216 NS_INTERFACE_MAP_END 217 218 JSObject* PushSubscription::WrapObject(JSContext* aCx, 219 JS::Handle<JSObject*> aGivenProto) { 220 return PushSubscription_Binding::Wrap(aCx, this, aGivenProto); 221 } 222 223 // static 224 already_AddRefed<PushSubscription> PushSubscription::Constructor( 225 GlobalObject& aGlobal, const PushSubscriptionInit& aInitDict, 226 ErrorResult& aRv) { 227 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); 228 229 nsTArray<uint8_t> rawKey; 230 if (!aInitDict.mP256dhKey.IsNull() && 231 !aInitDict.mP256dhKey.Value().AppendDataTo(rawKey)) { 232 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 233 return nullptr; 234 } 235 236 nsTArray<uint8_t> authSecret; 237 if (!aInitDict.mAuthSecret.IsNull() && 238 !aInitDict.mAuthSecret.Value().AppendDataTo(authSecret)) { 239 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 240 return nullptr; 241 } 242 243 nsTArray<uint8_t> appServerKey; 244 if (!aInitDict.mAppServerKey.IsNull() && 245 !AppendTypedArrayDataTo(aInitDict.mAppServerKey.Value(), appServerKey)) { 246 aRv.Throw(NS_ERROR_OUT_OF_MEMORY); 247 return nullptr; 248 } 249 250 Nullable<EpochTimeStamp> expirationTime; 251 if (aInitDict.mExpirationTime.IsNull()) { 252 expirationTime.SetNull(); 253 } else { 254 expirationTime.SetValue(aInitDict.mExpirationTime.Value()); 255 } 256 257 RefPtr<PushSubscription> sub = new PushSubscription( 258 global, aInitDict.mEndpoint, aInitDict.mScope, std::move(expirationTime), 259 std::move(rawKey), std::move(authSecret), std::move(appServerKey)); 260 261 return sub.forget(); 262 } 263 264 already_AddRefed<Promise> PushSubscription::Unsubscribe(ErrorResult& aRv) { 265 if (!NS_IsMainThread()) { 266 RefPtr<Promise> p = UnsubscribeFromWorker(aRv); 267 return p.forget(); 268 } 269 270 MOZ_ASSERT(mGlobal); 271 272 nsCOMPtr<nsIPushService> service = 273 do_GetService("@mozilla.org/push/Service;1"); 274 if (NS_WARN_IF(!service)) { 275 aRv.Throw(NS_ERROR_FAILURE); 276 return nullptr; 277 } 278 279 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mGlobal); 280 if (!window) { 281 aRv.Throw(NS_ERROR_FAILURE); 282 return nullptr; 283 } 284 285 RefPtr<Promise> p = Promise::Create(mGlobal, aRv); 286 if (NS_WARN_IF(aRv.Failed())) { 287 return nullptr; 288 } 289 290 RefPtr<UnsubscribeResultCallback> callback = new UnsubscribeResultCallback(p); 291 (void)NS_WARN_IF(NS_FAILED(service->Unsubscribe( 292 mScope, nsGlobalWindowInner::Cast(window)->GetClientPrincipal(), 293 callback))); 294 295 return p.forget(); 296 } 297 298 void PushSubscription::GetKey(JSContext* aCx, PushEncryptionKeyName aType, 299 JS::MutableHandle<JSObject*> aKey, 300 ErrorResult& aRv) { 301 if (aType == PushEncryptionKeyName::P256dh && !mRawP256dhKey.IsEmpty()) { 302 aKey.set(ArrayBuffer::Create(aCx, mRawP256dhKey, aRv)); 303 } else if (aType == PushEncryptionKeyName::Auth && !mAuthSecret.IsEmpty()) { 304 aKey.set(ArrayBuffer::Create(aCx, mAuthSecret, aRv)); 305 } else { 306 aKey.set(nullptr); 307 } 308 } 309 310 void PushSubscription::ToJSON(PushSubscriptionJSON& aJSON, ErrorResult& aRv) { 311 aJSON.mEndpoint.Construct(); 312 aJSON.mEndpoint.Value() = mEndpoint; 313 314 aJSON.mKeys.mP256dh.Construct(); 315 nsresult rv = Base64URLEncode( 316 mRawP256dhKey.Length(), mRawP256dhKey.Elements(), 317 Base64URLEncodePaddingPolicy::Omit, aJSON.mKeys.mP256dh.Value()); 318 if (NS_WARN_IF(NS_FAILED(rv))) { 319 aRv.Throw(rv); 320 return; 321 } 322 323 aJSON.mKeys.mAuth.Construct(); 324 rv = Base64URLEncode(mAuthSecret.Length(), mAuthSecret.Elements(), 325 Base64URLEncodePaddingPolicy::Omit, 326 aJSON.mKeys.mAuth.Value()); 327 if (NS_WARN_IF(NS_FAILED(rv))) { 328 aRv.Throw(rv); 329 return; 330 } 331 aJSON.mExpirationTime.Construct(mExpirationTime); 332 } 333 334 already_AddRefed<PushSubscriptionOptions> PushSubscription::Options() { 335 RefPtr<PushSubscriptionOptions> options = mOptions; 336 return options.forget(); 337 } 338 339 already_AddRefed<Promise> PushSubscription::UnsubscribeFromWorker( 340 ErrorResult& aRv) { 341 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); 342 MOZ_ASSERT(worker); 343 worker->AssertIsOnWorkerThread(); 344 345 nsCOMPtr<nsIGlobalObject> global = worker->GlobalScope(); 346 RefPtr<Promise> p = Promise::Create(global, aRv); 347 if (NS_WARN_IF(aRv.Failed())) { 348 return nullptr; 349 } 350 351 RefPtr<PromiseWorkerProxy> proxy = PromiseWorkerProxy::Create(worker, p); 352 if (!proxy) { 353 p->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE); 354 return p.forget(); 355 } 356 357 RefPtr<UnsubscribeRunnable> r = new UnsubscribeRunnable(proxy, mScope); 358 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r)); 359 360 return p.forget(); 361 } 362 363 } // namespace mozilla::dom