зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1149195 - Expose push message data accessors. r=nsm,dragana,smaug
--HG-- extra : commitid : 10hopC95zuL extra : rebase_source : c3c9cfac62d1acb234bba2f16b443783354c43a4
This commit is contained in:
Родитель
6c8172cef8
Коммит
cc7189cb64
|
@ -42,6 +42,7 @@
|
|||
#include "WorkerRunnable.h"
|
||||
#include "WorkerScope.h"
|
||||
#include "Workers.h"
|
||||
#include "FetchUtil.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
@ -909,53 +910,6 @@ ExtractByteStreamFromBody(const ArrayBufferOrArrayBufferViewOrBlobOrFormDataOrUS
|
|||
}
|
||||
|
||||
namespace {
|
||||
class StreamDecoder final
|
||||
{
|
||||
nsCOMPtr<nsIUnicodeDecoder> mDecoder;
|
||||
nsString mDecoded;
|
||||
|
||||
public:
|
||||
StreamDecoder()
|
||||
: mDecoder(EncodingUtils::DecoderForEncoding("UTF-8"))
|
||||
{
|
||||
MOZ_ASSERT(mDecoder);
|
||||
}
|
||||
|
||||
nsresult
|
||||
AppendText(const char* aSrcBuffer, uint32_t aSrcBufferLen)
|
||||
{
|
||||
int32_t destBufferLen;
|
||||
nsresult rv =
|
||||
mDecoder->GetMaxLength(aSrcBuffer, aSrcBufferLen, &destBufferLen);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (!mDecoded.SetCapacity(mDecoded.Length() + destBufferLen, fallible)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
char16_t* destBuffer = mDecoded.BeginWriting() + mDecoded.Length();
|
||||
int32_t totalChars = mDecoded.Length();
|
||||
|
||||
int32_t srcLen = (int32_t) aSrcBufferLen;
|
||||
int32_t outLen = destBufferLen;
|
||||
rv = mDecoder->Convert(aSrcBuffer, &srcLen, destBuffer, &outLen);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
|
||||
totalChars += outLen;
|
||||
mDecoded.SetLength(totalChars);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsString&
|
||||
GetText()
|
||||
{
|
||||
return mDecoded;
|
||||
}
|
||||
};
|
||||
|
||||
/*
|
||||
* Called on successfully reading the complete stream.
|
||||
*/
|
||||
|
@ -1436,128 +1390,81 @@ FetchBody<Derived>::ContinueConsumeBody(nsresult aStatus, uint32_t aResultLength
|
|||
jsapi.Init(DerivedClass()->GetParentObject());
|
||||
JSContext* cx = jsapi.cx();
|
||||
|
||||
ErrorResult error;
|
||||
|
||||
switch (mConsumeType) {
|
||||
case CONSUME_ARRAYBUFFER: {
|
||||
JS::Rooted<JSObject*> arrayBuffer(cx);
|
||||
arrayBuffer = JS_NewArrayBufferWithContents(cx, aResultLength, reinterpret_cast<void *>(aResult));
|
||||
if (!arrayBuffer) {
|
||||
JS_ClearPendingException(cx);
|
||||
localPromise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
|
||||
NS_WARNING("OUT OF MEMORY");
|
||||
return;
|
||||
}
|
||||
FetchUtil::ConsumeArrayBuffer(cx, &arrayBuffer, aResultLength, aResult,
|
||||
error);
|
||||
|
||||
JS::Rooted<JS::Value> val(cx);
|
||||
val.setObjectOrNull(arrayBuffer);
|
||||
localPromise->MaybeResolve(cx, val);
|
||||
// ArrayBuffer takes over ownership.
|
||||
autoFree.Reset();
|
||||
return;
|
||||
error.WouldReportJSException();
|
||||
if (!error.Failed()) {
|
||||
JS::Rooted<JS::Value> val(cx);
|
||||
val.setObjectOrNull(arrayBuffer);
|
||||
|
||||
localPromise->MaybeResolve(cx, val);
|
||||
// ArrayBuffer takes over ownership.
|
||||
autoFree.Reset();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case CONSUME_BLOB: {
|
||||
nsRefPtr<dom::Blob> blob =
|
||||
Blob::CreateMemoryBlob(DerivedClass()->GetParentObject(),
|
||||
reinterpret_cast<void *>(aResult), aResultLength,
|
||||
NS_ConvertUTF8toUTF16(mMimeType));
|
||||
|
||||
if (!blob) {
|
||||
localPromise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
|
||||
return;
|
||||
nsRefPtr<dom::Blob> blob = FetchUtil::ConsumeBlob(
|
||||
DerivedClass()->GetParentObject(), NS_ConvertUTF8toUTF16(mMimeType),
|
||||
aResultLength, aResult, error);
|
||||
error.WouldReportJSException();
|
||||
if (!error.Failed()) {
|
||||
localPromise->MaybeResolve(blob);
|
||||
// File takes over ownership.
|
||||
autoFree.Reset();
|
||||
}
|
||||
|
||||
localPromise->MaybeResolve(blob);
|
||||
// File takes over ownership.
|
||||
autoFree.Reset();
|
||||
return;
|
||||
break;
|
||||
}
|
||||
case CONSUME_FORMDATA: {
|
||||
nsCString data;
|
||||
data.Adopt(reinterpret_cast<char*>(aResult), aResultLength);
|
||||
autoFree.Reset();
|
||||
|
||||
NS_NAMED_LITERAL_CSTRING(formDataMimeType, "multipart/form-data");
|
||||
|
||||
// Allow semicolon separated boundary/encoding suffix like multipart/form-data; boundary=
|
||||
// but disallow multipart/form-datafoobar.
|
||||
bool isValidFormDataMimeType = StringBeginsWith(mMimeType, formDataMimeType);
|
||||
|
||||
if (isValidFormDataMimeType && mMimeType.Length() > formDataMimeType.Length()) {
|
||||
isValidFormDataMimeType = mMimeType[formDataMimeType.Length()] == ';';
|
||||
}
|
||||
|
||||
if (isValidFormDataMimeType) {
|
||||
FormDataParser parser(mMimeType, data, DerivedClass()->GetParentObject());
|
||||
if (!parser.Parse()) {
|
||||
ErrorResult result;
|
||||
result.ThrowTypeError(MSG_BAD_FORMDATA);
|
||||
localPromise->MaybeReject(result);
|
||||
return;
|
||||
}
|
||||
|
||||
nsRefPtr<nsFormData> fd = parser.FormData();
|
||||
MOZ_ASSERT(fd);
|
||||
nsRefPtr<nsFormData> fd = FetchUtil::ConsumeFormData(
|
||||
DerivedClass()->GetParentObject(),
|
||||
mMimeType, data, error);
|
||||
if (!error.Failed()) {
|
||||
localPromise->MaybeResolve(fd);
|
||||
} else {
|
||||
NS_NAMED_LITERAL_CSTRING(urlDataMimeType, "application/x-www-form-urlencoded");
|
||||
bool isValidUrlEncodedMimeType = StringBeginsWith(mMimeType, urlDataMimeType);
|
||||
|
||||
if (isValidUrlEncodedMimeType && mMimeType.Length() > urlDataMimeType.Length()) {
|
||||
isValidUrlEncodedMimeType = mMimeType[urlDataMimeType.Length()] == ';';
|
||||
}
|
||||
|
||||
if (isValidUrlEncodedMimeType) {
|
||||
URLParams params;
|
||||
params.ParseInput(data);
|
||||
|
||||
nsRefPtr<nsFormData> fd = new nsFormData(DerivedClass()->GetParentObject());
|
||||
FillFormIterator iterator(fd);
|
||||
DebugOnly<bool> status = params.ForEach(iterator);
|
||||
MOZ_ASSERT(status);
|
||||
|
||||
localPromise->MaybeResolve(fd);
|
||||
} else {
|
||||
ErrorResult result;
|
||||
result.ThrowTypeError(MSG_BAD_FORMDATA);
|
||||
localPromise->MaybeReject(result);
|
||||
}
|
||||
}
|
||||
return;
|
||||
break;
|
||||
}
|
||||
case CONSUME_TEXT:
|
||||
// fall through handles early exit.
|
||||
case CONSUME_JSON: {
|
||||
StreamDecoder decoder;
|
||||
decoder.AppendText(reinterpret_cast<char*>(aResult), aResultLength);
|
||||
|
||||
nsString& decoded = decoder.GetText();
|
||||
if (mConsumeType == CONSUME_TEXT) {
|
||||
localPromise->MaybeResolve(decoded);
|
||||
return;
|
||||
}
|
||||
|
||||
AutoForceSetExceptionOnContext forceExn(cx);
|
||||
JS::Rooted<JS::Value> json(cx);
|
||||
if (!JS_ParseJSON(cx, decoded.get(), decoded.Length(), &json)) {
|
||||
if (!JS_IsExceptionPending(cx)) {
|
||||
localPromise->MaybeReject(NS_ERROR_DOM_UNKNOWN_ERR);
|
||||
return;
|
||||
nsString decoded;
|
||||
if (NS_SUCCEEDED(FetchUtil::ConsumeText(aResultLength, aResult, decoded))) {
|
||||
if (mConsumeType == CONSUME_TEXT) {
|
||||
localPromise->MaybeResolve(decoded);
|
||||
} else {
|
||||
JS::Rooted<JS::Value> json(cx);
|
||||
FetchUtil::ConsumeJson(cx, &json, decoded, error);
|
||||
if (!error.Failed()) {
|
||||
localPromise->MaybeResolve(cx, json);
|
||||
}
|
||||
}
|
||||
|
||||
JS::Rooted<JS::Value> exn(cx);
|
||||
DebugOnly<bool> gotException = JS_GetPendingException(cx, &exn);
|
||||
MOZ_ASSERT(gotException);
|
||||
|
||||
JS_ClearPendingException(cx);
|
||||
localPromise->MaybeReject(cx, exn);
|
||||
return;
|
||||
}
|
||||
|
||||
localPromise->MaybeResolve(cx, json);
|
||||
return;
|
||||
};
|
||||
break;
|
||||
}
|
||||
default:
|
||||
NS_NOTREACHED("Unexpected consume body type");
|
||||
}
|
||||
|
||||
NS_NOTREACHED("Unexpected consume body type");
|
||||
error.WouldReportJSException();
|
||||
if (error.Failed()) {
|
||||
if (error.IsJSException()) {
|
||||
JS::Rooted<JS::Value> exn(cx);
|
||||
error.StealJSException(cx, &exn);
|
||||
localPromise->MaybeReject(cx, exn);
|
||||
} else {
|
||||
localPromise->MaybeReject(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <class Derived>
|
||||
|
@ -1622,5 +1529,6 @@ FetchBody<Request>::SetMimeType();
|
|||
template
|
||||
void
|
||||
FetchBody<Response>::SetMimeType();
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -1,10 +1,63 @@
|
|||
#include "FetchUtil.h"
|
||||
|
||||
#include "nsError.h"
|
||||
#include "nsIUnicodeDecoder.h"
|
||||
#include "nsString.h"
|
||||
|
||||
#include "mozilla/dom/EncodingUtils.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
||||
namespace {
|
||||
class StreamDecoder final
|
||||
{
|
||||
nsCOMPtr<nsIUnicodeDecoder> mDecoder;
|
||||
nsString mDecoded;
|
||||
|
||||
public:
|
||||
StreamDecoder()
|
||||
: mDecoder(EncodingUtils::DecoderForEncoding("UTF-8"))
|
||||
{
|
||||
MOZ_ASSERT(mDecoder);
|
||||
}
|
||||
|
||||
nsresult
|
||||
AppendText(const char* aSrcBuffer, uint32_t aSrcBufferLen)
|
||||
{
|
||||
int32_t destBufferLen;
|
||||
nsresult rv =
|
||||
mDecoder->GetMaxLength(aSrcBuffer, aSrcBufferLen, &destBufferLen);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
if (!mDecoded.SetCapacity(mDecoded.Length() + destBufferLen, fallible)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
char16_t* destBuffer = mDecoded.BeginWriting() + mDecoded.Length();
|
||||
int32_t totalChars = mDecoded.Length();
|
||||
|
||||
int32_t srcLen = (int32_t) aSrcBufferLen;
|
||||
int32_t outLen = destBufferLen;
|
||||
rv = mDecoder->Convert(aSrcBuffer, &srcLen, destBuffer, &outLen);
|
||||
MOZ_ASSERT(NS_SUCCEEDED(rv));
|
||||
|
||||
totalChars += outLen;
|
||||
mDecoded.SetLength(totalChars);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsString&
|
||||
GetText()
|
||||
{
|
||||
return mDecoded;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// static
|
||||
nsresult
|
||||
FetchUtil::GetValidRequestMethod(const nsACString& aMethod, nsCString& outMethod)
|
||||
|
@ -33,5 +86,133 @@ FetchUtil::GetValidRequestMethod(const nsACString& aMethod, nsCString& outMethod
|
|||
return NS_OK;
|
||||
}
|
||||
|
||||
// static
|
||||
void
|
||||
FetchUtil::ConsumeArrayBuffer(JSContext* aCx,
|
||||
JS::MutableHandle<JSObject*> aValue,
|
||||
uint32_t aInputLength, uint8_t* aInput,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
JS::Rooted<JSObject*> arrayBuffer(aCx);
|
||||
arrayBuffer = JS_NewArrayBufferWithContents(aCx, aInputLength,
|
||||
reinterpret_cast<void *>(aInput));
|
||||
if (!arrayBuffer) {
|
||||
JS_ClearPendingException(aCx);
|
||||
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
|
||||
return;
|
||||
}
|
||||
aValue.set(arrayBuffer);
|
||||
}
|
||||
|
||||
// static
|
||||
already_AddRefed<Blob>
|
||||
FetchUtil::ConsumeBlob(nsISupports* aParent, const nsString& aMimeType,
|
||||
uint32_t aInputLength, uint8_t* aInput,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsRefPtr<Blob> blob =
|
||||
Blob::CreateMemoryBlob(aParent,
|
||||
reinterpret_cast<void *>(aInput), aInputLength,
|
||||
aMimeType);
|
||||
|
||||
if (!blob) {
|
||||
aRv.Throw(NS_ERROR_DOM_UNKNOWN_ERR);
|
||||
return nullptr;
|
||||
}
|
||||
return blob.forget();
|
||||
}
|
||||
|
||||
// static
|
||||
already_AddRefed<nsFormData>
|
||||
FetchUtil::ConsumeFormData(nsIGlobalObject* aParent, const nsCString& aMimeType,
|
||||
const nsCString& aStr, ErrorResult& aRv)
|
||||
{
|
||||
NS_NAMED_LITERAL_CSTRING(formDataMimeType, "multipart/form-data");
|
||||
|
||||
// Allow semicolon separated boundary/encoding suffix like multipart/form-data; boundary=
|
||||
// but disallow multipart/form-datafoobar.
|
||||
bool isValidFormDataMimeType = StringBeginsWith(aMimeType, formDataMimeType);
|
||||
|
||||
if (isValidFormDataMimeType && aMimeType.Length() > formDataMimeType.Length()) {
|
||||
isValidFormDataMimeType = aMimeType[formDataMimeType.Length()] == ';';
|
||||
}
|
||||
|
||||
if (isValidFormDataMimeType) {
|
||||
FormDataParser parser(aMimeType, aStr, aParent);
|
||||
if (!parser.Parse()) {
|
||||
aRv.ThrowTypeError(MSG_BAD_FORMDATA);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
nsRefPtr<nsFormData> fd = parser.FormData();
|
||||
MOZ_ASSERT(fd);
|
||||
return fd.forget();
|
||||
}
|
||||
|
||||
NS_NAMED_LITERAL_CSTRING(urlDataMimeType, "application/x-www-form-urlencoded");
|
||||
bool isValidUrlEncodedMimeType = StringBeginsWith(aMimeType, urlDataMimeType);
|
||||
|
||||
if (isValidUrlEncodedMimeType && aMimeType.Length() > urlDataMimeType.Length()) {
|
||||
isValidUrlEncodedMimeType = aMimeType[urlDataMimeType.Length()] == ';';
|
||||
}
|
||||
|
||||
if (isValidUrlEncodedMimeType) {
|
||||
URLParams params;
|
||||
params.ParseInput(aStr);
|
||||
|
||||
nsRefPtr<nsFormData> fd = new nsFormData(aParent);
|
||||
FillFormIterator iterator(fd);
|
||||
DebugOnly<bool> status = params.ForEach(iterator);
|
||||
MOZ_ASSERT(status);
|
||||
|
||||
return fd.forget();
|
||||
}
|
||||
|
||||
aRv.ThrowTypeError(MSG_BAD_FORMDATA);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// static
|
||||
nsresult
|
||||
FetchUtil::ConsumeText(uint32_t aInputLength, uint8_t* aInput,
|
||||
nsString& aText)
|
||||
{
|
||||
StreamDecoder decoder;
|
||||
nsresult rv = decoder.AppendText(reinterpret_cast<char*>(aInput),
|
||||
aInputLength);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
aText = decoder.GetText();
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
// static
|
||||
void
|
||||
FetchUtil::ConsumeJson(JSContext* aCx, JS::MutableHandle<JS::Value> aValue,
|
||||
const nsString& aStr, ErrorResult& aRv)
|
||||
{
|
||||
aRv.MightThrowJSException();
|
||||
|
||||
AutoForceSetExceptionOnContext forceExn(aCx);
|
||||
JS::Rooted<JS::Value> json(aCx);
|
||||
if (!JS_ParseJSON(aCx, aStr.get(), aStr.Length(), &json)) {
|
||||
if (!JS_IsExceptionPending(aCx)) {
|
||||
aRv.Throw(NS_ERROR_DOM_UNKNOWN_ERR);
|
||||
return;
|
||||
}
|
||||
|
||||
JS::Rooted<JS::Value> exn(aCx);
|
||||
DebugOnly<bool> gotException = JS_GetPendingException(aCx, &exn);
|
||||
MOZ_ASSERT(gotException);
|
||||
|
||||
JS_ClearPendingException(aCx);
|
||||
aRv.ThrowJSException(aCx, exn);
|
||||
return;
|
||||
}
|
||||
|
||||
aValue.set(json);
|
||||
}
|
||||
|
||||
} // namespace dom
|
||||
} // namespace mozilla
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
|
||||
#include "nsString.h"
|
||||
#include "nsError.h"
|
||||
#include "nsFormData.h"
|
||||
|
||||
#include "mozilla/ErrorResult.h"
|
||||
#include "mozilla/dom/File.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace dom {
|
||||
|
@ -21,6 +25,46 @@ public:
|
|||
*/
|
||||
static nsresult
|
||||
GetValidRequestMethod(const nsACString& aMethod, nsCString& outMethod);
|
||||
|
||||
/**
|
||||
* Creates an array buffer from an array, assigning the result to |aValue|.
|
||||
* The array buffer takes ownership of |aInput|, which must be allocated
|
||||
* by |malloc|.
|
||||
*/
|
||||
static void
|
||||
ConsumeArrayBuffer(JSContext* aCx, JS::MutableHandle<JSObject*> aValue,
|
||||
uint32_t aInputLength, uint8_t* aInput, ErrorResult& aRv);
|
||||
|
||||
/**
|
||||
* Creates an in-memory blob from an array. The blob takes ownership of
|
||||
* |aInput|, which must be allocated by |malloc|.
|
||||
*/
|
||||
static already_AddRefed<Blob>
|
||||
ConsumeBlob(nsISupports* aParent, const nsString& aMimeType,
|
||||
uint32_t aInputLength, uint8_t* aInput, ErrorResult& aRv);
|
||||
|
||||
/**
|
||||
* Creates a form data object from a UTF-8 encoded |aStr|. Returns |nullptr|
|
||||
* and sets |aRv| to MSG_BAD_FORMDATA if |aStr| contains invalid data.
|
||||
*/
|
||||
static already_AddRefed<nsFormData>
|
||||
ConsumeFormData(nsIGlobalObject* aParent, const nsCString& aMimeType,
|
||||
const nsCString& aStr, ErrorResult& aRv);
|
||||
|
||||
/**
|
||||
* UTF-8 decodes |aInput| into |aText|. The caller may free |aInput|
|
||||
* once this method returns.
|
||||
*/
|
||||
static nsresult
|
||||
ConsumeText(uint32_t aInputLength, uint8_t* aInput, nsString& aText);
|
||||
|
||||
/**
|
||||
* Parses a UTF-8 encoded |aStr| as JSON, assigning the result to |aValue|.
|
||||
* Sets |aRv| to a syntax error if |aStr| contains invalid data.
|
||||
*/
|
||||
static void
|
||||
ConsumeJson(JSContext* aCx, JS::MutableHandle<JS::Value> aValue,
|
||||
const nsString& aStr, ErrorResult& aRv);
|
||||
};
|
||||
|
||||
} // namespace dom
|
||||
|
|
|
@ -34,7 +34,7 @@ interface nsIServiceWorkerInfo : nsISupports
|
|||
readonly attribute DOMString waitingCacheName;
|
||||
};
|
||||
|
||||
[scriptable, builtinclass, uuid(8d80dd18-597b-4378-b41e-768bfe48dd4f)]
|
||||
[scriptable, builtinclass, uuid(02d52950-f311-42c7-b573-fd3a2c5d3210)]
|
||||
interface nsIServiceWorkerManager : nsISupports
|
||||
{
|
||||
/**
|
||||
|
@ -143,7 +143,8 @@ interface nsIServiceWorkerManager : nsISupports
|
|||
in AString aBehavior);
|
||||
void sendPushEvent(in ACString aOriginAttributes,
|
||||
in ACString aScope,
|
||||
in DOMString aData);
|
||||
in uint32_t aDataLength,
|
||||
[array, size_is(aDataLength)] in uint8_t aDataBytes);
|
||||
void sendPushSubscriptionChangeEvent(in ACString aOriginAttributes,
|
||||
in ACString scope);
|
||||
|
||||
|
|
|
@ -827,9 +827,9 @@ this.PushService = {
|
|||
cryptoParams.dh,
|
||||
cryptoParams.salt,
|
||||
cryptoParams.rs
|
||||
).then(bytes => new TextDecoder("utf-8").decode(bytes));
|
||||
);
|
||||
} else {
|
||||
decodedPromise = Promise.resolve("");
|
||||
decodedPromise = Promise.resolve(null);
|
||||
}
|
||||
return decodedPromise.then(message => {
|
||||
if (shouldNotify) {
|
||||
|
@ -865,7 +865,16 @@ this.PushService = {
|
|||
.createInstance(Ci.nsIPushObserverNotification);
|
||||
notification.pushEndpoint = aPushRecord.pushEndpoint;
|
||||
notification.version = aPushRecord.version;
|
||||
notification.data = message;
|
||||
|
||||
let payload = ArrayBuffer.isView(message) ?
|
||||
new Uint8Array(message.buffer) : message;
|
||||
if (payload) {
|
||||
notification.data = "";
|
||||
for (let i = 0; i < payload.length; i++) {
|
||||
notification.data += String.fromCharCode(payload[i]);
|
||||
}
|
||||
}
|
||||
|
||||
notification.lastPush = aPushRecord.lastPush;
|
||||
notification.pushCount = aPushRecord.pushCount;
|
||||
|
||||
|
@ -881,9 +890,8 @@ this.PushService = {
|
|||
return false;
|
||||
}
|
||||
|
||||
// TODO data.
|
||||
let data = {
|
||||
payload: message,
|
||||
payload: payload,
|
||||
originAttributes: aPushRecord.originAttributes,
|
||||
scope: aPushRecord.scope
|
||||
};
|
||||
|
|
|
@ -23,8 +23,12 @@ var processType = Cc["@mozilla.org/xre/app-info;1"]
|
|||
var isParent = processType === Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
|
||||
|
||||
Services.cpmm.addMessageListener("push", function (aMessage) {
|
||||
let {payload} = aMessage.data;
|
||||
let length = payload ? payload.length : 0;
|
||||
swm.sendPushEvent(aMessage.data.originAttributes,
|
||||
aMessage.data.scope, aMessage.data.payload);
|
||||
aMessage.data.scope,
|
||||
length,
|
||||
payload);
|
||||
});
|
||||
|
||||
Services.cpmm.addMessageListener("pushsubscriptionchange", function (aMessage) {
|
||||
|
|
|
@ -10,8 +10,7 @@
|
|||
var p = new Promise(function(res, rej) {
|
||||
navigator.serviceWorker.onmessage = function(e) {
|
||||
if (e.data.type == "finished") {
|
||||
parent.ok(e.data.okay == "yes", "Got a push message.");
|
||||
res(pushSubscription);
|
||||
(e.data.okay == "yes" ? res : rej)(e.data);
|
||||
}
|
||||
};
|
||||
});
|
||||
|
|
|
@ -4,6 +4,7 @@ support-files =
|
|||
worker.js
|
||||
push-server.sjs
|
||||
frame.html
|
||||
webpush.js
|
||||
|
||||
[test_has_permissions.html]
|
||||
skip-if = os == "android" || toolkit == "gonk"
|
||||
|
@ -19,6 +20,8 @@ skip-if = os == "android" || toolkit == "gonk"
|
|||
skip-if = os == "android" || toolkit == "gonk"
|
||||
[test_multiple_register_different_scope.html]
|
||||
skip-if = os == "android" || toolkit == "gonk"
|
||||
[test_data.html]
|
||||
skip-if = os == "android" || toolkit == "gonk"
|
||||
# Disabled for too many intermittent failures (bug 1164432)
|
||||
# [test_try_registering_offline_disabled.html]
|
||||
# skip-if = os == "android" || toolkit == "gonk"
|
||||
|
|
|
@ -2,6 +2,15 @@ function debug(str) {
|
|||
// dump("@@@ push-server " + str + "\n");
|
||||
}
|
||||
|
||||
function concatUint8Arrays(arrays, size) {
|
||||
let index = 0;
|
||||
return arrays.reduce((result, a) => {
|
||||
result.set(new Uint8Array(a), index);
|
||||
index += a.byteLength;
|
||||
return result;
|
||||
}, new Uint8Array(size));
|
||||
}
|
||||
|
||||
function handleRequest(request, response)
|
||||
{
|
||||
debug("handling request!");
|
||||
|
@ -13,8 +22,25 @@ function handleRequest(request, response)
|
|||
debug("params = " + params);
|
||||
|
||||
let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"].createInstance(Ci.nsIXMLHttpRequest);
|
||||
xhr.open("PUT", params);
|
||||
xhr.send();
|
||||
xhr.open(request.getHeader("X-Push-Method"), params);
|
||||
|
||||
for (let headers = request.headers; headers.hasMoreElements();) {
|
||||
let header = headers.getNext().QueryInterface(Ci.nsISupportsString).data;
|
||||
if (header.toLowerCase() != "x-push-server") {
|
||||
xhr.setRequestHeader(header, request.getHeader(header));
|
||||
}
|
||||
}
|
||||
|
||||
let bodyStream = Cc["@mozilla.org/binaryinputstream;1"].createInstance(Ci.nsIBinaryInputStream);
|
||||
bodyStream.setInputStream(request.bodyInputStream);
|
||||
let size = 0;
|
||||
let data = [];
|
||||
for (let available; available = bodyStream.available();) {
|
||||
data.push(bodyStream.readByteArray(available));
|
||||
size += available;
|
||||
}
|
||||
xhr.send(concatUint8Arrays(data, size));
|
||||
|
||||
xhr.onload = function(e) {
|
||||
debug("xhr : " + this.status);
|
||||
}
|
||||
|
|
|
@ -0,0 +1,172 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<!--
|
||||
Bug 1185544: Add data delivery to the WebSocket backend.
|
||||
|
||||
Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/licenses/publicdomain/
|
||||
|
||||
-->
|
||||
<head>
|
||||
<title>Test for Bug 1185544</title>
|
||||
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="/tests/dom/push/test/webpush.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
|
||||
<meta http-equiv="Content-type" content="text/html;charset=UTF-8">
|
||||
</head>
|
||||
<a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1185544">Mozilla Bug 1185544</a>
|
||||
<p id="display"></p>
|
||||
<div id="content" style="display: none">
|
||||
|
||||
</div>
|
||||
<pre id="test">
|
||||
</pre>
|
||||
|
||||
<script class="testbody" type="text/javascript">
|
||||
|
||||
var registration;
|
||||
|
||||
function start() {
|
||||
return navigator.serviceWorker.register("worker.js" + "?" + (Math.random()), {scope: "."})
|
||||
.then(swr => { registration = swr; return swr; });
|
||||
}
|
||||
|
||||
var controlledFrame;
|
||||
function createControlledIFrame(swr) {
|
||||
var p = new Promise(function(res, rej) {
|
||||
var iframe = document.createElement('iframe');
|
||||
iframe.id = "controlledFrame";
|
||||
iframe.src = "http://mochi.test:8888/tests/dom/push/test/frame.html";
|
||||
|
||||
iframe.onload = function() {
|
||||
res(swr)
|
||||
}
|
||||
controlledFrame = iframe;
|
||||
document.body.appendChild(iframe);
|
||||
});
|
||||
return p;
|
||||
}
|
||||
|
||||
function subscribe(swr) {
|
||||
return swr.pushManager.subscribe();
|
||||
}
|
||||
|
||||
function sendRequestToWorker(request) {
|
||||
return new Promise((resolve, reject) => {
|
||||
var channel = new MessageChannel();
|
||||
channel.port1.onmessage = e => {
|
||||
(e.data.error ? reject : resolve)(e.data);
|
||||
};
|
||||
registration.active.postMessage(request, [channel.port2]);
|
||||
});
|
||||
}
|
||||
|
||||
function comparePublicKey(pushSubscription) {
|
||||
// FIXME(kitcambridge): Enable when `ServiceWorkerMessageEvent` is
|
||||
// implemented (bug 1143717).
|
||||
return Promise.resolve(pushSubscription);
|
||||
/*
|
||||
return sendRequestToWorker({ type: "publicKey" }).then(data => {
|
||||
return registration.pushManager.getSubscription().then(
|
||||
pushSubscription => {
|
||||
isDeeply(pushSubscription.getKey("p256dh"), data,
|
||||
"Mismatched key share");
|
||||
return pushSubscription;
|
||||
});
|
||||
});
|
||||
*/
|
||||
}
|
||||
|
||||
function waitForMessage(pushSubscription, message) {
|
||||
return Promise.all([
|
||||
controlledFrame.contentWindow.waitOnPushMessage(pushSubscription),
|
||||
webpush(pushSubscription, message),
|
||||
]).then(([message]) => message);
|
||||
}
|
||||
|
||||
function sendPushMessageFromPage(pushSubscription) {
|
||||
var typedArray = new Uint8Array([226, 130, 40, 240, 40, 140, 188]);
|
||||
var json = { hello: "world" };
|
||||
return waitForMessage(pushSubscription, "Text message from page")
|
||||
.then(message => {
|
||||
is(message.data.text, "Text message from page", "Wrong text message data");
|
||||
return waitForMessage(
|
||||
pushSubscription,
|
||||
typedArray
|
||||
);
|
||||
}).then(message => {
|
||||
isDeeply(new Uint8Array(message.data.arrayBuffer), typedArray,
|
||||
"Wrong array buffer message data");
|
||||
return waitForMessage(
|
||||
pushSubscription,
|
||||
JSON.stringify(json)
|
||||
);
|
||||
}).then(message => {
|
||||
ok(message.data.json.ok, "Unexpected error parsing JSON");
|
||||
isDeeply(message.data.json.value, json, "Wrong JSON message data");
|
||||
return waitForMessage(
|
||||
pushSubscription,
|
||||
""
|
||||
);
|
||||
}).then(message => {
|
||||
ok(message, "Should include data for empty messages");
|
||||
is(message.data.text, "", "Wrong text for empty message");
|
||||
is(message.data.arrayBuffer.byteLength, 0, "Wrong buffer length for empty message");
|
||||
ok(!message.data.json.ok, "Expected JSON parse error for empty message");
|
||||
return waitForMessage(
|
||||
pushSubscription,
|
||||
new Uint8Array([0x48, 0x69, 0x21, 0x20, 0xf0, 0x9f, 0x91, 0x80])
|
||||
);
|
||||
}).then(message => {
|
||||
is(message.data.text, "Hi! \ud83d\udc40", "Wrong text for message with emoji");
|
||||
return new Promise((resolve, reject) => {
|
||||
var reader = new FileReader();
|
||||
reader.onloadend = event => {
|
||||
if (reader.error) {
|
||||
reject(reader.error);
|
||||
} else {
|
||||
resolve(reader.result);
|
||||
}
|
||||
};
|
||||
reader.readAsText(message.data.blob);
|
||||
});
|
||||
}).then(text => {
|
||||
is(text, "Hi! \ud83d\udc40", "Wrong blob data for message with emoji");
|
||||
return pushSubscription;
|
||||
});
|
||||
}
|
||||
|
||||
function unsubscribe(pushSubscription) {
|
||||
controlledFrame.parentNode.removeChild(controlledFrame);
|
||||
controlledFrame = null;
|
||||
return pushSubscription.unsubscribe();
|
||||
}
|
||||
|
||||
function unregister() {
|
||||
return registration.unregister();
|
||||
}
|
||||
|
||||
function runTest() {
|
||||
start()
|
||||
.then(createControlledIFrame)
|
||||
.then(subscribe)
|
||||
.then(comparePublicKey)
|
||||
.then(sendPushMessageFromPage)
|
||||
.then(unsubscribe)
|
||||
.then(unregister)
|
||||
.catch(function(e) {
|
||||
ok(false, "Some test failed with error " + e);
|
||||
}).then(SimpleTest.finish);
|
||||
}
|
||||
|
||||
SpecialPowers.pushPrefEnv({"set": [
|
||||
["dom.push.enabled", true],
|
||||
["dom.serviceWorkers.exemptFromPerDomainMax", true],
|
||||
["dom.serviceWorkers.enabled", true],
|
||||
["dom.serviceWorkers.testing.enabled", true]
|
||||
]}, runTest);
|
||||
SpecialPowers.addPermission('push', true, document);
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -57,6 +57,7 @@ http://creativecommons.org/licenses/publicdomain/
|
|||
// Work around CORS for now.
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', "http://mochi.test:8888/tests/dom/push/test/push-server.sjs", true);
|
||||
xhr.setRequestHeader("X-Push-Method", "PUT");
|
||||
xhr.setRequestHeader("X-Push-Server", pushEndpoint);
|
||||
xhr.onload = function(e) {
|
||||
debug("xhr : " + this.status);
|
||||
|
|
|
@ -0,0 +1,206 @@
|
|||
/*
|
||||
* Browser-based Web Push client for the application server piece.
|
||||
*
|
||||
* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/licenses/publicdomain/
|
||||
*
|
||||
* Uses the WebCrypto API.
|
||||
* Uses the fetch API. Polyfill: https://github.com/github/fetch
|
||||
*/
|
||||
|
||||
(function (g) {
|
||||
'use strict';
|
||||
|
||||
var P256DH = {
|
||||
name: 'ECDH',
|
||||
namedCurve: 'P-256'
|
||||
};
|
||||
var webCrypto = g.crypto.subtle;
|
||||
var ENCRYPT_INFO = new TextEncoder('utf-8').encode("Content-Encoding: aesgcm128");
|
||||
var NONCE_INFO = new TextEncoder('utf-8').encode("Content-Encoding: nonce");
|
||||
|
||||
function chunkArray(array, size) {
|
||||
var start = array.byteOffset || 0;
|
||||
array = array.buffer || array;
|
||||
var index = 0;
|
||||
var result = [];
|
||||
while(index + size <= array.byteLength) {
|
||||
result.push(new Uint8Array(array, start + index, size));
|
||||
index += size;
|
||||
}
|
||||
if (index < array.byteLength) {
|
||||
result.push(new Uint8Array(array, start + index));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/* I can't believe that this is needed here, in this day and age ...
|
||||
* Note: these are not efficient, merely expedient.
|
||||
*/
|
||||
var base64url = {
|
||||
_strmap: 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_',
|
||||
encode: function(data) {
|
||||
data = new Uint8Array(data);
|
||||
var len = Math.ceil(data.length * 4 / 3);
|
||||
return chunkArray(data, 3).map(chunk => [
|
||||
chunk[0] >>> 2,
|
||||
((chunk[0] & 0x3) << 4) | (chunk[1] >>> 4),
|
||||
((chunk[1] & 0xf) << 2) | (chunk[2] >>> 6),
|
||||
chunk[2] & 0x3f
|
||||
].map(v => base64url._strmap[v]).join('')).join('').slice(0, len);
|
||||
},
|
||||
_lookup: function(s, i) {
|
||||
return base64url._strmap.indexOf(s.charAt(i));
|
||||
},
|
||||
decode: function(str) {
|
||||
var v = new Uint8Array(Math.floor(str.length * 3 / 4));
|
||||
var vi = 0;
|
||||
for (var si = 0; si < str.length;) {
|
||||
var w = base64url._lookup(str, si++);
|
||||
var x = base64url._lookup(str, si++);
|
||||
var y = base64url._lookup(str, si++);
|
||||
var z = base64url._lookup(str, si++);
|
||||
v[vi++] = w << 2 | x >>> 4;
|
||||
v[vi++] = x << 4 | y >>> 2;
|
||||
v[vi++] = y << 6 | z;
|
||||
}
|
||||
return v;
|
||||
}
|
||||
};
|
||||
|
||||
g.base64url = base64url;
|
||||
|
||||
/* Coerces data into a Uint8Array */
|
||||
function ensureView(data) {
|
||||
if (typeof data === 'string') {
|
||||
return new TextEncoder('utf-8').encode(data);
|
||||
}
|
||||
if (data instanceof ArrayBuffer) {
|
||||
return new Uint8Array(data);
|
||||
}
|
||||
if (ArrayBuffer.isView(data)) {
|
||||
return new Uint8Array(data.buffer);
|
||||
}
|
||||
throw new Error('webpush() needs a string or BufferSource');
|
||||
}
|
||||
|
||||
function bsConcat(arrays) {
|
||||
var size = arrays.reduce((total, a) => total + a.byteLength, 0);
|
||||
var index = 0;
|
||||
return arrays.reduce((result, a) => {
|
||||
result.set(new Uint8Array(a), index);
|
||||
index += a.byteLength;
|
||||
return result;
|
||||
}, new Uint8Array(size));
|
||||
}
|
||||
|
||||
function hmac(key) {
|
||||
this.keyPromise = webCrypto.importKey('raw', key, { name: 'HMAC', hash: 'SHA-256' },
|
||||
false, ['sign']);
|
||||
}
|
||||
hmac.prototype.hash = function(input) {
|
||||
return this.keyPromise.then(k => webCrypto.sign('HMAC', k, input));
|
||||
};
|
||||
|
||||
function hkdf(salt, ikm) {
|
||||
this.prkhPromise = new hmac(salt).hash(ikm)
|
||||
.then(prk => new hmac(prk));
|
||||
}
|
||||
|
||||
hkdf.prototype.generate = function(info, len) {
|
||||
var input = bsConcat([info, new Uint8Array([1])]);
|
||||
return this.prkhPromise
|
||||
.then(prkh => prkh.hash(input))
|
||||
.then(h => {
|
||||
if (h.byteLength < len) {
|
||||
throw new Error('Length is too long');
|
||||
}
|
||||
return h.slice(0, len);
|
||||
});
|
||||
};
|
||||
|
||||
/* generate a 96-bit IV for use in GCM, 48-bits of which are populated */
|
||||
function generateNonce(base, index) {
|
||||
var nonce = base.slice(0, 12);
|
||||
for (var i = 0; i < 6; ++i) {
|
||||
nonce[nonce.length - 1 - i] ^= (index / Math.pow(256, i)) & 0xff;
|
||||
}
|
||||
return nonce;
|
||||
}
|
||||
|
||||
function encrypt(localKey, remoteShare, salt, data) {
|
||||
return webCrypto.importKey('raw', remoteShare, P256DH, false, ['deriveBits'])
|
||||
.then(remoteKey =>
|
||||
webCrypto.deriveBits({ name: P256DH.name, public: remoteKey },
|
||||
localKey, 256))
|
||||
.then(rawKey => {
|
||||
var kdf = new hkdf(salt, rawKey);
|
||||
return Promise.all([
|
||||
kdf.generate(ENCRYPT_INFO, 16)
|
||||
.then(gcmBits =>
|
||||
webCrypto.importKey('raw', gcmBits, 'AES-GCM', false, ['encrypt'])),
|
||||
kdf.generate(NONCE_INFO, 12)
|
||||
]);
|
||||
})
|
||||
.then(([key, nonce]) => {
|
||||
if (data.byteLength === 0) {
|
||||
// Send an authentication tag for empty messages.
|
||||
return webCrypto.encrypt({
|
||||
name: 'AES-GCM',
|
||||
iv: generateNonce(nonce, 0)
|
||||
}, key, new Uint8Array([0])).then(value => [value]);
|
||||
}
|
||||
// 4096 is the default size, though we burn 1 for padding
|
||||
return Promise.all(chunkArray(data, 4095).map((slice, index) => {
|
||||
var padded = bsConcat([new Uint8Array([0]), slice]);
|
||||
return webCrypto.encrypt({
|
||||
name: 'AES-GCM',
|
||||
iv: generateNonce(nonce, index)
|
||||
}, key, padded);
|
||||
}));
|
||||
}).then(bsConcat);
|
||||
}
|
||||
|
||||
/*
|
||||
* Request push for a message. This returns a promise that resolves when the
|
||||
* push has been delivered to the push service.
|
||||
*
|
||||
* @param subscription A PushSubscription that contains endpoint and p256dh
|
||||
* parameters.
|
||||
* @param data The message to send.
|
||||
*/
|
||||
function webpush(subscription, data) {
|
||||
data = ensureView(data);
|
||||
|
||||
var salt = g.crypto.getRandomValues(new Uint8Array(16));
|
||||
return webCrypto.generateKey(P256DH, false, ['deriveBits'])
|
||||
.then(localKey => {
|
||||
return Promise.all([
|
||||
encrypt(localKey.privateKey, subscription.getKey("p256dh"), salt, data),
|
||||
// 1337 p-256 specific haxx to get the raw value out of the spki value
|
||||
webCrypto.exportKey('raw', localKey.publicKey),
|
||||
]);
|
||||
}).then(([payload, pubkey]) => {
|
||||
var options = {
|
||||
method: 'PUT',
|
||||
headers: {
|
||||
'X-Push-Server': subscription.endpoint,
|
||||
// Web Push requires POST requests.
|
||||
'X-Push-Method': 'POST',
|
||||
'Encryption-Key': 'keyid=p256dh;dh=' + base64url.encode(pubkey),
|
||||
Encryption: 'keyid=p256dh;salt=' + base64url.encode(salt),
|
||||
'Content-Encoding': 'aesgcm128'
|
||||
},
|
||||
body: payload,
|
||||
};
|
||||
return fetch('http://mochi.test:8888/tests/dom/push/test/push-server.sjs', options);
|
||||
}).then(response => {
|
||||
if (response.status / 100 !== 2) {
|
||||
throw new Error('Unable to deliver message');
|
||||
}
|
||||
return response;
|
||||
});
|
||||
}
|
||||
|
||||
g.webpush = webpush;
|
||||
}(this));
|
|
@ -2,15 +2,56 @@
|
|||
// http://creativecommons.org/licenses/publicdomain/
|
||||
|
||||
this.onpush = handlePush;
|
||||
this.onmessage = handleMessage;
|
||||
|
||||
function getJSON(data) {
|
||||
var result = {
|
||||
ok: false,
|
||||
};
|
||||
try {
|
||||
result.value = data.json();
|
||||
result.ok = true;
|
||||
} catch (e) {
|
||||
// Ignore syntax errors for invalid JSON.
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function handlePush(event) {
|
||||
|
||||
self.clients.matchAll().then(function(result) {
|
||||
// FIXME(nsm): Bug 1149195 will fix data exposure.
|
||||
if (event instanceof PushEvent && !('data' in event)) {
|
||||
result[0].postMessage({type: "finished", okay: "yes"});
|
||||
if (event instanceof PushEvent) {
|
||||
if (!('data' in event)) {
|
||||
result[0].postMessage({type: "finished", okay: "yes"});
|
||||
return;
|
||||
}
|
||||
var message = {
|
||||
type: "finished",
|
||||
okay: "yes",
|
||||
};
|
||||
if (event.data) {
|
||||
message.data = {
|
||||
text: event.data.text(),
|
||||
arrayBuffer: event.data.arrayBuffer(),
|
||||
json: getJSON(event.data),
|
||||
blob: event.data.blob(),
|
||||
};
|
||||
}
|
||||
result[0].postMessage(message);
|
||||
return;
|
||||
}
|
||||
result[0].postMessage({type: "finished", okay: "no"});
|
||||
});
|
||||
}
|
||||
|
||||
function handleMessage(event) {
|
||||
// FIXME(kitcambridge): Enable when `ServiceWorkerMessageEvent` is
|
||||
// implemented (bug 1143717).
|
||||
/*
|
||||
if (event.data.type == "publicKey") {
|
||||
self.registration.pushManager.getSubscription().then(subscription => {
|
||||
event.ports[0].postMessage(subscription.getKey("p256dh"));
|
||||
});
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
|
|
@ -11,13 +11,11 @@
|
|||
Func="nsContentUtils::PushEnabled",
|
||||
Exposed=ServiceWorker]
|
||||
interface PushEvent : ExtendableEvent {
|
||||
// FIXME(nsm): Bug 1149195.
|
||||
// readonly attribute PushMessageData data;
|
||||
readonly attribute PushMessageData data;
|
||||
};
|
||||
|
||||
typedef USVString PushMessageDataInit;
|
||||
typedef (BufferSource or USVString) PushMessageDataInit;
|
||||
|
||||
dictionary PushEventInit : ExtendableEventInit {
|
||||
// FIXME(nsm): Bug 1149195.
|
||||
// PushMessageDataInit data;
|
||||
PushMessageDataInit data;
|
||||
};
|
||||
|
|
|
@ -11,10 +11,11 @@
|
|||
Exposed=ServiceWorker]
|
||||
interface PushMessageData
|
||||
{
|
||||
// FIXME(nsm): Bug 1149195.
|
||||
// These methods will be exposed once encryption is supported.
|
||||
// ArrayBuffer arrayBuffer();
|
||||
// Blob blob();
|
||||
// object json();
|
||||
// USVString text();
|
||||
[Throws]
|
||||
ArrayBuffer arrayBuffer();
|
||||
[Throws]
|
||||
Blob blob();
|
||||
[Throws]
|
||||
any json();
|
||||
USVString text();
|
||||
};
|
||||
|
|
|
@ -26,6 +26,15 @@
|
|||
#include "mozilla/dom/WorkerScope.h"
|
||||
#include "mozilla/dom/workers/bindings/ServiceWorker.h"
|
||||
|
||||
#ifndef MOZ_SIMPLEPUSH
|
||||
#include "nsIUnicodeDecoder.h"
|
||||
#include "nsIUnicodeEncoder.h"
|
||||
|
||||
#include "mozilla/dom/EncodingUtils.h"
|
||||
#include "mozilla/dom/FetchUtil.h"
|
||||
#include "mozilla/dom/TypedArray.h"
|
||||
#endif
|
||||
|
||||
#include "WorkerPrivate.h"
|
||||
|
||||
using namespace mozilla::dom;
|
||||
|
@ -435,16 +444,82 @@ NS_IMPL_CYCLE_COLLECTION_INHERITED(ExtendableEvent, Event, mPromises)
|
|||
|
||||
#ifndef MOZ_SIMPLEPUSH
|
||||
|
||||
PushMessageData::PushMessageData(const nsAString& aData)
|
||||
: mData(aData)
|
||||
namespace {
|
||||
nsresult
|
||||
ExtractBytesFromArrayBufferView(const ArrayBufferView& aView, nsTArray<uint8_t>& aBytes)
|
||||
{
|
||||
MOZ_ASSERT(aBytes.IsEmpty());
|
||||
aView.ComputeLengthAndData();
|
||||
aBytes.InsertElementsAt(0, aView.Data(), aView.Length());
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
ExtractBytesFromArrayBuffer(const ArrayBuffer& aBuffer, nsTArray<uint8_t>& aBytes)
|
||||
{
|
||||
MOZ_ASSERT(aBytes.IsEmpty());
|
||||
aBuffer.ComputeLengthAndData();
|
||||
aBytes.InsertElementsAt(0, aBuffer.Data(), aBuffer.Length());
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
ExtractBytesFromUSVString(const nsAString& aStr, nsTArray<uint8_t>& aBytes)
|
||||
{
|
||||
MOZ_ASSERT(aBytes.IsEmpty());
|
||||
nsCOMPtr<nsIUnicodeEncoder> encoder = EncodingUtils::EncoderForEncoding("UTF-8");
|
||||
if (!encoder) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
int32_t srcLen = aStr.Length();
|
||||
int32_t destBufferLen;
|
||||
nsresult rv = encoder->GetMaxLength(aStr.BeginReading(), srcLen, &destBufferLen);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
aBytes.SetLength(destBufferLen);
|
||||
|
||||
char* destBuffer = reinterpret_cast<char*>(aBytes.Elements());
|
||||
int32_t outLen = destBufferLen;
|
||||
rv = encoder->Convert(aStr.BeginReading(), &srcLen, destBuffer, &outLen);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
return rv;
|
||||
}
|
||||
|
||||
MOZ_ASSERT(outLen <= destBufferLen);
|
||||
aBytes.SetLength(outLen);
|
||||
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
nsresult
|
||||
ExtractBytesFromData(const OwningArrayBufferViewOrArrayBufferOrUSVString& aDataInit, nsTArray<uint8_t>& aBytes)
|
||||
{
|
||||
if (aDataInit.IsArrayBufferView()) {
|
||||
const ArrayBufferView& view = aDataInit.GetAsArrayBufferView();
|
||||
return ExtractBytesFromArrayBufferView(view, aBytes);
|
||||
} else if (aDataInit.IsArrayBuffer()) {
|
||||
const ArrayBuffer& buffer = aDataInit.GetAsArrayBuffer();
|
||||
return ExtractBytesFromArrayBuffer(buffer, aBytes);
|
||||
} else if (aDataInit.IsUSVString()) {
|
||||
return ExtractBytesFromUSVString(aDataInit.GetAsUSVString(), aBytes);
|
||||
}
|
||||
NS_NOTREACHED("Unexpected push message data");
|
||||
return NS_ERROR_FAILURE;
|
||||
}
|
||||
}
|
||||
|
||||
PushMessageData::PushMessageData(nsISupports* aOwner,
|
||||
const nsTArray<uint8_t>& aBytes)
|
||||
: mOwner(aOwner), mBytes(aBytes) {}
|
||||
|
||||
PushMessageData::~PushMessageData()
|
||||
{
|
||||
}
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE_0(PushMessageData);
|
||||
NS_IMPL_CYCLE_COLLECTION_WRAPPERCACHE(PushMessageData, mOwner)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTING_ADDREF(PushMessageData)
|
||||
NS_IMPL_CYCLE_COLLECTING_RELEASE(PushMessageData)
|
||||
|
@ -455,38 +530,114 @@ NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(PushMessageData)
|
|||
NS_INTERFACE_MAP_END
|
||||
|
||||
void
|
||||
PushMessageData::Json(JSContext* cx, JS::MutableHandle<JSObject*> aRetval)
|
||||
PushMessageData::Json(JSContext* cx, JS::MutableHandle<JS::Value> aRetval,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
//todo bug 1149195. Don't be lazy.
|
||||
NS_ABORT();
|
||||
if (NS_FAILED(EnsureDecodedText())) {
|
||||
aRv.Throw(NS_ERROR_DOM_UNKNOWN_ERR);
|
||||
return;
|
||||
}
|
||||
FetchUtil::ConsumeJson(cx, aRetval, mDecodedText, aRv);
|
||||
}
|
||||
|
||||
void
|
||||
PushMessageData::Text(nsAString& aData)
|
||||
{
|
||||
aData = mData;
|
||||
if (NS_SUCCEEDED(EnsureDecodedText())) {
|
||||
aData = mDecodedText;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PushMessageData::ArrayBuffer(JSContext* cx, JS::MutableHandle<JSObject*> aRetval)
|
||||
PushMessageData::ArrayBuffer(JSContext* cx,
|
||||
JS::MutableHandle<JSObject*> aRetval,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
//todo bug 1149195. Don't be lazy.
|
||||
NS_ABORT();
|
||||
uint8_t* data = GetContentsCopy();
|
||||
if (data) {
|
||||
FetchUtil::ConsumeArrayBuffer(cx, aRetval, mBytes.Length(), data, aRv);
|
||||
}
|
||||
}
|
||||
|
||||
mozilla::dom::Blob*
|
||||
PushMessageData::Blob()
|
||||
already_AddRefed<mozilla::dom::Blob>
|
||||
PushMessageData::Blob(ErrorResult& aRv)
|
||||
{
|
||||
//todo bug 1149195. Don't be lazy.
|
||||
NS_ABORT();
|
||||
uint8_t* data = GetContentsCopy();
|
||||
if (data) {
|
||||
nsRefPtr<mozilla::dom::Blob> blob = FetchUtil::ConsumeBlob(
|
||||
mOwner, EmptyString(), mBytes.Length(), data, aRv);
|
||||
if (blob) {
|
||||
return blob.forget();
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
NS_METHOD
|
||||
PushMessageData::EnsureDecodedText()
|
||||
{
|
||||
if (mBytes.IsEmpty() || !mDecodedText.IsEmpty()) {
|
||||
return NS_OK;
|
||||
}
|
||||
nsresult rv = FetchUtil::ConsumeText(
|
||||
mBytes.Length(),
|
||||
reinterpret_cast<uint8_t*>(mBytes.Elements()),
|
||||
mDecodedText
|
||||
);
|
||||
if (NS_WARN_IF(NS_FAILED(rv))) {
|
||||
mDecodedText.Truncate();
|
||||
return rv;
|
||||
}
|
||||
return NS_OK;
|
||||
}
|
||||
|
||||
uint8_t*
|
||||
PushMessageData::GetContentsCopy()
|
||||
{
|
||||
uint32_t length = mBytes.Length();
|
||||
void* data = malloc(length);
|
||||
if (!data) {
|
||||
return nullptr;
|
||||
}
|
||||
memcpy(data, mBytes.Elements(), length);
|
||||
return reinterpret_cast<uint8_t*>(data);
|
||||
}
|
||||
|
||||
PushEvent::PushEvent(EventTarget* aOwner)
|
||||
: ExtendableEvent(aOwner)
|
||||
{
|
||||
}
|
||||
|
||||
already_AddRefed<PushEvent>
|
||||
PushEvent::Constructor(mozilla::dom::EventTarget* aOwner,
|
||||
const nsAString& aType,
|
||||
const PushEventInit& aOptions,
|
||||
ErrorResult& aRv)
|
||||
{
|
||||
nsRefPtr<PushEvent> e = new PushEvent(aOwner);
|
||||
bool trusted = e->Init(aOwner);
|
||||
e->InitEvent(aType, aOptions.mBubbles, aOptions.mCancelable);
|
||||
e->SetTrusted(trusted);
|
||||
if(aOptions.mData.WasPassed()){
|
||||
nsTArray<uint8_t> bytes;
|
||||
nsresult rv = ExtractBytesFromData(aOptions.mData.Value(), bytes);
|
||||
if (NS_FAILED(rv)) {
|
||||
aRv.Throw(rv);
|
||||
return nullptr;
|
||||
}
|
||||
e->mData = new PushMessageData(aOwner, bytes);
|
||||
}
|
||||
return e.forget();
|
||||
}
|
||||
|
||||
NS_IMPL_ADDREF_INHERITED(PushEvent, ExtendableEvent)
|
||||
NS_IMPL_RELEASE_INHERITED(PushEvent, ExtendableEvent)
|
||||
|
||||
NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(PushEvent)
|
||||
NS_INTERFACE_MAP_END_INHERITING(ExtendableEvent)
|
||||
|
||||
NS_IMPL_CYCLE_COLLECTION_INHERITED(PushEvent, ExtendableEvent, mData)
|
||||
|
||||
#endif /* ! MOZ_SIMPLEPUSH */
|
||||
|
||||
END_WORKERS_NAMESPACE
|
||||
|
|
|
@ -17,6 +17,7 @@
|
|||
#ifndef MOZ_SIMPLEPUSH
|
||||
#include "mozilla/dom/PushEventBinding.h"
|
||||
#include "mozilla/dom/PushMessageDataBinding.h"
|
||||
#include "mozilla/dom/File.h"
|
||||
#endif
|
||||
|
||||
#include "nsProxyRelease.h"
|
||||
|
@ -168,8 +169,6 @@ public:
|
|||
class PushMessageData final : public nsISupports,
|
||||
public nsWrapperCache
|
||||
{
|
||||
nsString mData;
|
||||
|
||||
public:
|
||||
NS_DECL_CYCLE_COLLECTING_ISUPPORTS
|
||||
NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS(PushMessageData)
|
||||
|
@ -180,24 +179,30 @@ public:
|
|||
}
|
||||
|
||||
nsISupports* GetParentObject() const {
|
||||
return nullptr;
|
||||
return mOwner;
|
||||
}
|
||||
|
||||
void Json(JSContext* cx, JS::MutableHandle<JSObject*> aRetval);
|
||||
void Json(JSContext* cx, JS::MutableHandle<JS::Value> aRetval,
|
||||
ErrorResult& aRv);
|
||||
void Text(nsAString& aData);
|
||||
void ArrayBuffer(JSContext* cx, JS::MutableHandle<JSObject*> aRetval);
|
||||
mozilla::dom::Blob* Blob();
|
||||
void ArrayBuffer(JSContext* cx, JS::MutableHandle<JSObject*> aRetval,
|
||||
ErrorResult& aRv);
|
||||
already_AddRefed<mozilla::dom::Blob> Blob(ErrorResult& aRv);
|
||||
|
||||
explicit PushMessageData(const nsAString& aData);
|
||||
PushMessageData(nsISupports* aOwner, const nsTArray<uint8_t>& aBytes);
|
||||
private:
|
||||
nsCOMPtr<nsISupports> mOwner;
|
||||
nsTArray<uint8_t> mBytes;
|
||||
nsString mDecodedText;
|
||||
~PushMessageData();
|
||||
|
||||
NS_METHOD EnsureDecodedText();
|
||||
uint8_t* GetContentsCopy();
|
||||
};
|
||||
|
||||
class PushEvent final : public ExtendableEvent
|
||||
{
|
||||
// FIXME(nsm): Bug 1149195.
|
||||
// nsRefPtr<PushMessageData> mData;
|
||||
nsRefPtr<PushMessageData> mData;
|
||||
nsMainThreadPtrHandle<ServiceWorker> mServiceWorker;
|
||||
|
||||
protected:
|
||||
|
@ -205,8 +210,8 @@ protected:
|
|||
~PushEvent() {}
|
||||
|
||||
public:
|
||||
// FIXME(nsm): Bug 1149195.
|
||||
// Add cycle collection macros once data is re-exposed.
|
||||
NS_DECL_ISUPPORTS_INHERITED
|
||||
NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(PushEvent, ExtendableEvent)
|
||||
NS_FORWARD_TO_EVENT
|
||||
|
||||
virtual JSObject* WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override
|
||||
|
@ -217,18 +222,8 @@ public:
|
|||
static already_AddRefed<PushEvent>
|
||||
Constructor(mozilla::dom::EventTarget* aOwner,
|
||||
const nsAString& aType,
|
||||
const PushEventInit& aOptions)
|
||||
{
|
||||
nsRefPtr<PushEvent> e = new PushEvent(aOwner);
|
||||
bool trusted = e->Init(aOwner);
|
||||
e->InitEvent(aType, aOptions.mBubbles, aOptions.mCancelable);
|
||||
e->SetTrusted(trusted);
|
||||
// FIXME(nsm): Bug 1149195.
|
||||
//if(aOptions.mData.WasPassed()){
|
||||
// e->mData = new PushMessageData(aOptions.mData.Value());
|
||||
//}
|
||||
return e.forget();
|
||||
}
|
||||
const PushEventInit& aOptions,
|
||||
ErrorResult& aRv);
|
||||
|
||||
static already_AddRefed<PushEvent>
|
||||
Constructor(const GlobalObject& aGlobal,
|
||||
|
@ -237,7 +232,7 @@ public:
|
|||
ErrorResult& aRv)
|
||||
{
|
||||
nsCOMPtr<EventTarget> owner = do_QueryInterface(aGlobal.GetAsSupports());
|
||||
return Constructor(owner, aType, aOptions);
|
||||
return Constructor(owner, aType, aOptions, aRv);
|
||||
}
|
||||
|
||||
void PostInit(nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker)
|
||||
|
@ -247,9 +242,7 @@ public:
|
|||
|
||||
PushMessageData* Data()
|
||||
{
|
||||
// FIXME(nsm): Bug 1149195.
|
||||
MOZ_CRASH("Should not be called!");
|
||||
return nullptr;
|
||||
return mData;
|
||||
}
|
||||
};
|
||||
#endif /* ! MOZ_SIMPLEPUSH */
|
||||
|
|
|
@ -72,6 +72,10 @@
|
|||
#include "WorkerRunnable.h"
|
||||
#include "WorkerScope.h"
|
||||
|
||||
#ifndef MOZ_SIMPLEPUSH
|
||||
#include "mozilla/dom/TypedArray.h"
|
||||
#endif
|
||||
|
||||
#ifdef PostMessage
|
||||
#undef PostMessage
|
||||
#endif
|
||||
|
@ -2293,13 +2297,13 @@ public:
|
|||
|
||||
class SendPushEventRunnable final : public WorkerRunnable
|
||||
{
|
||||
nsString mData;
|
||||
nsTArray<uint8_t> mData;
|
||||
nsMainThreadPtrHandle<ServiceWorker> mServiceWorker;
|
||||
|
||||
public:
|
||||
SendPushEventRunnable(
|
||||
WorkerPrivate* aWorkerPrivate,
|
||||
const nsAString& aData,
|
||||
const nsTArray<uint8_t>& aData,
|
||||
nsMainThreadPtrHandle<ServiceWorker>& aServiceWorker)
|
||||
: WorkerRunnable(aWorkerPrivate, WorkerThreadModifyBusyCount)
|
||||
, mData(aData)
|
||||
|
@ -2316,9 +2320,12 @@ public:
|
|||
MOZ_ASSERT(aWorkerPrivate);
|
||||
GlobalObject globalObj(aCx, aWorkerPrivate->GlobalScope()->GetWrapper());
|
||||
|
||||
JSObject* data = Uint8Array::Create(aCx, mData.Length(), mData.Elements());
|
||||
if (!data) {
|
||||
return false;
|
||||
}
|
||||
PushEventInit pei;
|
||||
// FIXME(nsm): Bug 1149195.
|
||||
// pei.mData.Construct(mData);
|
||||
pei.mData.Construct().SetAsArrayBufferView().Init(data);
|
||||
pei.mBubbles = false;
|
||||
pei.mCancelable = false;
|
||||
|
||||
|
@ -2387,7 +2394,8 @@ public:
|
|||
NS_IMETHODIMP
|
||||
ServiceWorkerManager::SendPushEvent(const nsACString& aOriginAttributes,
|
||||
const nsACString& aScope,
|
||||
const nsAString& aData)
|
||||
uint32_t aDataLength,
|
||||
uint8_t* aDataBytes)
|
||||
{
|
||||
#ifdef MOZ_SIMPLEPUSH
|
||||
return NS_ERROR_NOT_AVAILABLE;
|
||||
|
@ -2406,8 +2414,13 @@ ServiceWorkerManager::SendPushEvent(const nsACString& aOriginAttributes,
|
|||
nsMainThreadPtrHandle<ServiceWorker> serviceWorkerHandle(
|
||||
new nsMainThreadPtrHolder<ServiceWorker>(serviceWorker));
|
||||
|
||||
nsTArray<uint8_t> data;
|
||||
if (!data.InsertElementsAt(0, aDataBytes, aDataLength, fallible)) {
|
||||
return NS_ERROR_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
nsRefPtr<SendPushEventRunnable> r =
|
||||
new SendPushEventRunnable(serviceWorker->GetWorkerPrivate(), aData,
|
||||
new SendPushEventRunnable(serviceWorker->GetWorkerPrivate(), data,
|
||||
serviceWorkerHandle);
|
||||
|
||||
AutoJSAPI jsapi;
|
||||
|
|
Загрузка…
Ссылка в новой задаче