--- a/dom/base/StructuredCloneHolder.cpp
+++ b/dom/base/StructuredCloneHolder.cpp
@@ -40,16 +40,20 @@
#ifdef MOZ_NFC
#include "mozilla/dom/MozNDEFRecord.h"
#endif // MOZ_NFC
#ifdef MOZ_WEBRTC
#include "mozilla/dom/RTCCertificate.h"
#include "mozilla/dom/RTCCertificateBinding.h"
#endif
+#ifndef MOZ_SIMPLEPUSH
+#include "mozilla/dom/PushManager.h"
+#endif
+
using namespace mozilla::ipc;
namespace mozilla {
namespace dom {
namespace {
JSObject*
@@ -410,16 +414,52 @@ StructuredCloneHolder::ReadFullySerializ
result = nullptr;
} else {
result = key->WrapObject(aCx, nullptr);
}
}
return result;
}
+#ifndef MOZ_SIMPLEPUSH
+ if (aTag == SCTAG_DOM_PUSH_SUBSCRIPTION) {
+ JS::Rooted<JSObject*> result(aCx);
+
+ nsTArray<uint8_t> rawKey;
+ nsTArray<uint8_t> authSecret;
+
+ // The PushSubscription interface has two implementations: PushSubscription
+ // for the main thread, and WorkerPushSubscription for workers. Use the
+ // appropriate implementation for the current thread.
+ if (NS_IsMainThread()) {
+ nsIGlobalObject *global = xpc::NativeGlobal(JS::CurrentGlobalOrNull(aCx));
+ if (!global) {
+ return nullptr;
+ }
+ RefPtr<PushSubscription> subscription = new PushSubscription(global,
+ EmptyString(), EmptyString(), rawKey, authSecret);
+ if (!subscription->ReadStructuredClone(aCx, aReader)) {
+ result = nullptr;
+ } else {
+ result = subscription->WrapObject(aCx, nullptr);
+ }
+ } else {
+ RefPtr<WorkerPushSubscription> subscription =
+ new WorkerPushSubscription(EmptyString(), EmptyString(), rawKey,
+ authSecret);
+ if (!subscription->ReadStructuredClone(aCx, aReader)) {
+ result = nullptr;
+ } else {
+ result = subscription->WrapObject(aCx, nullptr);
+ }
+ }
+ return result;
+ }
+#endif
+
if (aTag == SCTAG_DOM_NULL_PRINCIPAL ||
aTag == SCTAG_DOM_SYSTEM_PRINCIPAL ||
aTag == SCTAG_DOM_CONTENT_PRINCIPAL) {
JSPrincipals* prin;
if (!nsJSPrincipals::ReadKnownPrincipalType(aCx, aReader, aTag, &prin)) {
return nullptr;
}
// nsJSPrincipals::ReadKnownPrincipalType addrefs for us, but because of the
@@ -509,16 +549,34 @@ StructuredCloneHolder::WriteFullySeriali
CryptoKey* key = nullptr;
if (NS_SUCCEEDED(UNWRAP_OBJECT(CryptoKey, aObj, key))) {
MOZ_ASSERT(NS_IsMainThread());
return JS_WriteUint32Pair(aWriter, SCTAG_DOM_WEBCRYPTO_KEY, 0) &&
key->WriteStructuredClone(aWriter);
}
}
+#ifndef MOZ_SIMPLEPUSH
+ {
+ if (NS_IsMainThread()) {
+ PushSubscription* subscription = nullptr;
+ if (NS_SUCCEEDED(UNWRAP_OBJECT(PushSubscription, aObj, subscription))) {
+ return JS_WriteUint32Pair(aWriter, SCTAG_DOM_PUSH_SUBSCRIPTION, 0) &&
+ subscription->WriteStructuredClone(aCx, aWriter);
+ }
+ } else {
+ WorkerPushSubscription* subscription = nullptr;
+ if (NS_SUCCEEDED(UNWRAP_WORKER_OBJECT(PushSubscription, aObj, subscription))) {
+ return JS_WriteUint32Pair(aWriter, SCTAG_DOM_PUSH_SUBSCRIPTION, 0) &&
+ subscription->WriteStructuredClone(aCx, aWriter);
+ }
+ }
+ }
+#endif
+
#ifdef MOZ_WEBRTC
{
// Handle WebRTC Certificate cloning
RTCCertificate* cert = nullptr;
if (NS_SUCCEEDED(UNWRAP_OBJECT(RTCCertificate, aObj, cert))) {
MOZ_ASSERT(NS_IsMainThread());
return JS_WriteUint32Pair(aWriter, SCTAG_DOM_RTC_CERTIFICATE, 0) &&
cert->WriteStructuredClone(aWriter);
--- a/dom/base/StructuredCloneTags.h
+++ b/dom/base/StructuredCloneTags.h
@@ -46,15 +46,17 @@ enum StructuredCloneTags {
SCTAG_DOM_RTC_CERTIFICATE,
SCTAG_DOM_FORMDATA,
// This tag is for OffscreenCanvas.
SCTAG_DOM_CANVAS,
- SCTAG_DOM_MAX
+ SCTAG_DOM_MAX,
+
+ SCTAG_DOM_PUSH_SUBSCRIPTION
};
} // namespace dom
} // namespace mozilla
#endif // StructuredCloneTags_h__
--- a/dom/push/Push.js
+++ b/dom/push/Push.js
@@ -204,17 +204,16 @@ PushSubscriptionCallback.prototype = {
}
let publicKey = this._getKey(subscription, "p256dh");
let authSecret = this._getKey(subscription, "auth");
let sub = new pushManager._window.PushSubscription(subscription.endpoint,
pushManager._scope,
publicKey,
authSecret);
- sub.setPrincipal(pushManager._principal);
this.resolve(sub);
},
_getKey: function(subscription, name) {
let outKeyLen = {};
let rawKey = subscription.getKey(name, outKeyLen);
if (!outKeyLen.value) {
return null;
--- a/dom/push/PushManager.cpp
+++ b/dom/push/PushManager.cpp
@@ -11,25 +11,28 @@
#include "mozilla/Services.h"
#include "mozilla/unused.h"
#include "mozilla/dom/PushManagerBinding.h"
#include "mozilla/dom/PushSubscriptionBinding.h"
#include "mozilla/dom/ServiceWorkerGlobalScopeBinding.h"
#include "mozilla/dom/Promise.h"
#include "mozilla/dom/PromiseWorkerProxy.h"
+#include "mozilla/dom/ToJSValue.h"
#include "nsIGlobalObject.h"
#include "nsIPermissionManager.h"
#include "nsIPrincipal.h"
#include "nsIPushService.h"
+#include "nsIScriptObjectPrincipal.h"
#include "nsComponentManagerUtils.h"
#include "nsFrameMessageManager.h"
#include "nsContentCID.h"
+#include "nsJSUtils.h"
#include "WorkerRunnable.h"
#include "WorkerPrivate.h"
#include "WorkerScope.h"
namespace mozilla {
namespace dom {
@@ -81,16 +84,88 @@ SubscriptionToJSON(PushSubscriptionJSON&
Unused << NS_WARN_IF(NS_FAILED(rv));
aJSON.mKeys.mAuth.Construct();
rv = Base64URLEncode(aAuthSecret.Length(), aAuthSecret.Elements(),
aJSON.mKeys.mAuth.Value());
Unused << NS_WARN_IF(NS_FAILED(rv));
}
+/* Structured clone helpers for subscriptions. */
+
+bool
+ReadByteArray(JSContext* aCx, JSStructuredCloneReader* aReader,
+ nsTArray<uint8_t>& aArray)
+{
+ uint32_t length, zero;
+ if (!JS_ReadUint32Pair(aReader, &length, &zero)) {
+ return false;
+ }
+ if (length > 0) {
+ return aArray.SetLength(length, fallible) &&
+ JS_ReadBytes(aReader, aArray.Elements(), aArray.Length());
+ }
+ return true;
+}
+
+bool
+WriteByteArray(JSContext* aCx, JSStructuredCloneWriter* aWriter,
+ const nsTArray<uint8_t>& aArray)
+{
+ uint32_t length = aArray.Length();
+ if (JS_WriteUint32Pair(aWriter, length, 0)) {
+ if (length > 0) {
+ return JS_WriteBytes(aWriter, aArray.Elements(), length);
+ }
+ return true;
+ }
+ return false;
+}
+
+bool
+WriteSubscriptionClone(JSContext* aCx, JSStructuredCloneWriter* aWriter,
+ const nsAString& aEndpoint,
+ const nsTArray<uint8_t>& aRawP256dhKey,
+ const nsTArray<uint8_t>& aAuthSecret)
+{
+ JS::Rooted<JS::Value> endpointValue(aCx);
+ if (!ToJSValue(aCx, aEndpoint, &endpointValue)) {
+ return false;
+ }
+ JS::Rooted<JSString*> endpoint(aCx, endpointValue.toString());
+ if (!endpoint ||
+ !JS_WriteString(aWriter, endpoint) ||
+ !WriteByteArray(aCx, aWriter, aRawP256dhKey) ||
+ !WriteByteArray(aCx, aWriter, aAuthSecret)) {
+ return false;
+ }
+ return true;
+}
+
+bool
+ReadSubscriptionClone(JSContext* aCx, JSStructuredCloneReader* aReader,
+ nsAString& aEndpoint, nsTArray<uint8_t>& aRawP256dhKey,
+ nsTArray<uint8_t>& aAuthSecret)
+{
+ JS::Rooted<JS::Value> endpointValue(aCx);
+ if (!JS_ReadString(aReader, &endpointValue)) {
+ return false;
+ }
+ nsAutoJSString endpoint;
+ if (!endpoint.init(aCx, endpointValue)) {
+ return false;
+ }
+ aEndpoint = endpoint;
+ if (!ReadByteArray(aCx, aReader, aRawP256dhKey) ||
+ !ReadByteArray(aCx, aReader, aAuthSecret)) {
+ return false;
+ }
+ return true;
+}
+
} // anonymous namespace
class UnsubscribeResultCallback final : public nsIUnsubscribeResultCallback
{
public:
NS_DECL_ISUPPORTS
explicit UnsubscribeResultCallback(Promise* aPromise)
@@ -118,37 +193,45 @@ private:
RefPtr<Promise> mPromise;
};
NS_IMPL_ISUPPORTS(UnsubscribeResultCallback, nsIUnsubscribeResultCallback)
already_AddRefed<Promise>
PushSubscription::Unsubscribe(ErrorResult& aRv)
{
- MOZ_ASSERT(mPrincipal);
-
nsCOMPtr<nsIPushService> service =
do_GetService("@mozilla.org/push/Service;1");
if (NS_WARN_IF(!service)) {
aRv = 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, mPrincipal, callback)));
+ service->Unsubscribe(mScope, GetPrincipal(), callback)));
return p.forget();
}
+nsIPrincipal*
+PushSubscription::GetPrincipal()
+{
+ nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(GetParentObject());
+ if (!sop) {
+ return nullptr;
+ }
+ return sop->GetPrincipal();
+}
+
void
PushSubscription::ToJSON(PushSubscriptionJSON& aJSON)
{
SubscriptionToJSON(aJSON, mEndpoint, mRawP256dhKey, mAuthSecret);
}
PushSubscription::PushSubscription(nsIGlobalObject* aGlobal,
const nsAString& aEndpoint,
@@ -186,21 +269,31 @@ PushSubscription::GetKey(JSContext* aCx,
aKey.set(ArrayBuffer::Create(aCx,
mAuthSecret.Length(),
mAuthSecret.Elements()));
} else {
aKey.set(nullptr);
}
}
-void
-PushSubscription::SetPrincipal(nsIPrincipal* aPrincipal)
+bool
+PushSubscription::WriteStructuredClone(JSContext* aCx,
+ JSStructuredCloneWriter* aWriter) const
{
- MOZ_ASSERT(!mPrincipal);
- mPrincipal = aPrincipal;
+ return WriteSubscriptionClone(aCx, aWriter, mEndpoint, mRawP256dhKey,
+ mAuthSecret);
+}
+
+bool
+PushSubscription::ReadStructuredClone(JSContext* aCx,
+ JSStructuredCloneReader* aReader)
+{
+ nsCOMPtr<nsIPrincipal> principal;
+ return ReadSubscriptionClone(aCx, aReader, mEndpoint, mRawP256dhKey,
+ mAuthSecret);
}
// static
already_AddRefed<PushSubscription>
PushSubscription::Constructor(GlobalObject& aGlobal,
const nsAString& aEndpoint,
const nsAString& aScope,
const Nullable<ArrayBuffer>& aP256dhKey,
@@ -229,17 +322,17 @@ PushSubscription::Constructor(GlobalObje
aEndpoint,
aScope,
rawKey,
authSecret);
return sub.forget();
}
-NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushSubscription, mGlobal, mPrincipal)
+NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushSubscription, mGlobal)
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
@@ -303,20 +396,17 @@ NS_INTERFACE_MAP_END
WorkerPushSubscription::WorkerPushSubscription(const nsAString& aEndpoint,
const nsAString& aScope,
const nsTArray<uint8_t>& aRawP256dhKey,
const nsTArray<uint8_t>& aAuthSecret)
: mEndpoint(aEndpoint)
, mScope(aScope)
, mRawP256dhKey(aRawP256dhKey)
, mAuthSecret(aAuthSecret)
-{
- MOZ_ASSERT(!aScope.IsEmpty());
- MOZ_ASSERT(!aEndpoint.IsEmpty());
-}
+{}
WorkerPushSubscription::~WorkerPushSubscription()
{}
JSObject*
WorkerPushSubscription::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aGivenProto)
{
return PushSubscriptionBinding_workers::Wrap(aCx, this, aGivenProto);
@@ -374,16 +464,33 @@ WorkerPushSubscription::GetKey(JSContext
aKey.set(ArrayBuffer::Create(aCx,
mAuthSecret.Length(),
mAuthSecret.Elements()));
} else {
aKey.set(nullptr);
}
}
+bool
+WorkerPushSubscription::WriteStructuredClone(JSContext* aCx,
+ JSStructuredCloneWriter* aWriter) const
+{
+ return WriteSubscriptionClone(aCx, aWriter, mEndpoint, mRawP256dhKey,
+ mAuthSecret);
+}
+
+bool
+WorkerPushSubscription::ReadStructuredClone(JSContext* aCx,
+ JSStructuredCloneReader* aReader)
+{
+ nsCOMPtr<nsIPrincipal> principal;
+ return ReadSubscriptionClone(aCx, aReader, mEndpoint, mRawP256dhKey,
+ mAuthSecret);
+}
+
class UnsubscribeResultRunnable final : public WorkerRunnable
{
public:
UnsubscribeResultRunnable(PromiseWorkerProxy* aProxy,
nsresult aStatus,
bool aSuccess)
: WorkerRunnable(aProxy->GetWorkerPrivate(), WorkerThreadModifyBusyCount)
, mProxy(aProxy)
--- a/dom/push/PushManager.h
+++ b/dom/push/PushManager.h
@@ -28,16 +28,18 @@
* verbose to implement in C++ compared to JS.
*/
#ifndef mozilla_dom_PushManager_h
#define mozilla_dom_PushManager_h
#include "nsWrapperCache.h"
+#include "js/StructuredClone.h"
+
#include "mozilla/AlreadyAddRefed.h"
#include "mozilla/ErrorResult.h"
#include "mozilla/dom/BindingDeclarations.h"
#include "mozilla/dom/TypedArray.h"
#include "nsCOMPtr.h"
#include "mozilla/RefPtr.h"
#include "jsapi.h"
@@ -93,25 +95,31 @@ public:
static already_AddRefed<PushSubscription>
Constructor(GlobalObject& aGlobal,
const nsAString& aEndpoint,
const nsAString& aScope,
const Nullable<ArrayBuffer>& aP256dhKey,
const Nullable<ArrayBuffer>& aAuthSecret,
ErrorResult& aRv);
- void
- SetPrincipal(nsIPrincipal* aPrincipal);
+ nsIPrincipal*
+ GetPrincipal();
already_AddRefed<Promise>
Unsubscribe(ErrorResult& aRv);
void
ToJSON(PushSubscriptionJSON& aJSON);
+ bool
+ WriteStructuredClone(JSContext* aCx, JSStructuredCloneWriter* aWriter) const;
+
+ bool
+ ReadStructuredClone(JSContext* aCx, JSStructuredCloneReader* aReader);
+
protected:
~PushSubscription();
private:
nsCOMPtr<nsIGlobalObject> mGlobal;
nsCOMPtr<nsIPrincipal> mPrincipal;
nsString mEndpoint;
nsString mScope;
@@ -198,16 +206,22 @@ public:
JS::MutableHandle<JSObject*> aP256dhKey);
already_AddRefed<Promise>
Unsubscribe(ErrorResult& aRv);
void
ToJSON(PushSubscriptionJSON& aJSON);
+ bool
+ WriteStructuredClone(JSContext* aCx, JSStructuredCloneWriter* aWriter) const;
+
+ bool
+ ReadStructuredClone(JSContext* aCx, JSStructuredCloneReader* aReader);
+
protected:
~WorkerPushSubscription();
private:
nsString mEndpoint;
nsString mScope;
nsTArray<uint8_t> mRawP256dhKey;
nsTArray<uint8_t> mAuthSecret;
--- a/dom/push/test/test_register.html
+++ b/dom/push/test/test_register.html
@@ -51,21 +51,28 @@ http://creativecommons.org/licenses/publ
var pushSubscription;
add_task(function* subscribe() {
pushSubscription = yield registration.pushManager.subscribe();
});
add_task(function* resubscribe() {
var data = yield sendRequestToWorker({
type: "resubscribe",
- endpoint: pushSubscription.endpoint,
+ subscription: pushSubscription,
});
pushSubscription = yield registration.pushManager.getSubscription();
is(data.endpoint, pushSubscription.endpoint,
"Subscription endpoints should match after resubscribing in worker");
+ ["p256dh", "auth"].forEach(keyName => {
+ isDeeply(
+ new Uint8Array(data.getKey(keyName)),
+ new Uint8Array(pushSubscription.getKey(keyName)),
+ "Mismatched key after resubscribing in worker: " + keyName
+ );
+ });
});
add_task(function* waitForPushNotification() {
yield Promise.all([
controlledFrame.waitOnWorkerMessage("finished"),
fetch("http://mochi.test:8888/tests/dom/push/test/push-server.sjs", {
method: "PUT",
headers: {
--- a/dom/push/test/worker.js
+++ b/dom/push/test/worker.js
@@ -81,30 +81,26 @@ function handleMessage(event) {
auth: subscription.getKey("auth"),
})
));
return;
}
if (event.data.type == "resubscribe") {
reply(event, self.registration.pushManager.getSubscription().then(
subscription => {
- assert(subscription.endpoint == event.data.endpoint,
+ assert(subscription.endpoint == event.data.subscription.endpoint,
"Wrong push endpoint in worker");
return subscription.unsubscribe();
}
).then(result => {
assert(result, "Error unsubscribing in worker");
return self.registration.pushManager.getSubscription();
}).then(subscription => {
assert(!subscription, "Subscription not removed in worker");
return self.registration.pushManager.subscribe();
- }).then(subscription => {
- return {
- endpoint: subscription.endpoint,
- };
}));
return;
}
if (event.data.type == "denySubscribe") {
reply(event, self.registration.pushManager.getSubscription().then(
subscription => {
assert(!subscription,
"Should not return worker subscription with revoked permission");
--- a/dom/webidl/PushSubscription.webidl
+++ b/dom/webidl/PushSubscription.webidl
@@ -34,13 +34,9 @@ interface PushSubscription
{
readonly attribute USVString endpoint;
ArrayBuffer? getKey(PushEncryptionKeyName name);
[Throws, UseCounter]
Promise<boolean> unsubscribe();
// Implements the custom serializer specified in Push API, section 9.
PushSubscriptionJSON toJSON();
-
- // Used to set the principal from the JS implemented PushManager.
- [Exposed=Window,ChromeOnly]
- void setPrincipal(Principal principal);
};