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
{
30 class UnsubscribeResultCallback final
: public nsIUnsubscribeResultCallback
{
34 explicit UnsubscribeResultCallback(Promise
* aPromise
) : mPromise(aPromise
) {
35 AssertIsOnMainThread();
39 OnUnsubscribe(nsresult aStatus
, bool aSuccess
) override
{
40 if (NS_SUCCEEDED(aStatus
)) {
41 mPromise
->MaybeResolve(aSuccess
);
43 mPromise
->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE
);
50 ~UnsubscribeResultCallback() = default;
52 RefPtr
<Promise
> mPromise
;
55 NS_IMPL_ISUPPORTS(UnsubscribeResultCallback
, nsIUnsubscribeResultCallback
)
57 class UnsubscribeResultRunnable final
: public WorkerRunnable
{
59 UnsubscribeResultRunnable(WorkerPrivate
* aWorkerPrivate
,
60 RefPtr
<PromiseWorkerProxy
>&& aProxy
,
61 nsresult aStatus
, bool aSuccess
)
62 : WorkerRunnable(aWorkerPrivate
, "UnsubscribeResultRunnable"),
63 mProxy(std::move(aProxy
)),
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
78 if (NS_SUCCEEDED(mStatus
)) {
79 promise
->MaybeResolve(mSuccess
);
81 promise
->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE
);
90 ~UnsubscribeResultRunnable() = default;
92 RefPtr
<PromiseWorkerProxy
> mProxy
;
97 class WorkerUnsubscribeResultCallback final
98 : public nsIUnsubscribeResultCallback
{
102 explicit WorkerUnsubscribeResultCallback(PromiseWorkerProxy
* aProxy
)
104 AssertIsOnMainThread();
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()) {
117 WorkerPrivate
* worker
= mProxy
->GetWorkerPrivate();
118 RefPtr
<UnsubscribeResultRunnable
> r
= new UnsubscribeResultRunnable(
119 worker
, std::move(mProxy
), aStatus
, aSuccess
);
120 MOZ_ALWAYS_TRUE(r
->Dispatch());
126 ~WorkerUnsubscribeResultCallback() = default;
128 RefPtr
<PromiseWorkerProxy
> mProxy
;
131 NS_IMPL_ISUPPORTS(WorkerUnsubscribeResultCallback
, nsIUnsubscribeResultCallback
)
133 class UnsubscribeRunnable final
: public Runnable
{
135 UnsubscribeRunnable(PromiseWorkerProxy
* aProxy
, const nsAString
& aScope
)
136 : Runnable("dom::UnsubscribeRunnable"), mProxy(aProxy
), mScope(aScope
) {
138 MOZ_ASSERT(!aScope
.IsEmpty());
143 AssertIsOnMainThread();
145 nsCOMPtr
<nsIPrincipal
> principal
;
148 MutexAutoLock
lock(mProxy
->Lock());
149 if (mProxy
->CleanedUp()) {
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);
168 NS_FAILED(service
->Unsubscribe(mScope
, principal
, callback
)))) {
169 callback
->OnUnsubscribe(NS_ERROR_FAILURE
, false);
177 ~UnsubscribeRunnable() = default;
179 RefPtr
<PromiseWorkerProxy
> mProxy
;
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
),
194 mExpirationTime(std::move(aExpirationTime
)),
195 mRawP256dhKey(std::move(aRawP256dhKey
)),
196 mAuthSecret(std::move(aAuthSecret
)) {
197 if (NS_IsMainThread()) {
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();
205 worker
->AssertIsOnWorkerThread();
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
)
221 JSObject
* PushSubscription::WrapObject(JSContext
* aCx
,
222 JS::Handle
<JSObject
*> aGivenProto
) {
223 return PushSubscription_Binding::Wrap(aCx
, this, aGivenProto
);
227 already_AddRefed
<PushSubscription
> PushSubscription::Constructor(
228 GlobalObject
& aGlobal
, const PushSubscriptionInit
& aInitDict
,
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
);
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
);
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
);
259 Nullable
<EpochTimeStamp
> expirationTime
;
260 if (aInitDict
.mExpirationTime
.IsNull()) {
261 expirationTime
.SetNull();
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
));
273 already_AddRefed
<Promise
> PushSubscription::Unsubscribe(ErrorResult
& aRv
) {
274 if (!NS_IsMainThread()) {
275 RefPtr
<Promise
> p
= UnsubscribeFromWorker(aRv
);
281 nsCOMPtr
<nsIPushService
> service
=
282 do_GetService("@mozilla.org/push/Service;1");
283 if (NS_WARN_IF(!service
)) {
284 aRv
.Throw(NS_ERROR_FAILURE
);
288 nsCOMPtr
<nsPIDOMWindowInner
> window
= do_QueryInterface(mGlobal
);
290 aRv
.Throw(NS_ERROR_FAILURE
);
294 RefPtr
<Promise
> p
= Promise::Create(mGlobal
, aRv
);
295 if (NS_WARN_IF(aRv
.Failed())) {
299 RefPtr
<UnsubscribeResultCallback
> callback
= new UnsubscribeResultCallback(p
);
300 Unused
<< NS_WARN_IF(NS_FAILED(service
->Unsubscribe(
301 mScope
, nsGlobalWindowInner::Cast(window
)->GetClientPrincipal(),
307 void PushSubscription::GetKey(JSContext
* aCx
, PushEncryptionKeyName aType
,
308 JS::MutableHandle
<JSObject
*> aKey
,
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
);
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
))) {
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
))) {
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(
350 WorkerPrivate
* worker
= GetCurrentThreadWorkerPrivate();
352 worker
->AssertIsOnWorkerThread();
354 nsCOMPtr
<nsIGlobalObject
> global
= worker
->GlobalScope();
355 RefPtr
<Promise
> p
= Promise::Create(global
, aRv
);
356 if (NS_WARN_IF(aRv
.Failed())) {
360 RefPtr
<PromiseWorkerProxy
> proxy
= PromiseWorkerProxy::Create(worker
, p
);
362 p
->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE
);
366 RefPtr
<UnsubscribeRunnable
> r
= new UnsubscribeRunnable(proxy
, mScope
);
367 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r
));
372 } // namespace mozilla::dom