Bug 982726 - Patch 3 - Implement client.postMessage and add tests for getServiced(). r=baku

--HG--
extra : rebase_source : 39ac96d409c4ea2ccf1794a7b2a0f681fc7c5786
extra : amend_source : 9357dd9eec14a5af6bd234189473d0bfdf5d6ba7
This commit is contained in:
Catalin Badea 2014-08-24 21:17:21 -07:00
parent 1ad2186153
commit dc60a6e2b1
11 changed files with 439 additions and 0 deletions

View File

@ -11,4 +11,6 @@
[Exposed=ServiceWorker]
interface ServiceWorkerClient {
readonly attribute unsigned long id;
[Throws]
void postMessage(any message, optional sequence<Transferable> transfer);
};

View File

@ -6,8 +6,13 @@
#include "ServiceWorkerClient.h"
#include "mozilla/dom/MessageEvent.h"
#include "nsGlobalWindow.h"
#include "WorkerPrivate.h"
#include "mozilla/dom/ServiceWorkerClientBinding.h"
using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::dom::workers;
@ -27,3 +32,128 @@ ServiceWorkerClient::WrapObject(JSContext* aCx)
return ServiceWorkerClientBinding::Wrap(aCx, this);
}
namespace {
class ServiceWorkerClientPostMessageRunnable MOZ_FINAL : public nsRunnable
{
uint64_t mId;
JSAutoStructuredCloneBuffer mBuffer;
nsTArray<nsCOMPtr<nsISupports>> mClonedObjects;
public:
ServiceWorkerClientPostMessageRunnable(uint64_t aId,
JSAutoStructuredCloneBuffer&& aData,
nsTArray<nsCOMPtr<nsISupports>>& aClonedObjects)
: mId(aId),
mBuffer(Move(aData))
{
mClonedObjects.SwapElements(aClonedObjects);
}
NS_IMETHOD
Run()
{
AssertIsOnMainThread();
nsGlobalWindow* window = nsGlobalWindow::GetInnerWindowWithId(mId);
if (!window) {
return NS_ERROR_FAILURE;
}
AutoJSAPI jsapi;
jsapi.Init(window);
JSContext* cx = jsapi.cx();
return DispatchDOMEvent(cx, window);
}
private:
NS_IMETHOD
DispatchDOMEvent(JSContext* aCx, nsGlobalWindow* aTargetWindow)
{
AssertIsOnMainThread();
// Release reference to objects that were AddRef'd for
// cloning into worker when array goes out of scope.
nsTArray<nsCOMPtr<nsISupports>> clonedObjects;
clonedObjects.SwapElements(mClonedObjects);
JS::Rooted<JS::Value> messageData(aCx);
if (!mBuffer.read(aCx, &messageData,
WorkerStructuredCloneCallbacks(true))) {
xpc::Throw(aCx, NS_ERROR_DOM_DATA_CLONE_ERR);
return NS_ERROR_FAILURE;
}
nsCOMPtr<nsIDOMMessageEvent> event = new MessageEvent(aTargetWindow,
nullptr, nullptr);
nsresult rv =
event->InitMessageEvent(NS_LITERAL_STRING("message"),
false /* non-bubbling */,
false /* not cancelable */,
messageData,
EmptyString(),
EmptyString(),
nullptr);
if (NS_FAILED(rv)) {
xpc::Throw(aCx, rv);
return NS_ERROR_FAILURE;
}
event->SetTrusted(true);
bool status = false;
aTargetWindow->DispatchEvent(event, &status);
if (!status) {
return NS_ERROR_FAILURE;
}
return NS_OK;
}
};
} // anonymous namespace
void
ServiceWorkerClient::PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
const Optional<Sequence<JS::Value>>& aTransferable,
ErrorResult& aRv)
{
WorkerPrivate* workerPrivate = GetCurrentThreadWorkerPrivate();
MOZ_ASSERT(workerPrivate);
workerPrivate->AssertIsOnWorkerThread();
JS::Rooted<JS::Value> transferable(aCx, JS::UndefinedValue());
if (aTransferable.WasPassed()) {
const Sequence<JS::Value>& realTransferable = aTransferable.Value();
JS::HandleValueArray elements =
JS::HandleValueArray::fromMarkedLocation(realTransferable.Length(),
realTransferable.Elements());
JSObject* array = JS_NewArrayObject(aCx, elements);
if (!array) {
aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
return;
}
transferable.setObject(*array);
}
JSStructuredCloneCallbacks* callbacks = WorkerStructuredCloneCallbacks(false);
nsTArray<nsCOMPtr<nsISupports>> clonedObjects;
JSAutoStructuredCloneBuffer buffer;
if (!buffer.write(aCx, aMessage, transferable, callbacks, &clonedObjects)) {
aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
return;
}
nsRefPtr<ServiceWorkerClientPostMessageRunnable> runnable =
new ServiceWorkerClientPostMessageRunnable(mId, Move(buffer), clonedObjects);
nsresult rv = NS_DispatchToMainThread(runnable);
if (NS_FAILED(rv)) {
aRv.Throw(NS_ERROR_FAILURE);
}
}

