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.cpp398
1 files changed, 398 insertions, 0 deletions
diff --git a/dom/push/PushSubscription.cpp b/dom/push/PushSubscription.cpp
new file mode 100644
index 000000000..bfe8b5dd9
--- /dev/null
+++ b/dom/push/PushSubscription.cpp
@@ -0,0 +1,398 @@
+/* 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 "nsIPushService.h"
+#include "nsIScriptObjectPrincipal.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/WorkerPrivate.h"
+#include "mozilla/dom/WorkerScope.h"
+#include "mozilla/dom/workers/Workers.h"
+
+namespace mozilla {
+namespace dom {
+
+using namespace workers;
+
+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()
+ {}
+
+ RefPtr<Promise> mPromise;
+};
+
+NS_IMPL_ISUPPORTS(UnsubscribeResultCallback, nsIUnsubscribeResultCallback)
+
+class UnsubscribeResultRunnable final : public WorkerRunnable
+{
+public:
+ UnsubscribeResultRunnable(WorkerPrivate* aWorkerPrivate,
+ already_AddRefed<PromiseWorkerProxy>&& aProxy,
+ nsresult aStatus,
+ bool aSuccess)
+ : WorkerRunnable(aWorkerPrivate)
+ , mProxy(Move(aProxy))
+ , mStatus(aStatus)
+ , mSuccess(aSuccess)
+ {
+ AssertIsOnMainThread();
+ }
+
+ bool
+ WorkerRun(JSContext* aCx, WorkerPrivate* aWorkerPrivate) override
+ {
+ MOZ_ASSERT(aWorkerPrivate);
+ aWorkerPrivate->AssertIsOnWorkerThread();
+
+ RefPtr<Promise> promise = mProxy->WorkerPromise();
+ if (NS_SUCCEEDED(mStatus)) {
+ promise->MaybeResolve(mSuccess);
+ } else {
+ promise->MaybeReject(NS_ERROR_DOM_PUSH_SERVICE_UNREACHABLE);
+ }
+
+ mProxy->CleanUp();
+
+ return true;
+ }
+private:
+ ~UnsubscribeResultRunnable()
+ {}
+
+ 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, mProxy.forget(), aStatus, aSuccess);
+ MOZ_ALWAYS_TRUE(r->Dispatch());
+
+ return NS_OK;
+ }
+
+private:
+ ~WorkerUnsubscribeResultCallback()
+ {
+ }
+
+ RefPtr<PromiseWorkerProxy> mProxy;
+};
+
+NS_IMPL_ISUPPORTS(WorkerUnsubscribeResultCallback, nsIUnsubscribeResultCallback)
+
+class UnsubscribeRunnable final : public Runnable
+{
+public:
+ UnsubscribeRunnable(PromiseWorkerProxy* aProxy,
+ const nsAString& aScope)
+ : 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()
+ {}
+
+ RefPtr<PromiseWorkerProxy> mProxy;
+ nsString mScope;
+};
+
+} // anonymous namespace
+
+PushSubscription::PushSubscription(nsIGlobalObject* aGlobal,
+ const nsAString& aEndpoint,
+ const nsAString& aScope,
+ nsTArray<uint8_t>&& aRawP256dhKey,
+ nsTArray<uint8_t>&& aAuthSecret,
+ nsTArray<uint8_t>&& aAppServerKey)
+ : mEndpoint(aEndpoint)
+ , mScope(aScope)
+ , mRawP256dhKey(Move(aRawP256dhKey))
+ , mAuthSecret(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, Move(aAppServerKey));
+}
+
+PushSubscription::~PushSubscription()
+{}
+
+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 PushSubscriptionBinding::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() &&
+ !PushUtil::CopyArrayBufferToArray(aInitDict.mP256dhKey.Value().Value(),
+ rawKey)) {
+ aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
+ return nullptr;
+ }
+
+ nsTArray<uint8_t> authSecret;
+ if (aInitDict.mAuthSecret.WasPassed() &&
+ !aInitDict.mAuthSecret.Value().IsNull() &&
+ !PushUtil::CopyArrayBufferToArray(aInitDict.mAuthSecret.Value().Value(),
+ 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;
+ }
+ }
+
+ RefPtr<PushSubscription> sub = new PushSubscription(global,
+ aInitDict.mEndpoint,
+ aInitDict.mScope,
+ Move(rawKey),
+ Move(authSecret),
+ 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<nsIScriptObjectPrincipal> sop = do_QueryInterface(mGlobal);
+ if (!sop) {
+ 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, sop->GetPrincipal(), 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;
+ }
+}
+
+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 dom
+} // namespace mozilla