Bug 1885602 - Part 5: Implement navigating to the SUMO help topic from the menu heade...
[gecko.git] / dom / push / PushSubscription.cpp
blob0afb63eee82963cb02c950c9cffdad9ff893066f
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/. */
7 #include "mozilla/dom/PushSubscription.h"
9 #include "nsGlobalWindowInner.h"
10 #include "nsIPushService.h"
11 #include "nsIScriptObjectPrincipal.h"
12 #include "nsServiceManagerUtils.h"
14 #include "mozilla/Base64.h"
15 #include "mozilla/Unused.h"
17 #include "mozilla/dom/Promise.h"
18 #include "mozilla/dom/PromiseWorkerProxy.h"
19 #include "mozilla/dom/PushSubscriptionOptions.h"
20 #include "mozilla/dom/PushUtil.h"
21 #include "mozilla/dom/WorkerCommon.h"
22 #include "mozilla/dom/WorkerPrivate.h"
23 #include "mozilla/dom/WorkerRunnable.h"
24 #include "mozilla/dom/WorkerScope.h"
26 namespace mozilla::dom {
28 namespace {
30 class UnsubscribeResultCallback final : public nsIUnsubscribeResultCallback {
31 public:
32 NS_DECL_ISUPPORTS
34 explicit UnsubscribeResultCallback(Promise* aPromise) : mPromise(aPromise) {
35 AssertIsOnMainThread();
38 NS_IMETHOD
39 OnUnsubscribe(nsresult aStatus, bool aSuccess) override {
40 if (NS_SUCCEEDED(aStatus)) {
41 mPromise->MaybeResolve(aSuccess);
42 } else {
43 mPromise->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE);
46 return NS_OK;
49 private:
50 ~UnsubscribeResultCallback() = default;
52 RefPtr<Promise> mPromise;
55 NS_IMPL_ISUPPORTS(UnsubscribeResultCallback, nsIUnsubscribeResultCallback)
57 class UnsubscribeResultRunnable final : public WorkerRunnable {
58 public:
59 UnsubscribeResultRunnable(WorkerPrivate* aWorkerPrivate,
60 RefPtr<PromiseWorkerProxy>&& aProxy,
61 nsresult aStatus, bool aSuccess)
62 : WorkerRunnable(aWorkerPrivate, "UnsubscribeResultRunnable"),
63 mProxy(std::move(aProxy)),
64 mStatus(aStatus),
65 mSuccess(aSuccess) {
66 AssertIsOnMainThread();
69 bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
70 MOZ_ASSERT(aWorkerPrivate);
71 aWorkerPrivate->AssertIsOnWorkerThread();
73 RefPtr<Promise> promise = mProxy->GetWorkerPromise();
74 // Once Worker had already started shutdown, workerPromise would be nullptr
75 if (!promise) {
76 return true;
78 if (NS_SUCCEEDED(mStatus)) {
79 promise->MaybeResolve(mSuccess);
80 } else {
81 promise->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE);
84 mProxy->CleanUp();
86 return true;
89 private:
90 ~UnsubscribeResultRunnable() = default;
92 RefPtr<PromiseWorkerProxy> mProxy;
93 nsresult mStatus;
94 bool mSuccess;
97 class WorkerUnsubscribeResultCallback final
98 : public nsIUnsubscribeResultCallback {
99 public:
100 NS_DECL_ISUPPORTS
102 explicit WorkerUnsubscribeResultCallback(PromiseWorkerProxy* aProxy)
103 : mProxy(aProxy) {
104 AssertIsOnMainThread();
107 NS_IMETHOD
108 OnUnsubscribe(nsresult aStatus, bool aSuccess) override {
109 AssertIsOnMainThread();
110 MOZ_ASSERT(mProxy, "OnUnsubscribe() called twice?");
112 MutexAutoLock lock(mProxy->Lock());
113 if (mProxy->CleanedUp()) {
114 return NS_OK;
117 WorkerPrivate* worker = mProxy->GetWorkerPrivate();
118 RefPtr<UnsubscribeResultRunnable> r = new UnsubscribeResultRunnable(
119 worker, std::move(mProxy), aStatus, aSuccess);
120 MOZ_ALWAYS_TRUE(r->Dispatch());
122 return NS_OK;
125 private:
126 ~WorkerUnsubscribeResultCallback() = default;
128 RefPtr<PromiseWorkerProxy> mProxy;
131 NS_IMPL_ISUPPORTS(WorkerUnsubscribeResultCallback, nsIUnsubscribeResultCallback)
133 class UnsubscribeRunnable final : public Runnable {
134 public:
135 UnsubscribeRunnable(PromiseWorkerProxy* aProxy, const nsAString& aScope)
136 : Runnable("dom::UnsubscribeRunnable"), mProxy(aProxy), mScope(aScope) {
137 MOZ_ASSERT(aProxy);
138 MOZ_ASSERT(!aScope.IsEmpty());
141 NS_IMETHOD
142 Run() override {
143 AssertIsOnMainThread();
145 nsCOMPtr<nsIPrincipal> principal;
148 MutexAutoLock lock(mProxy->Lock());
149 if (mProxy->CleanedUp()) {
150 return NS_OK;
152 principal = mProxy->GetWorkerPrivate()->GetPrincipal();
155 MOZ_ASSERT(principal);
157 RefPtr<WorkerUnsubscribeResultCallback> callback =
158 new WorkerUnsubscribeResultCallback(mProxy);
160 nsCOMPtr<nsIPushService> service =
161 do_GetService("@mozilla.org/push/Service;1");
162 if (NS_WARN_IF(!service)) {
163 callback->OnUnsubscribe(NS_ERROR_FAILURE, false);
164 return NS_OK;
167 if (NS_WARN_IF(
168 NS_FAILED(service->Unsubscribe(mScope, principal, callback)))) {
169 callback->OnUnsubscribe(NS_ERROR_FAILURE, false);
170 return NS_OK;
173 return NS_OK;
176 private:
177 ~UnsubscribeRunnable() = default;
179 RefPtr<PromiseWorkerProxy> mProxy;
180 nsString mScope;
183 } // anonymous namespace
185 PushSubscription::PushSubscription(nsIGlobalObject* aGlobal,
186 const nsAString& aEndpoint,
187 const nsAString& aScope,
188 Nullable<EpochTimeStamp>&& aExpirationTime,
189 nsTArray<uint8_t>&& aRawP256dhKey,
190 nsTArray<uint8_t>&& aAuthSecret,
191 nsTArray<uint8_t>&& aAppServerKey)
192 : mEndpoint(aEndpoint),
193 mScope(aScope),
194 mExpirationTime(std::move(aExpirationTime)),
195 mRawP256dhKey(std::move(aRawP256dhKey)),
196 mAuthSecret(std::move(aAuthSecret)) {
197 if (NS_IsMainThread()) {
198 mGlobal = aGlobal;
199 } else {
200 #ifdef DEBUG
201 // There's only one global on a worker, so we don't need to pass a global
202 // object to the constructor.
203 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
204 MOZ_ASSERT(worker);
205 worker->AssertIsOnWorkerThread();
206 #endif
208 mOptions = new PushSubscriptionOptions(mGlobal, std::move(aAppServerKey));
211 PushSubscription::~PushSubscription() = default;
213 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushSubscription, mGlobal, mOptions)
214 NS_IMPL_CYCLE_COLLECTING_ADDREF(PushSubscription)
215 NS_IMPL_CYCLE_COLLECTING_RELEASE(PushSubscription)
216 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushSubscription)
217 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
218 NS_INTERFACE_MAP_ENTRY(nsISupports)
219 NS_INTERFACE_MAP_END
221 JSObject* PushSubscription::WrapObject(JSContext* aCx,
222 JS::Handle<JSObject*> aGivenProto) {
223 return PushSubscription_Binding::Wrap(aCx, this, aGivenProto);
226 // static
227 already_AddRefed<PushSubscription> PushSubscription::Constructor(
228 GlobalObject& aGlobal, const PushSubscriptionInit& aInitDict,
229 ErrorResult& aRv) {
230 nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
232 nsTArray<uint8_t> rawKey;
233 if (aInitDict.mP256dhKey.WasPassed() &&
234 !aInitDict.mP256dhKey.Value().IsNull() &&
235 !aInitDict.mP256dhKey.Value().Value().AppendDataTo(rawKey)) {
236 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
237 return nullptr;
240 nsTArray<uint8_t> authSecret;
241 if (aInitDict.mAuthSecret.WasPassed() &&
242 !aInitDict.mAuthSecret.Value().IsNull() &&
243 !aInitDict.mAuthSecret.Value().Value().AppendDataTo(authSecret)) {
244 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
245 return nullptr;
248 nsTArray<uint8_t> appServerKey;
249 if (aInitDict.mAppServerKey.WasPassed() &&
250 !aInitDict.mAppServerKey.Value().IsNull()) {
251 const OwningArrayBufferViewOrArrayBuffer& bufferSource =
252 aInitDict.mAppServerKey.Value().Value();
253 if (!PushUtil::CopyBufferSourceToArray(bufferSource, appServerKey)) {
254 aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
255 return nullptr;
259 Nullable<EpochTimeStamp> expirationTime;
260 if (aInitDict.mExpirationTime.IsNull()) {
261 expirationTime.SetNull();
262 } else {
263 expirationTime.SetValue(aInitDict.mExpirationTime.Value());
266 RefPtr<PushSubscription> sub = new PushSubscription(
267 global, aInitDict.mEndpoint, aInitDict.mScope, std::move(expirationTime),
268 std::move(rawKey), std::move(authSecret), std::move(appServerKey));
270 return sub.forget();
273 already_AddRefed<Promise> PushSubscription::Unsubscribe(ErrorResult& aRv) {
274 if (!NS_IsMainThread()) {
275 RefPtr<Promise> p = UnsubscribeFromWorker(aRv);
276 return p.forget();
279 MOZ_ASSERT(mGlobal);
281 nsCOMPtr<nsIPushService> service =
282 do_GetService("@mozilla.org/push/Service;1");
283 if (NS_WARN_IF(!service)) {
284 aRv.Throw(NS_ERROR_FAILURE);
285 return nullptr;
288 nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mGlobal);
289 if (!window) {
290 aRv.Throw(NS_ERROR_FAILURE);
291 return nullptr;
294 RefPtr<Promise> p = Promise::Create(mGlobal, aRv);
295 if (NS_WARN_IF(aRv.Failed())) {
296 return nullptr;
299 RefPtr<UnsubscribeResultCallback> callback = new UnsubscribeResultCallback(p);
300 Unused << NS_WARN_IF(NS_FAILED(service->Unsubscribe(
301 mScope, nsGlobalWindowInner::Cast(window)->GetClientPrincipal(),
302 callback)));
304 return p.forget();
307 void PushSubscription::GetKey(JSContext* aCx, PushEncryptionKeyName aType,
308 JS::MutableHandle<JSObject*> aKey,
309 ErrorResult& aRv) {
310 if (aType == PushEncryptionKeyName::P256dh) {
311 PushUtil::CopyArrayToArrayBuffer(aCx, mRawP256dhKey, aKey, aRv);
312 } else if (aType == PushEncryptionKeyName::Auth) {
313 PushUtil::CopyArrayToArrayBuffer(aCx, mAuthSecret, aKey, aRv);
314 } else {
315 aKey.set(nullptr);
319 void PushSubscription::ToJSON(PushSubscriptionJSON& aJSON, ErrorResult& aRv) {
320 aJSON.mEndpoint.Construct();
321 aJSON.mEndpoint.Value() = mEndpoint;
323 aJSON.mKeys.mP256dh.Construct();
324 nsresult rv = Base64URLEncode(
325 mRawP256dhKey.Length(), mRawP256dhKey.Elements(),
326 Base64URLEncodePaddingPolicy::Omit, aJSON.mKeys.mP256dh.Value());
327 if (NS_WARN_IF(NS_FAILED(rv))) {
328 aRv.Throw(rv);
329 return;
332 aJSON.mKeys.mAuth.Construct();
333 rv = Base64URLEncode(mAuthSecret.Length(), mAuthSecret.Elements(),
334 Base64URLEncodePaddingPolicy::Omit,
335 aJSON.mKeys.mAuth.Value());
336 if (NS_WARN_IF(NS_FAILED(rv))) {
337 aRv.Throw(rv);
338 return;
340 aJSON.mExpirationTime.Construct(mExpirationTime);
343 already_AddRefed<PushSubscriptionOptions> PushSubscription::Options() {
344 RefPtr<PushSubscriptionOptions> options = mOptions;
345 return options.forget();
348 already_AddRefed<Promise> PushSubscription::UnsubscribeFromWorker(
349 ErrorResult& aRv) {
350 WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
351 MOZ_ASSERT(worker);
352 worker->AssertIsOnWorkerThread();
354 nsCOMPtr<nsIGlobalObject> global = worker->GlobalScope();
355 RefPtr<Promise> p = Promise::Create(global, aRv);
356 if (NS_WARN_IF(aRv.Failed())) {
357 return nullptr;
360 RefPtr<PromiseWorkerProxy> proxy = PromiseWorkerProxy::Create(worker, p);
361 if (!proxy) {
362 p->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE);
363 return p.forget();
366 RefPtr<UnsubscribeRunnable> r = new UnsubscribeRunnable(proxy, mScope);
367 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r));
369 return p.forget();
372 } // namespace mozilla::dom