Bug 1149195 - Expose push message data accessors. r=nsm,dragana,smaug

--HG--
extra : commitid : 10hopC95zuL
extra : rebase_source : c3c9cfac62d1acb234bba2f16b443783354c43a4
This commit is contained in:
Kit Cambridge 2015-09-17 05:10:42 -07:00
Родитель 6c8172cef8
Коммит cc7189cb64
18 изменённых файлов: 969 добавлений и 219 удалений

Просмотреть файл

@ -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);

206
dom/push/test/webpush.js Normal file
Просмотреть файл

@ -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;