/src/mozilla-central/dom/push/PushSubscription.cpp
Line | Count | Source (jump to first uncovered line) |
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 "nsIPushService.h" |
10 | | #include "nsIScriptObjectPrincipal.h" |
11 | | |
12 | | #include "mozilla/Base64.h" |
13 | | #include "mozilla/Unused.h" |
14 | | |
15 | | #include "mozilla/dom/Promise.h" |
16 | | #include "mozilla/dom/PromiseWorkerProxy.h" |
17 | | #include "mozilla/dom/PushSubscriptionOptions.h" |
18 | | #include "mozilla/dom/PushUtil.h" |
19 | | #include "mozilla/dom/WorkerCommon.h" |
20 | | #include "mozilla/dom/WorkerPrivate.h" |
21 | | #include "mozilla/dom/WorkerScope.h" |
22 | | |
23 | | namespace mozilla { |
24 | | namespace dom { |
25 | | |
26 | | namespace { |
27 | | |
28 | | class UnsubscribeResultCallback final : public nsIUnsubscribeResultCallback |
29 | | { |
30 | | public: |
31 | | NS_DECL_ISUPPORTS |
32 | | |
33 | | explicit UnsubscribeResultCallback(Promise* aPromise) |
34 | | : mPromise(aPromise) |
35 | 0 | { |
36 | 0 | AssertIsOnMainThread(); |
37 | 0 | } |
38 | | |
39 | | NS_IMETHOD |
40 | | OnUnsubscribe(nsresult aStatus, bool aSuccess) override |
41 | 0 | { |
42 | 0 | if (NS_SUCCEEDED(aStatus)) { |
43 | 0 | mPromise->MaybeResolve(aSuccess); |
44 | 0 | } else { |
45 | 0 | mPromise->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE); |
46 | 0 | } |
47 | 0 |
|
48 | 0 | return NS_OK; |
49 | 0 | } |
50 | | |
51 | | private: |
52 | | ~UnsubscribeResultCallback() |
53 | 0 | {} |
54 | | |
55 | | RefPtr<Promise> mPromise; |
56 | | }; |
57 | | |
58 | | NS_IMPL_ISUPPORTS(UnsubscribeResultCallback, nsIUnsubscribeResultCallback) |
59 | | |
60 | | class UnsubscribeResultRunnable final : public WorkerRunnable |
61 | | { |
62 | | public: |
63 | | UnsubscribeResultRunnable(WorkerPrivate* aWorkerPrivate, |
64 | | already_AddRefed<PromiseWorkerProxy>&& aProxy, |
65 | | nsresult aStatus, |
66 | | bool aSuccess) |
67 | | : WorkerRunnable(aWorkerPrivate) |
68 | | , mProxy(std::move(aProxy)) |
69 | | , mStatus(aStatus) |
70 | | , mSuccess(aSuccess) |
71 | 0 | { |
72 | 0 | AssertIsOnMainThread(); |
73 | 0 | } |
74 | | |
75 | | bool |
76 | | WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override |
77 | 0 | { |
78 | 0 | MOZ_ASSERT(aWorkerPrivate); |
79 | 0 | aWorkerPrivate->AssertIsOnWorkerThread(); |
80 | 0 |
|
81 | 0 | RefPtr<Promise> promise = mProxy->WorkerPromise(); |
82 | 0 | if (NS_SUCCEEDED(mStatus)) { |
83 | 0 | promise->MaybeResolve(mSuccess); |
84 | 0 | } else { |
85 | 0 | promise->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE); |
86 | 0 | } |
87 | 0 |
|
88 | 0 | mProxy->CleanUp(); |
89 | 0 |
|
90 | 0 | return true; |
91 | 0 | } |
92 | | private: |
93 | | ~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 | | explicit WorkerUnsubscribeResultCallback(PromiseWorkerProxy* aProxy) |
107 | | : mProxy(aProxy) |
108 | 0 | { |
109 | 0 | AssertIsOnMainThread(); |
110 | 0 | } |
111 | | |
112 | | NS_IMETHOD |
113 | | OnUnsubscribe(nsresult aStatus, bool aSuccess) override |
114 | 0 | { |
115 | 0 | AssertIsOnMainThread(); |
116 | 0 | MOZ_ASSERT(mProxy, "OnUnsubscribe() called twice?"); |
117 | 0 |
|
118 | 0 | MutexAutoLock lock(mProxy->Lock()); |
119 | 0 | if (mProxy->CleanedUp()) { |
120 | 0 | return NS_OK; |
121 | 0 | } |
122 | 0 | |
123 | 0 | WorkerPrivate* worker = mProxy->GetWorkerPrivate(); |
124 | 0 | RefPtr<UnsubscribeResultRunnable> r = |
125 | 0 | new UnsubscribeResultRunnable(worker, mProxy.forget(), aStatus, aSuccess); |
126 | 0 | MOZ_ALWAYS_TRUE(r->Dispatch()); |
127 | 0 |
|
128 | 0 | return NS_OK; |
129 | 0 | } |
130 | | |
131 | | private: |
132 | | ~WorkerUnsubscribeResultCallback() |
133 | 0 | { |
134 | 0 | } |
135 | | |
136 | | RefPtr<PromiseWorkerProxy> mProxy; |
137 | | }; |
138 | | |
139 | | NS_IMPL_ISUPPORTS(WorkerUnsubscribeResultCallback, nsIUnsubscribeResultCallback) |
140 | | |
141 | | class UnsubscribeRunnable final : public Runnable |
142 | | { |
143 | | public: |
144 | | UnsubscribeRunnable(PromiseWorkerProxy* aProxy, const nsAString& aScope) |
145 | | : Runnable("dom::UnsubscribeRunnable") |
146 | | , mProxy(aProxy) |
147 | | , mScope(aScope) |
148 | 0 | { |
149 | 0 | MOZ_ASSERT(aProxy); |
150 | 0 | MOZ_ASSERT(!aScope.IsEmpty()); |
151 | 0 | } |
152 | | |
153 | | NS_IMETHOD |
154 | | Run() override |
155 | 0 | { |
156 | 0 | AssertIsOnMainThread(); |
157 | 0 |
|
158 | 0 | nsCOMPtr<nsIPrincipal> principal; |
159 | 0 |
|
160 | 0 | { |
161 | 0 | MutexAutoLock lock(mProxy->Lock()); |
162 | 0 | if (mProxy->CleanedUp()) { |
163 | 0 | return NS_OK; |
164 | 0 | } |
165 | 0 | principal = mProxy->GetWorkerPrivate()->GetPrincipal(); |
166 | 0 | } |
167 | 0 |
|
168 | 0 | MOZ_ASSERT(principal); |
169 | 0 |
|
170 | 0 | RefPtr<WorkerUnsubscribeResultCallback> callback = |
171 | 0 | new WorkerUnsubscribeResultCallback(mProxy); |
172 | 0 |
|
173 | 0 | 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 | 0 | } |
179 | 0 | |
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 | 0 | } |
184 | 0 | |
185 | 0 | return NS_OK; |
186 | 0 | } |
187 | | |
188 | | private: |
189 | | ~UnsubscribeRunnable() |
190 | 0 | {} |
191 | | |
192 | | RefPtr<PromiseWorkerProxy> mProxy; |
193 | | nsString mScope; |
194 | | }; |
195 | | |
196 | | } // anonymous namespace |
197 | | |
198 | | PushSubscription::PushSubscription(nsIGlobalObject* aGlobal, |
199 | | const nsAString& aEndpoint, |
200 | | const nsAString& aScope, |
201 | | nsTArray<uint8_t>&& aRawP256dhKey, |
202 | | nsTArray<uint8_t>&& aAuthSecret, |
203 | | nsTArray<uint8_t>&& aAppServerKey) |
204 | | : mEndpoint(aEndpoint) |
205 | | , mScope(aScope) |
206 | | , mRawP256dhKey(std::move(aRawP256dhKey)) |
207 | | , mAuthSecret(std::move(aAuthSecret)) |
208 | 0 | { |
209 | 0 | if (NS_IsMainThread()) { |
210 | 0 | mGlobal = aGlobal; |
211 | 0 | } 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 | | WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); |
216 | | MOZ_ASSERT(worker); |
217 | | worker->AssertIsOnWorkerThread(); |
218 | | #endif |
219 | | } |
220 | 0 | mOptions = new PushSubscriptionOptions(mGlobal, std::move(aAppServerKey)); |
221 | 0 | } |
222 | | |
223 | | PushSubscription::~PushSubscription() |
224 | 0 | {} |
225 | | |
226 | | NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushSubscription, mGlobal, mOptions) |
227 | | NS_IMPL_CYCLE_COLLECTING_ADDREF(PushSubscription) |
228 | | 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 | | PushSubscription::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) |
236 | 0 | { |
237 | 0 | return PushSubscription_Binding::Wrap(aCx, this, aGivenProto); |
238 | 0 | } |
239 | | |
240 | | // static |
241 | | already_AddRefed<PushSubscription> |
242 | | PushSubscription::Constructor(GlobalObject& aGlobal, |
243 | | const PushSubscriptionInit& aInitDict, |
244 | | ErrorResult& aRv) |
245 | 0 | { |
246 | 0 | nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports()); |
247 | 0 |
|
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 | 0 | rawKey)) { |
253 | 0 | aRv.Throw(NS_ERROR_OUT_OF_MEMORY); |
254 | 0 | return nullptr; |
255 | 0 | } |
256 | 0 | |
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 | 0 | authSecret)) { |
262 | 0 | aRv.Throw(NS_ERROR_OUT_OF_MEMORY); |
263 | 0 | return nullptr; |
264 | 0 | } |
265 | 0 | |
266 | 0 | nsTArray<uint8_t> appServerKey; |
267 | 0 | if (aInitDict.mAppServerKey.WasPassed() && |
268 | 0 | !aInitDict.mAppServerKey.Value().IsNull()) { |
269 | 0 | 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 | 0 | } |
275 | 0 | } |
276 | 0 | |
277 | 0 | RefPtr<PushSubscription> sub = new PushSubscription(global, |
278 | 0 | aInitDict.mEndpoint, |
279 | 0 | aInitDict.mScope, |
280 | 0 | std::move(rawKey), |
281 | 0 | std::move(authSecret), |
282 | 0 | std::move(appServerKey)); |
283 | 0 |
|
284 | 0 | return sub.forget(); |
285 | 0 | } |
286 | | |
287 | | already_AddRefed<Promise> |
288 | | PushSubscription::Unsubscribe(ErrorResult& aRv) |
289 | 0 | { |
290 | 0 | if (!NS_IsMainThread()) { |
291 | 0 | RefPtr<Promise> p = UnsubscribeFromWorker(aRv); |
292 | 0 | return p.forget(); |
293 | 0 | } |
294 | 0 | |
295 | 0 | MOZ_ASSERT(mGlobal); |
296 | 0 |
|
297 | 0 | 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 | 0 | } |
303 | 0 | |
304 | 0 | nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(mGlobal); |
305 | 0 | if (!sop) { |
306 | 0 | aRv.Throw(NS_ERROR_FAILURE); |
307 | 0 | return nullptr; |
308 | 0 | } |
309 | 0 | |
310 | 0 | RefPtr<Promise> p = Promise::Create(mGlobal, aRv); |
311 | 0 | if (NS_WARN_IF(aRv.Failed())) { |
312 | 0 | return nullptr; |
313 | 0 | } |
314 | 0 | |
315 | 0 | RefPtr<UnsubscribeResultCallback> callback = |
316 | 0 | new UnsubscribeResultCallback(p); |
317 | 0 | Unused << NS_WARN_IF(NS_FAILED( |
318 | 0 | service->Unsubscribe(mScope, sop->GetPrincipal(), callback))); |
319 | 0 |
|
320 | 0 | return p.forget(); |
321 | 0 | } |
322 | | |
323 | | void |
324 | | PushSubscription::GetKey(JSContext* aCx, |
325 | | PushEncryptionKeyName aType, |
326 | | JS::MutableHandle<JSObject*> aKey, |
327 | | ErrorResult& aRv) |
328 | 0 | { |
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 | 0 | } else { |
334 | 0 | aKey.set(nullptr); |
335 | 0 | } |
336 | 0 | } |
337 | | |
338 | | void |
339 | | PushSubscription::ToJSON(PushSubscriptionJSON& aJSON, ErrorResult& aRv) |
340 | 0 | { |
341 | 0 | aJSON.mEndpoint.Construct(); |
342 | 0 | aJSON.mEndpoint.Value() = mEndpoint; |
343 | 0 |
|
344 | 0 | aJSON.mKeys.mP256dh.Construct(); |
345 | 0 | nsresult rv = Base64URLEncode(mRawP256dhKey.Length(), |
346 | 0 | mRawP256dhKey.Elements(), |
347 | 0 | 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 | 0 | } |
353 | 0 | |
354 | 0 | aJSON.mKeys.mAuth.Construct(); |
355 | 0 | rv = Base64URLEncode(mAuthSecret.Length(), mAuthSecret.Elements(), |
356 | 0 | 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 | 0 | } |
362 | 0 | } |
363 | | |
364 | | already_AddRefed<PushSubscriptionOptions> |
365 | | PushSubscription::Options() |
366 | 0 | { |
367 | 0 | RefPtr<PushSubscriptionOptions> options = mOptions; |
368 | 0 | return options.forget(); |
369 | 0 | } |
370 | | |
371 | | already_AddRefed<Promise> |
372 | | PushSubscription::UnsubscribeFromWorker(ErrorResult& aRv) |
373 | 0 | { |
374 | 0 | WorkerPrivate* worker = GetCurrentThreadWorkerPrivate(); |
375 | 0 | MOZ_ASSERT(worker); |
376 | 0 | worker->AssertIsOnWorkerThread(); |
377 | 0 |
|
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 | 0 | } |
383 | 0 | |
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 | 0 | } |
389 | 0 | |
390 | 0 | RefPtr<UnsubscribeRunnable> r = |
391 | 0 | new UnsubscribeRunnable(proxy, mScope); |
392 | 0 | MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r)); |
393 | 0 |
|
394 | 0 | return p.forget(); |
395 | 0 | } |
396 | | |
397 | | } // namespace dom |
398 | | } // namespace mozilla |