View File

@ -11,9 +11,14 @@
#include "nsWrapperCache.h"
namespace mozilla {
class ErrorResult;
namespace dom {
class Promise;
template<typename T> class Optional;
template<typename T> class Sequence;
namespace workers {
@ -36,6 +41,10 @@ public:
return mId;
}
void PostMessage(JSContext* aCx, JS::Handle<JS::Value> aMessage,
const Optional<Sequence<JS::Value>>& aTransferable,
ErrorResult& aRv);
nsISupports* GetParentObject() const
{
return mOwner;

View File

@ -0,0 +1,12 @@
onmessage = function(e) {
if (!e.data) {
dump("ERROR: message has no data.\n");
}
self.clients.getServiced().then(function(res) {
if (res.length === 0) {
dump("ERROR: no client is currently being controlled.\n");
}
res[res.length - 1].postMessage(res.length);
});
};

View File

@ -0,0 +1,7 @@
onmessage = function() {
self.clients.getServiced().then(function(result) {
for (i = 0; i < result.length; i++) {
result[i].postMessage(i);
}
});
};

View File

@ -0,0 +1,9 @@
onmessage = function(e) {
self.clients.getServiced().then(function(res) {
if (!res.length) {
dump("ERROR: no clients are currently controlled.\n");
}
res[0].postMessage(e.data);
});
};

View File

@ -12,11 +12,18 @@ support-files =
unregister/index.html
sw_clients/simple.html
get_serviced_worker.js
get_serviced_worker_advanced.js
message_posting_worker.js
sw_clients/service_worker_controlled.html
get_serviced_worker_enumerate.js
[test_get_serviced.html]
[test_get_serviced_advanced.html]
[test_get_serviced_enumerate.html]
[test_installation_simple.html]
[test_install_event.html]
[test_navigator.html]
[test_post_message.html]
[test_scopes.html]
[test_controller.html]
[test_unregister.html]

View File

@ -0,0 +1,39 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<!DOCTYPE HTML>
<html>
<head>
<title>controlled page</title>
<!--
Paged controlled by a service worker for testing getServiced().
See bug 982726.
-->
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<script class="testbody" type="text/javascript">
function fail(msg) {
info("service_worker_controlled.html: " + msg);
opener.postMessage("FAIL", "*");
}
if (!opener) {
info("service_worker_controlled.html should not be launched directly!");
}
window.onload = function() {
navigator.serviceWorker.ready.then(function(swr) {
opener.postMessage("READY", "*");
});
}
window.onmessage = function(msg) {
// forward message to the test page.
opener.postMessage(msg.data, "*");
};
</script>
</head>
<body>
</body>
</html>

View File

@ -0,0 +1,80 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<!DOCTYPE HTML>
<html>
<head>
<title>Bug 982726 - test get_serviced </title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test"></pre>
<script class="testbody" type="text/javascript">
var opened = [];
var registration;
function start() {
return navigator.serviceWorker.register("get_serviced_worker_advanced.js",
{ scope: "./sw_clients/" }).then((swr) => registration = swr);
}
function testGetServiced() {
var p = new Promise(function(res, rej) {
window.onmessage = function(e) {
if (e.data === "READY") {
opened.push(w);
registration.active.postMessage("getServiced");
} else if (e.data === opened.length) {
ok(true, "getServiced returned the correct value.");
res();
} else {
ok(false, "Wrong value. Expected: " + opened.length +
", got: " + e.data);
res();
}
}
});
var w;
setTimeout(function() {
w = window.open("sw_clients/service_worker_controlled.html");
}, 100);
return p;
}
function removeAndTest() {
opened.pop().close();
opened.pop().close();
return testGetServiced();
}
function runTest() {
start()
.then(testGetServiced)
.then(testGetServiced)
.then(testGetServiced)
.then(removeAndTest)
.then(function(e) {
while (opened.length) {
opened.pop().close();
}
SimpleTest.finish();
}).catch(function(e) {
ok(false, "Some test failed with error " + e);
SimpleTest.finish();
});
}
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({"set": [
["dom.serviceWorkers.enabled", true],
["dom.serviceWorkers.testing.enabled", true]
]}, runTest);
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,82 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<!DOCTYPE HTML>
<html>
<head>
<title>Bug 982726 - test get_serviced </title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test"></pre>
<script class="testbody" type="text/javascript">
var registration;
var opened = [];
var results = [];
function start() {
return navigator.serviceWorker.register("get_serviced_worker_enumerate.js",
{ scope: "./sw_clients/" }).then((swr) => registration = swr);
}
function openWindow() {
var p = new Promise(function(res, rej) {
var w = window.open("sw_clients/service_worker_controlled.html");
window.onmessage = function(e) {
if (e.data === "READY") {
opened.push(w);
res();
}
}
});
return p;
}
function testGetServiced() {
var count = 0;
return new Promise(function (res, rej) {
registration.active.postMessage("getServiced");
// wait for response
window.onmessage = function(msg) {
results[msg.data] = "ok";
count = count + 1;
if (count == opened.length) {
for (i = 0; i < count; i++) {
ok(results[i] == "ok", "Client received the message.");
}
res();
}
}
});
}
function runTest() {
start()
.then(openWindow).then(openWindow)
.then(openWindow).then(openWindow)
.then(testGetServiced)
.then(function(e) {
while (opened.length) {
opened.pop().close();
}
SimpleTest.finish();
}).catch(function(e) {
ok(false, "Some test failed with error " + e);
SimpleTest.finish();
});
}
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({"set": [
["dom.serviceWorkers.enabled", true],
["dom.serviceWorkers.testing.enabled", true]
]}, runTest);
</script>
</pre>
</body>
</html>

View File

@ -0,0 +1,62 @@
<!--
Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/
-->
<!DOCTYPE HTML>
<html>
<head>
<title>Bug 982726 - Test service worker post message </title>
<script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
<link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
</head>
<body>
<p id="display"></p>
<div id="content" style="display: none"></div>
<pre id="test"></pre>
<script class="testbody" type="text/javascript">
var magic_value = "MAGIC_VALUE_123";
function start() {
return navigator.serviceWorker.register("message_posting_worker.js",
{ scope: "./sw_clients/" });
}
function testPostMessage(swr) {
var p = new Promise(function(res, rej) {
window.onmessage = function(e) {
if (e.data === "READY") {
swr.active.postMessage(magic_value);
} else if (e.data === magic_value) {
ok(true, "Worker posted the correct value.");
res();
} else {
ok(false, "Wrong value. Expected: " + magic_value +
", got: " + e.data);
res();
}
}
});
var w;
setTimeout(function() {
w = window.open("sw_clients/service_worker_controlled.html");
}, 100);
return p.then(() => w.close());
}
function runTest() {
start()
.then(testPostMessage).catch(function(e) {
ok(false, "Some test failed with error " + e);
}).then(SimpleTest.finish);
}
SimpleTest.waitForExplicitFinish();
SpecialPowers.pushPrefEnv({"set": [
["dom.serviceWorkers.enabled", true],
["dom.serviceWorkers.testing.enabled", true]
]}, runTest);
</script>
</pre>
</body>
</html>