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 "nsIPushService.h"
10 #include "nsIScriptObjectPrincipal.h"
11 #include "nsServiceManagerUtils.h"
13 #include "mozilla/Base64.h"
14 #include "mozilla/Unused.h"
16 #include "mozilla/dom/Promise.h"
17 #include "mozilla/dom/PromiseWorkerProxy.h"
18 #include "mozilla/dom/PushSubscriptionOptions.h"
19 #include "mozilla/dom/PushUtil.h"
20 #include "mozilla/dom/WorkerCommon.h"
21 #include "mozilla/dom/WorkerPrivate.h"
22 #include "mozilla/dom/WorkerRunnable.h"
23 #include "mozilla/dom/WorkerScope.h"
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
),
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
->WorkerPromise();
74 if (NS_SUCCEEDED(mStatus
)) {
75 promise
->MaybeResolve(mSuccess
);
77 promise
->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE
);
86 ~UnsubscribeResultRunnable() = default;
88 RefPtr
<PromiseWorkerProxy
> mProxy
;
93 class WorkerUnsubscribeResultCallback final
94 : public nsIUnsubscribeResultCallback
{
98 explicit WorkerUnsubscribeResultCallback(PromiseWorkerProxy
* aProxy
)
100 AssertIsOnMainThread();
104 OnUnsubscribe(nsresult aStatus
, bool aSuccess
) override
{
105 AssertIsOnMainThread();
106 MOZ_ASSERT(mProxy
, "OnUnsubscribe() called twice?");
108 MutexAutoLock
lock(mProxy
->Lock());
109 if (mProxy
->CleanedUp()) {
113 WorkerPrivate
* worker
= mProxy
->GetWorkerPrivate();
114 RefPtr
<UnsubscribeResultRunnable
> r
= new UnsubscribeResultRunnable(
115 worker
, std::move(mProxy
), aStatus
, aSuccess
);
116 MOZ_ALWAYS_TRUE(r
->Dispatch());
122 ~WorkerUnsubscribeResultCallback() = default;
124 RefPtr
<PromiseWorkerProxy
> mProxy
;
127 NS_IMPL_ISUPPORTS(WorkerUnsubscribeResultCallback
, nsIUnsubscribeResultCallback
)
129 class UnsubscribeRunnable final
: public Runnable
{
131 UnsubscribeRunnable(PromiseWorkerProxy
* aProxy
, const nsAString
& aScope
)
132 : Runnable("dom::UnsubscribeRunnable"), mProxy(aProxy
), mScope(aScope
) {
134 MOZ_ASSERT(!aScope
.IsEmpty());
139 AssertIsOnMainThread();
141 nsCOMPtr
<nsIPrincipal
> principal
;
144 MutexAutoLock
lock(mProxy
->Lock());
145 if (mProxy
->CleanedUp()) {
148 principal
= mProxy
->GetWorkerPrivate()->GetPrincipal();
151 MOZ_ASSERT(principal
);
153 RefPtr
<WorkerUnsubscribeResultCallback
> callback
=
154 new WorkerUnsubscribeResultCallback(mProxy
);
156 nsCOMPtr
<nsIPushService
> service
=
157 do_GetService("@mozilla.org/push/Service;1");
158 if (NS_WARN_IF(!service
)) {
159 callback
->OnUnsubscribe(NS_ERROR_FAILURE
, false);
164 NS_FAILED(service
->Unsubscribe(mScope
, principal
, callback
)))) {
165 callback
->OnUnsubscribe(NS_ERROR_FAILURE
, false);
173 ~UnsubscribeRunnable() = default;
175 RefPtr
<PromiseWorkerProxy
> mProxy
;
179 } // anonymous namespace
181 PushSubscription::PushSubscription(nsIGlobalObject
* aGlobal
,
182 const nsAString
& aEndpoint
,
183 const nsAString
& aScope
,
184 Nullable
<EpochTimeStamp
>&& aExpirationTime
,
185 nsTArray
<uint8_t>&& aRawP256dhKey
,
186 nsTArray
<uint8_t>&& aAuthSecret
,
187 nsTArray
<uint8_t>&& aAppServerKey
)
188 : mEndpoint(aEndpoint
),
190 mExpirationTime(std::move(aExpirationTime
)),
191 mRawP256dhKey(std::move(aRawP256dhKey
)),
192 mAuthSecret(std::move(aAuthSecret
)) {
193 if (NS_IsMainThread()) {
197 // There's only one global on a worker, so we don't need to pass a global
198 // object to the constructor.
199 WorkerPrivate
* worker
= GetCurrentThreadWorkerPrivate();
201 worker
->AssertIsOnWorkerThread();
204 mOptions
= new PushSubscriptionOptions(mGlobal
, std::move(aAppServerKey
));
207 PushSubscription::~PushSubscription() = default;
209 NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushSubscription
, mGlobal
, mOptions
)
210 NS_IMPL_CYCLE_COLLECTING_ADDREF(PushSubscription
)
211 NS_IMPL_CYCLE_COLLECTING_RELEASE(PushSubscription
)
212 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushSubscription
)
213 NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
214 NS_INTERFACE_MAP_ENTRY(nsISupports
)
217 JSObject
* PushSubscription::WrapObject(JSContext
* aCx
,
218 JS::Handle
<JSObject
*> aGivenProto
) {
219 return PushSubscription_Binding::Wrap(aCx
, this, aGivenProto
);
223 already_AddRefed
<PushSubscription
> PushSubscription::Constructor(
224 GlobalObject
& aGlobal
, const PushSubscriptionInit
& aInitDict
,
226 nsCOMPtr
<nsIGlobalObject
> global
= do_QueryInterface(aGlobal
.GetAsSupports());
228 nsTArray
<uint8_t> rawKey
;
229 if (aInitDict
.mP256dhKey
.WasPassed() &&
230 !aInitDict
.mP256dhKey
.Value().IsNull() &&
231 !PushUtil::CopyArrayBufferToArray(aInitDict
.mP256dhKey
.Value().Value(),
233 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
237 nsTArray
<uint8_t> authSecret
;
238 if (aInitDict
.mAuthSecret
.WasPassed() &&
239 !aInitDict
.mAuthSecret
.Value().IsNull() &&
240 !PushUtil::CopyArrayBufferToArray(aInitDict
.mAuthSecret
.Value().Value(),
242 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
246 nsTArray
<uint8_t> appServerKey
;
247 if (aInitDict
.mAppServerKey
.WasPassed() &&
248 !aInitDict
.mAppServerKey
.Value().IsNull()) {
249 const OwningArrayBufferViewOrArrayBuffer
& bufferSource
=
250 aInitDict
.mAppServerKey
.Value().Value();
251 if (!PushUtil::CopyBufferSourceToArray(bufferSource
, appServerKey
)) {
252 aRv
.Throw(NS_ERROR_OUT_OF_MEMORY
);
257 Nullable
<EpochTimeStamp
> expirationTime
;
258 if (aInitDict
.mExpirationTime
.IsNull()) {
259 expirationTime
.SetNull();
261 expirationTime
.SetValue(aInitDict
.mExpirationTime
.Value());
264 RefPtr
<PushSubscription
> sub
= new PushSubscription(
265 global
, aInitDict
.mEndpoint
, aInitDict
.mScope
, std::move(expirationTime
),
266 std::move(rawKey
), std::move(authSecret
), std::move(appServerKey
));
271 already_AddRefed
<Promise
> PushSubscription::Unsubscribe(ErrorResult
& aRv
) {
272 if (!NS_IsMainThread()) {
273 RefPtr
<Promise
> p
= UnsubscribeFromWorker(aRv
);
279 nsCOMPtr
<nsIPushService
> service
=
280 do_GetService("@mozilla.org/push/Service;1");
281 if (NS_WARN_IF(!service
)) {
282 aRv
.Throw(NS_ERROR_FAILURE
);
286 nsCOMPtr
<nsIScriptObjectPrincipal
> sop
= do_QueryInterface(mGlobal
);
288 aRv
.Throw(NS_ERROR_FAILURE
);
292 RefPtr
<Promise
> p
= Promise::Create(mGlobal
, aRv
);
293 if (NS_WARN_IF(aRv
.Failed())) {
297 RefPtr
<UnsubscribeResultCallback
> callback
= new UnsubscribeResultCallback(p
);
298 Unused
<< NS_WARN_IF(
299 NS_FAILED(service
->Unsubscribe(mScope
, sop
->GetPrincipal(), callback
)));
304 void PushSubscription::GetKey(JSContext
* aCx
, PushEncryptionKeyName aType
,
305 JS::MutableHandle
<JSObject
*> aKey
,
307 if (aType
== PushEncryptionKeyName::P256dh
) {
308 PushUtil::CopyArrayToArrayBuffer(aCx
, mRawP256dhKey
, aKey
, aRv
);
309 } else if (aType
== PushEncryptionKeyName::Auth
) {
310 PushUtil::CopyArrayToArrayBuffer(aCx
, mAuthSecret
, aKey
, aRv
);
316 void PushSubscription::ToJSON(PushSubscriptionJSON
& aJSON
, ErrorResult
& aRv
) {
317 aJSON
.mEndpoint
.Construct();
318 aJSON
.mEndpoint
.Value() = mEndpoint
;
320 aJSON
.mKeys
.mP256dh
.Construct();
321 nsresult rv
= Base64URLEncode(
322 mRawP256dhKey
.Length(), mRawP256dhKey
.Elements(),
323 Base64URLEncodePaddingPolicy::Omit
, aJSON
.mKeys
.mP256dh
.Value());
324 if (NS_WARN_IF(NS_FAILED(rv
))) {
329 aJSON
.mKeys
.mAuth
.Construct();
330 rv
= Base64URLEncode(mAuthSecret
.Length(), mAuthSecret
.Elements(),
331 Base64URLEncodePaddingPolicy::Omit
,
332 aJSON
.mKeys
.mAuth
.Value());
333 if (NS_WARN_IF(NS_FAILED(rv
))) {
337 aJSON
.mExpirationTime
.Construct(mExpirationTime
);
340 already_AddRefed
<PushSubscriptionOptions
> PushSubscription::Options() {
341 RefPtr
<PushSubscriptionOptions
> options
= mOptions
;
342 return options
.forget();
345 already_AddRefed
<Promise
> PushSubscription::UnsubscribeFromWorker(
347 WorkerPrivate
* worker
= GetCurrentThreadWorkerPrivate();
349 worker
->AssertIsOnWorkerThread();
351 nsCOMPtr
<nsIGlobalObject
> global
= worker
->GlobalScope();
352 RefPtr
<Promise
> p
= Promise::Create(global
, aRv
);
353 if (NS_WARN_IF(aRv
.Failed())) {
357 RefPtr
<PromiseWorkerProxy
> proxy
= PromiseWorkerProxy::Create(worker
, p
);
359 p
->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE
);
363 RefPtr
<UnsubscribeRunnable
> r
= new UnsubscribeRunnable(proxy
, mScope
);
364 MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r
));
370 } // namespace mozilla