Coverage Report

Created: 2018-09-25 14:53

/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