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