summaryrefslogtreecommitdiffstats
path: root/dom/push/PushSubscription.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'dom/push/PushSubscription.cpp')
-rw-r--r--dom/push/PushSubscription.cpp372
1 files changed, 372 insertions, 0 deletions
diff --git a/dom/push/PushSubscription.cpp b/dom/push/PushSubscription.cpp
new file mode 100644
index 0000000000..0afb63eee8
--- /dev/null
+++ b/dom/push/PushSubscription.cpp
@@ -0,0 +1,372 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/dom/PushSubscription.h"
+
+#include "nsGlobalWindowInner.h"
+#include "nsIPushService.h"
+#include "nsIScriptObjectPrincipal.h"
+#include "nsServiceManagerUtils.h"
+
+#include "mozilla/Base64.h"
+#include "mozilla/Unused.h"
+
+#include "mozilla/dom/Promise.h"
+#include "mozilla/dom/PromiseWorkerProxy.h"
+#include "mozilla/dom/PushSubscriptionOptions.h"
+#include "mozilla/dom/PushUtil.h"
+#include "mozilla/dom/WorkerCommon.h"
+#include "mozilla/dom/WorkerPrivate.h"
+#include "mozilla/dom/WorkerRunnable.h"
+#include "mozilla/dom/WorkerScope.h"
+
+namespace mozilla::dom {
+
+namespace {
+
+class UnsubscribeResultCallback final : public nsIUnsubscribeResultCallback {
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit UnsubscribeResultCallback(Promise* aPromise) : mPromise(aPromise) {
+ AssertIsOnMainThread();
+ }
+
+ NS_IMETHOD
+ OnUnsubscribe(nsresult aStatus, bool aSuccess) override {
+ if (NS_SUCCEEDED(aStatus)) {
+ mPromise->MaybeResolve(aSuccess);
+ } else {
+ mPromise->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE);
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ ~UnsubscribeResultCallback() = default;
+
+ RefPtr<Promise> mPromise;
+};
+
+NS_IMPL_ISUPPORTS(UnsubscribeResultCallback, nsIUnsubscribeResultCallback)
+
+class UnsubscribeResultRunnable final : public WorkerRunnable {
+ public:
+ UnsubscribeResultRunnable(WorkerPrivate* aWorkerPrivate,
+ RefPtr<PromiseWorkerProxy>&& aProxy,
+ nsresult aStatus, bool aSuccess)
+ : WorkerRunnable(aWorkerPrivate, "UnsubscribeResultRunnable"),
+ mProxy(std::move(aProxy)),
+ mStatus(aStatus),
+ mSuccess(aSuccess) {
+ AssertIsOnMainThread();
+ }
+
+ bool WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<Promise> promise = mProxy->GetWorkerPromise();
+ // Once Worker had already started shutdown, workerPromise would be nullptr
+ if (!promise) {
+ return true;
+ }
+ if (NS_SUCCEEDED(mStatus)) {
+ promise->MaybeResolve(mSuccess);
+ } else {
+ promise->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE);
+ }
+
+ mProxy->CleanUp();
+
+ return true;
+ }
+
+ private:
+ ~UnsubscribeResultRunnable() = default;
+
+ RefPtr<PromiseWorkerProxy> mProxy;
+ nsresult mStatus;
+ bool mSuccess;
+};
+
+class WorkerUnsubscribeResultCallback final
+ : public nsIUnsubscribeResultCallback {
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit WorkerUnsubscribeResultCallback(PromiseWorkerProxy* aProxy)
+ : mProxy(aProxy) {
+ AssertIsOnMainThread();
+ }
+
+ NS_IMETHOD
+ OnUnsubscribe(nsresult aStatus, bool aSuccess) override {
+ AssertIsOnMainThread();
+ MOZ_ASSERT(mProxy, "OnUnsubscribe() called twice?");
+
+ MutexAutoLock lock(mProxy->Lock());
+ if (mProxy->CleanedUp()) {
+ return NS_OK;
+ }
+
+ WorkerPrivate* worker = mProxy->GetWorkerPrivate();
+ RefPtr<UnsubscribeResultRunnable> r = new UnsubscribeResultRunnable(
+ worker, std::move(mProxy), aStatus, aSuccess);
+ MOZ_ALWAYS_TRUE(r->Dispatch());
+
+ return NS_OK;
+ }
+
+ private:
+ ~WorkerUnsubscribeResultCallback() = default;
+
+ RefPtr<PromiseWorkerProxy> mProxy;
+};
+
+NS_IMPL_ISUPPORTS(WorkerUnsubscribeResultCallback, nsIUnsubscribeResultCallback)
+
+class UnsubscribeRunnable final : public Runnable {
+ public:
+ UnsubscribeRunnable(PromiseWorkerProxy* aProxy, const nsAString& aScope)
+ : Runnable("dom::UnsubscribeRunnable"), mProxy(aProxy), mScope(aScope) {
+ MOZ_ASSERT(aProxy);
+ MOZ_ASSERT(!aScope.IsEmpty());
+ }
+
+ NS_IMETHOD
+ Run() override {
+ AssertIsOnMainThread();
+
+ nsCOMPtr<nsIPrincipal> principal;
+
+ {
+ MutexAutoLock lock(mProxy->Lock());
+ if (mProxy->CleanedUp()) {
+ return NS_OK;
+ }
+ principal = mProxy->GetWorkerPrivate()->GetPrincipal();
+ }
+
+ MOZ_ASSERT(principal);
+
+ RefPtr<WorkerUnsubscribeResultCallback> callback =
+ new WorkerUnsubscribeResultCallback(mProxy);
+
+ nsCOMPtr<nsIPushService> service =
+ do_GetService("@mozilla.org/push/Service;1");
+ if (NS_WARN_IF(!service)) {
+ callback->OnUnsubscribe(NS_ERROR_FAILURE, false);
+ return NS_OK;
+ }
+
+ if (NS_WARN_IF(
+ NS_FAILED(service->Unsubscribe(mScope, principal, callback)))) {
+ callback->OnUnsubscribe(NS_ERROR_FAILURE, false);
+ return NS_OK;
+ }
+
+ return NS_OK;
+ }
+
+ private:
+ ~UnsubscribeRunnable() = default;
+
+ RefPtr<PromiseWorkerProxy> mProxy;
+ nsString mScope;
+};
+
+} // anonymous namespace
+
+PushSubscription::PushSubscription(nsIGlobalObject* aGlobal,
+ const nsAString& aEndpoint,
+ const nsAString& aScope,
+ Nullable<EpochTimeStamp>&& aExpirationTime,
+ nsTArray<uint8_t>&& aRawP256dhKey,
+ nsTArray<uint8_t>&& aAuthSecret,
+ nsTArray<uint8_t>&& aAppServerKey)
+ : mEndpoint(aEndpoint),
+ mScope(aScope),
+ mExpirationTime(std::move(aExpirationTime)),
+ mRawP256dhKey(std::move(aRawP256dhKey)),
+ mAuthSecret(std::move(aAuthSecret)) {
+ if (NS_IsMainThread()) {
+ mGlobal = aGlobal;
+ } else {
+#ifdef DEBUG
+ // There's only one global on a worker, so we don't need to pass a global
+ // object to the constructor.
+ WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(worker);
+ worker->AssertIsOnWorkerThread();
+#endif
+ }
+ mOptions = new PushSubscriptionOptions(mGlobal, std::move(aAppServerKey));
+}
+
+PushSubscription::~PushSubscription() = default;
+
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushSubscription, mGlobal, mOptions)
+NS_IMPL_CYCLE_COLLECTING_ADDREF(PushSubscription)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(PushSubscription)
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushSubscription)
+ NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
+ NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+JSObject* PushSubscription::WrapObject(JSContext* aCx,
+ JS::Handle<JSObject*> aGivenProto) {
+ return PushSubscription_Binding::Wrap(aCx, this, aGivenProto);
+}
+
+// static
+already_AddRefed<PushSubscription> PushSubscription::Constructor(
+ GlobalObject& aGlobal, const PushSubscriptionInit& aInitDict,
+ ErrorResult& aRv) {
+ nsCOMPtr<nsIGlobalObject> global = do_QueryInterface(aGlobal.GetAsSupports());
+
+ nsTArray<uint8_t> rawKey;
+ if (aInitDict.mP256dhKey.WasPassed() &&
+ !aInitDict.mP256dhKey.Value().IsNull() &&
+ !aInitDict.mP256dhKey.Value().Value().AppendDataTo(rawKey)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+
+ nsTArray<uint8_t> authSecret;
+ if (aInitDict.mAuthSecret.WasPassed() &&
+ !aInitDict.mAuthSecret.Value().IsNull() &&
+ !aInitDict.mAuthSecret.Value().Value().AppendDataTo(authSecret)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+
+ nsTArray<uint8_t> appServerKey;
+ if (aInitDict.mAppServerKey.WasPassed() &&
+ !aInitDict.mAppServerKey.Value().IsNull()) {
+ const OwningArrayBufferViewOrArrayBuffer& bufferSource =
+ aInitDict.mAppServerKey.Value().Value();
+ if (!PushUtil::CopyBufferSourceToArray(bufferSource, appServerKey)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+ }
+
+ Nullable<EpochTimeStamp> expirationTime;
+ if (aInitDict.mExpirationTime.IsNull()) {
+ expirationTime.SetNull();
+ } else {
+ expirationTime.SetValue(aInitDict.mExpirationTime.Value());
+ }
+
+ RefPtr<PushSubscription> sub = new PushSubscription(
+ global, aInitDict.mEndpoint, aInitDict.mScope, std::move(expirationTime),
+ std::move(rawKey), std::move(authSecret), std::move(appServerKey));
+
+ return sub.forget();
+}
+
+already_AddRefed<Promise> PushSubscription::Unsubscribe(ErrorResult& aRv) {
+ if (!NS_IsMainThread()) {
+ RefPtr<Promise> p = UnsubscribeFromWorker(aRv);
+ return p.forget();
+ }
+
+ MOZ_ASSERT(mGlobal);
+
+ nsCOMPtr<nsIPushService> service =
+ do_GetService("@mozilla.org/push/Service;1");
+ if (NS_WARN_IF(!service)) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ nsCOMPtr<nsPIDOMWindowInner> window = do_QueryInterface(mGlobal);
+ if (!window) {
+ aRv.Throw(NS_ERROR_FAILURE);
+ return nullptr;
+ }
+
+ RefPtr<Promise> p = Promise::Create(mGlobal, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ RefPtr<UnsubscribeResultCallback> callback = new UnsubscribeResultCallback(p);
+ Unused << NS_WARN_IF(NS_FAILED(service->Unsubscribe(
+ mScope, nsGlobalWindowInner::Cast(window)->GetClientPrincipal(),
+ callback)));
+
+ return p.forget();
+}
+
+void PushSubscription::GetKey(JSContext* aCx, PushEncryptionKeyName aType,
+ JS::MutableHandle<JSObject*> aKey,
+ ErrorResult& aRv) {
+ if (aType == PushEncryptionKeyName::P256dh) {
+ PushUtil::CopyArrayToArrayBuffer(aCx, mRawP256dhKey, aKey, aRv);
+ } else if (aType == PushEncryptionKeyName::Auth) {
+ PushUtil::CopyArrayToArrayBuffer(aCx, mAuthSecret, aKey, aRv);
+ } else {
+ aKey.set(nullptr);
+ }
+}
+
+void PushSubscription::ToJSON(PushSubscriptionJSON& aJSON, ErrorResult& aRv) {
+ aJSON.mEndpoint.Construct();
+ aJSON.mEndpoint.Value() = mEndpoint;
+
+ aJSON.mKeys.mP256dh.Construct();
+ nsresult rv = Base64URLEncode(
+ mRawP256dhKey.Length(), mRawP256dhKey.Elements(),
+ Base64URLEncodePaddingPolicy::Omit, aJSON.mKeys.mP256dh.Value());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+
+ aJSON.mKeys.mAuth.Construct();
+ rv = Base64URLEncode(mAuthSecret.Length(), mAuthSecret.Elements(),
+ Base64URLEncodePaddingPolicy::Omit,
+ aJSON.mKeys.mAuth.Value());
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ aRv.Throw(rv);
+ return;
+ }
+ aJSON.mExpirationTime.Construct(mExpirationTime);
+}
+
+already_AddRefed<PushSubscriptionOptions> PushSubscription::Options() {
+ RefPtr<PushSubscriptionOptions> options = mOptions;
+ return options.forget();
+}
+
+already_AddRefed<Promise> PushSubscription::UnsubscribeFromWorker(
+ ErrorResult& aRv) {
+ WorkerPrivate* worker = GetCurrentThreadWorkerPrivate();
+ MOZ_ASSERT(worker);
+ worker->AssertIsOnWorkerThread();
+
+ nsCOMPtr<nsIGlobalObject> global = worker->GlobalScope();
+ RefPtr<Promise> p = Promise::Create(global, aRv);
+ if (NS_WARN_IF(aRv.Failed())) {
+ return nullptr;
+ }
+
+ RefPtr<PromiseWorkerProxy> proxy = PromiseWorkerProxy::Create(worker, p);
+ if (!proxy) {
+ p->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE);
+ return p.forget();
+ }
+
+ RefPtr<UnsubscribeRunnable> r = new UnsubscribeRunnable(proxy, mScope);
+ MOZ_ALWAYS_SUCCEEDS(NS_DispatchToMainThread(r));
+
+ return p.forget();
+}
+
+} // namespace mozilla::dom