Commit cae3e925 authored by jsbell@chromium.org's avatar jsbell@chromium.org

Implement ServiceWorker::postMessage()

This allows the registering page, or pages loaded via the worker,
to send arbitrary messages to the worker script.

BUG=350103
R=abarth@chromium.org, adamk@chromium.org, kinuko@chromium.org, marja@chromium.org

Review URL: https://codereview.chromium.org/185643009

git-svn-id: svn://svn.chromium.org/blink/trunk@169668 bbb929c8-8fbe-4397-9dbb-9b2b20218538
parent 89d016a1
......@@ -963,3 +963,7 @@ crbug.com/353754 fast/scrolling/hover-during-scroll.html [ Pass Failure ]
crbug.com/353770 [ Linux ] media/encrypted-media/encrypted-media-playback-setmediakeys-after-src.html [ Pass Failure Timeout ]
crbug.com/353790 [ Linux Win ] virtual/threaded/animations/import.html [ Pass Timeout ]
crbug.com/353792 media/encrypted-media/encrypted-media-waiting-for-a-key.html [ Pass Failure Crash ]
# Disabled until Chromium side lands
crbug.com/350103 virtual/serviceworker/http/tests/serviceworker/indexeddb.html [ Skip ]
crbug.com/350103 virtual/serviceworker/http/tests/serviceworker/postmessage.html [ Skip ]
\ No newline at end of file
Verify that IndexedDB is functional in a ServiceWorker
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
messageChannel = new MessageChannel()
messageChannel.port1.onmessage = onMessageHandler
navigator.serviceWorker.register('indexeddb-worker.js')
FAIL DisabledError
PASS successfullyParsed is true
TEST COMPLETE
var port;
self.addEventListener('message', function(e) {
var message = e.data;
if ('port' in message) {
port = message.port;
doIndexedDBTest();
}
});
function send(action, text) {
if (port) port.postMessage({action: action, text: text});
}
function evalAndLog(s) {
send('log', s);
try {
return eval(s);
} catch (ex) {
send('fail', 'EXCEPTION: ' + ex);
throw ex;
}
}
function debug(s) {
send('log', s);
}
function doIndexedDBTest() {
debug('Preparing the database in the service worker');
var request = evalAndLog("indexedDB.deleteDatabase('db')");
request.onsuccess = function() {
request = evalAndLog("indexedDB.open('db')");
request.onupgradeneeded = function() {
db = request.result;
evalAndLog("db.createObjectStore('store')");
};
request.onsuccess = function() {
db = request.result;
evalAndLog("tx = db.transaction('store', 'readwrite')");
evalAndLog("store = tx.objectStore('store')");
evalAndLog("store.put('value', 'key')");
tx.oncomplete = function() {
send('quit');
};
};
};
}
<!DOCTYPE html>
<script src="/js-test-resources/js-test.js"></script>
<script>
description("Verify that IndexedDB is functional in a ServiceWorker");
var jsTestIsAsync = true;
evalAndLog("messageChannel = new MessageChannel()");
evalAndLog("messageChannel.port1.onmessage = onMessageHandler");
debug("");
evalAndLog("navigator.serviceWorker.register('indexeddb-worker.js')").then(
function(result) {
serviceWorker = result;
evalAndLog("serviceWorker.postMessage({port: messageChannel.port2}, [messageChannel.port2])");
},
function(reason) {
testFailed(reason.name);
finishJSTest();
});
function onMessageHandler(e) {
var prefix = "[ServiceWorker] ";
message = e.data;
switch (message.action) {
case 'log':
debug(prefix + message.text);
break;
case 'pass':
testPassed(prefix + message.text);
break;
case 'fail':
testFailed(prefix + message.text);
break;
case 'quit':
verifyDatabase();
break;
}
}
function verifyDatabase() {
debug("");
debug("Verifying the database from the page");
debug("");
evalAndLog("request = indexedDB.open('db')");
request.onsuccess = function() {
evalAndLog("db = request.result");
evalAndLog("tx = db.transaction('store')");
evalAndLog("store = tx.objectStore('store')");
evalAndLog("request = store.get('key')");
request.onsuccess = function() {
shouldBe("request.result", "'value'");
finishJSTest();
};
};
}
</script>
Tests postMessage to and from a ServiceWorker
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
messageChannel = new MessageChannel()
messageChannel.port1.onmessage = onMessageHandler
navigator.serviceWorker.register('postmessage-worker.js')
FAIL DisabledError
PASS successfullyParsed is true
TEST COMPLETE
var port;
self.addEventListener('message', function(e) {
var message = e.data;
if ('port' in message) {
port = message.port;
} else if ('value' in message) {
port.postMessage("Acking value: " + message.value);
} else if ('done' in message) {
port.postMessage("quit");
}
});
<!DOCTYPE html>
<script src="/js-test-resources/js-test.js"></script>
<script>
description("Tests postMessage to and from a ServiceWorker");
var jsTestIsAsync = true;
evalAndLog("messageChannel = new MessageChannel()");
evalAndLog("messageChannel.port1.onmessage = onMessageHandler");
debug("");
evalAndLog("navigator.serviceWorker.register('postmessage-worker.js')").then(
function(result) {
serviceWorker = result;
evalAndLog("serviceWorker.postMessage({port: messageChannel.port2}, [messageChannel.port2])");
evalAndLog("serviceWorker.postMessage({value: 1})");
evalAndLog("serviceWorker.postMessage({value: 2})");
evalAndLog("serviceWorker.postMessage({done: true})");
},
function(reason) {
testFailed(reason.name);
finishJSTest();
});
function onMessageHandler(e) {
message = e.data;
debug("");
debug("onMessageHandler: " + JSON.stringify(message));
if (message === "quit")
finishJSTest();
}
</script>
Verify that IndexedDB is functional in a ServiceWorker
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
messageChannel = new MessageChannel()
messageChannel.port1.onmessage = onMessageHandler
navigator.serviceWorker.register('indexeddb-worker.js')
serviceWorker.postMessage({port: messageChannel.port2}, [messageChannel.port2])
[ServiceWorker] Preparing the database in the service worker
[ServiceWorker] indexedDB.deleteDatabase('db')
[ServiceWorker] indexedDB.open('db')
[ServiceWorker] db.createObjectStore('store')
[ServiceWorker] tx = db.transaction('store', 'readwrite')
[ServiceWorker] store = tx.objectStore('store')
[ServiceWorker] store.put('value', 'key')
Verifying the database from the page
request = indexedDB.open('db')
db = request.result
tx = db.transaction('store')
store = tx.objectStore('store')
request = store.get('key')
PASS request.result is 'value'
PASS successfullyParsed is true
TEST COMPLETE
Tests postMessage to and from a ServiceWorker
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
messageChannel = new MessageChannel()
messageChannel.port1.onmessage = onMessageHandler
navigator.serviceWorker.register('postmessage-worker.js')
serviceWorker.postMessage({port: messageChannel.port2}, [messageChannel.port2])
serviceWorker.postMessage({value: 1})
serviceWorker.postMessage({value: 2})
serviceWorker.postMessage({done: true})
onMessageHandler: "Acking value: 1"
onMessageHandler: "Acking value: 2"
onMessageHandler: "quit"
PASS successfullyParsed is true
TEST COMPLETE
......@@ -217,15 +217,16 @@
'v8/custom/V8SQLTransactionSyncCustom.cpp',
'v8/custom/V8SVGElementCustom.cpp',
'v8/custom/V8SVGPathSegCustom.cpp',
'v8/custom/V8ServiceWorkerCustom.cpp',
'v8/custom/V8StyleSheetCustom.cpp',
'v8/custom/V8TextCustom.cpp',
'v8/custom/V8TextTrackCueCustom.cpp',
'v8/custom/V8TrackEventCustom.cpp',
'v8/custom/V8TypedArrayCustom.h',
'v8/custom/V8Uint8ArrayCustom.h',
'v8/custom/V8Uint8ClampedArrayCustom.h',
'v8/custom/V8Uint16ArrayCustom.h',
'v8/custom/V8Uint32ArrayCustom.h',
'v8/custom/V8Uint8ArrayCustom.h',
'v8/custom/V8Uint8ClampedArrayCustom.h',
'v8/custom/V8WebGLRenderingContextBaseCustom.cpp',
'v8/custom/V8WebKitPointCustom.cpp',
'v8/custom/V8WindowCustom.cpp',
......
// Copyright 2014 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "config.h"
#include "V8ServiceWorker.h"
#include "bindings/v8/ExceptionMessages.h"
#include "bindings/v8/ExceptionState.h"
#include "bindings/v8/SerializedScriptValue.h"
#include "bindings/v8/V8Binding.h"
#include "core/dom/MessagePort.h"
#include "modules/serviceworkers/ServiceWorker.h"
#include "wtf/ArrayBuffer.h"
namespace WebCore {
void V8ServiceWorker::postMessageMethodCustom(const v8::FunctionCallbackInfo<v8::Value>& info)
{
ExceptionState exceptionState(ExceptionState::ExecutionContext, "postMessage", "ServiceWorker", info.Holder(), info.GetIsolate());
ServiceWorker* worker = V8ServiceWorker::toNative(info.Holder());
MessagePortArray ports;
ArrayBufferArray arrayBuffers;
if (info.Length() > 1) {
const int transferablesArgIndex = 1;
if (!SerializedScriptValue::extractTransferables(info[transferablesArgIndex], transferablesArgIndex, ports, arrayBuffers, exceptionState, info.GetIsolate())) {
exceptionState.throwIfNeeded();
return;
}
}
RefPtr<SerializedScriptValue> message = SerializedScriptValue::create(info[0], &ports, &arrayBuffers, exceptionState, info.GetIsolate());
if (exceptionState.throwIfNeeded())
return;
worker->postMessage(message.release(), &ports, exceptionState);
exceptionState.throwIfNeeded();
}
} // namespace WebCore
......@@ -87,13 +87,33 @@ void MessagePort::postMessage(PassRefPtr<SerializedScriptValue> message, const M
}
blink::WebString messageString = message->toWireString();
blink::WebMessagePortChannelArray* webChannels = 0;
OwnPtr<blink::WebMessagePortChannelArray> webChannels = toWebMessagePortChannelArray(channels.release());
m_entangledChannel->postMessage(messageString, webChannels.leakPtr());
}
// static
PassOwnPtr<blink::WebMessagePortChannelArray> MessagePort::toWebMessagePortChannelArray(PassOwnPtr<MessagePortChannelArray> channels)
{
OwnPtr<blink::WebMessagePortChannelArray> webChannels;
if (channels && channels->size()) {
webChannels = new blink::WebMessagePortChannelArray(channels->size());
webChannels = adoptPtr(new blink::WebMessagePortChannelArray(channels->size()));
for (size_t i = 0; i < channels->size(); ++i)
(*webChannels)[i] = (*channels)[i].leakPtr();
}
m_entangledChannel->postMessage(messageString, webChannels);
return webChannels.release();
}
// static
PassOwnPtr<MessagePortArray> MessagePort::toMessagePortArray(ExecutionContext* context, const blink::WebMessagePortChannelArray& webChannels)
{
OwnPtr<MessagePortArray> ports;
if (!webChannels.isEmpty()) {
OwnPtr<MessagePortChannelArray> channels = adoptPtr(new MessagePortChannelArray(webChannels.size()));
for (size_t i = 0; i < webChannels.size(); ++i)
(*channels)[i] = adoptPtr(webChannels[i]);
ports = MessagePort::entanglePorts(*context, channels.release());
}
return ports.release();
}
PassOwnPtr<blink::WebMessagePortChannel> MessagePort::disentangle()
......
......@@ -74,6 +74,9 @@ public:
void entangle(PassOwnPtr<blink::WebMessagePortChannel>);
PassOwnPtr<blink::WebMessagePortChannel> disentangle();
static PassOwnPtr<blink::WebMessagePortChannelArray> toWebMessagePortChannelArray(PassOwnPtr<MessagePortChannelArray>);
static PassOwnPtr<MessagePortArray> toMessagePortArray(ExecutionContext*, const blink::WebMessagePortChannelArray&);
// Returns 0 if there is an exception, or if the passed-in array is 0/empty.
static PassOwnPtr<MessagePortChannelArray> disentanglePorts(const MessagePortArray*, ExceptionState&);
......
......@@ -31,6 +31,10 @@
#include "config.h"
#include "ServiceWorker.h"
#include "bindings/v8/ExceptionState.h"
#include "core/dom/MessagePort.h"
#include "public/platform/WebMessagePortChannel.h"
#include "public/platform/WebString.h"
namespace WebCore {
......@@ -39,4 +43,16 @@ ServiceWorker::ServiceWorker(PassOwnPtr<blink::WebServiceWorker> worker)
{
}
void ServiceWorker::postMessage(PassRefPtr<SerializedScriptValue> message, const MessagePortArray* ports, ExceptionState& exceptionState)
{
// Disentangle the port in preparation for sending it to the remote context.
OwnPtr<MessagePortChannelArray> channels = MessagePort::disentanglePorts(ports, exceptionState);
if (exceptionState.hadException())
return;
blink::WebString messageString = message->toWireString();
OwnPtr<blink::WebMessagePortChannelArray> webChannels = MessagePort::toWebMessagePortChannelArray(channels.release());
m_outerWorker->postMessage(messageString, webChannels.leakPtr());
}
} // namespace WebCore
......@@ -31,6 +31,7 @@
#ifndef ServiceWorker_h
#define ServiceWorker_h
#include "bindings/v8/SerializedScriptValue.h"
#include "public/platform/WebServiceWorker.h"
#include "wtf/OwnPtr.h"
#include "wtf/PassOwnPtr.h"
......@@ -59,6 +60,8 @@ public:
~ServiceWorker() { }
void postMessage(PassRefPtr<SerializedScriptValue> message, const MessagePortArray*, ExceptionState&);
private:
explicit ServiceWorker(PassOwnPtr<blink::WebServiceWorker>);
......
......@@ -31,4 +31,7 @@
RuntimeEnabled=ServiceWorker,
NoInterfaceObject
] interface ServiceWorker {
// FIXME: Should inherit this from Worker.
[Custom, RaisesException] void postMessage(SerializedScriptValue message, optional MessagePort[] messagePorts);
};
......@@ -35,4 +35,3 @@
attribute EventHandler onactivate;
attribute EventHandler onfetch;
};
......@@ -32,9 +32,12 @@
#include "ServiceWorkerGlobalScopeProxy.h"
#include "WebEmbeddedWorkerImpl.h"
#include "WebSerializedScriptValue.h"
#include "WebServiceWorkerContextClient.h"
#include "bindings/v8/WorkerScriptController.h"
#include "core/dom/ExecutionContext.h"
#include "core/dom/MessagePort.h"
#include "core/events/MessageEvent.h"
#include "core/events/ThreadLocalEventNames.h"
#include "core/workers/WorkerGlobalScope.h"
#include "modules/serviceworkers/FetchEvent.h"
......@@ -74,6 +77,15 @@ void ServiceWorkerGlobalScopeProxy::dispatchFetchEvent(int eventID)
observer->didDispatchEvent();
}
void ServiceWorkerGlobalScopeProxy::dispatchMessageEvent(const WebString& message, const WebMessagePortChannelArray& webChannels)
{
ASSERT(m_workerGlobalScope);
OwnPtr<MessagePortArray> ports = MessagePort::toMessagePortArray(m_workerGlobalScope, webChannels);
WebSerializedScriptValue value = WebSerializedScriptValue::fromString(message);
m_workerGlobalScope->dispatchEvent(MessageEvent::create(ports.release(), value));
}
void ServiceWorkerGlobalScopeProxy::reportException(const String& errorMessage, int lineNumber, int columnNumber, const String& sourceURL)
{
m_client.reportException(errorMessage, lineNumber, columnNumber, sourceURL);
......
......@@ -68,6 +68,7 @@ public:
// WebServiceWorkerContextProxy overrides:
virtual void dispatchInstallEvent(int) OVERRIDE;
virtual void dispatchFetchEvent(int) OVERRIDE;
virtual void dispatchMessageEvent(const WebString& message, const WebMessagePortChannelArray&) OVERRIDE;
// WorkerReportingProxy overrides:
virtual void reportException(const String& errorMessage, int lineNumber, int columnNumber, const String& sourceURL) OVERRIDE;
......
......@@ -53,12 +53,8 @@ void WebDOMMessageEvent::initMessageEvent(const WebString& type, bool canBubble,
if (sourceFrame)
window = toWebFrameImpl(sourceFrame)->frame()->domWindow();
OwnPtr<MessagePortArray> ports;
if (!webChannels.isEmpty() && sourceFrame) {
OwnPtr<MessagePortChannelArray> channels = adoptPtr(new MessagePortChannelArray(webChannels.size()));
for (size_t i = 0; i < webChannels.size(); ++i)
(*channels)[i] = adoptPtr(webChannels[i]);
ports = MessagePort::entanglePorts(*window->document(), channels.release());
}
if (sourceFrame)
ports = MessagePort::toMessagePortArray(window->document(), webChannels);
unwrap<MessageEvent>()->initMessageEvent(type, canBubble, cancelable, messageData, origin, lastEventId, window, ports.release());
}
......
......@@ -50,6 +50,7 @@ public:
// WebKit versions of WebCore::MessagePortChannel.
virtual void entangle(WebMessagePortChannel*) = 0;
// Callee receives ownership of the passed vector.
// FIXME: Blob refs should be passed to maintain ref counts. crbug.com/351753
virtual void postMessage(const WebString&, WebMessagePortChannelArray*) = 0;
virtual bool tryGetMessage(WebString*, WebMessagePortChannelArray&) = 0;
......
......@@ -31,10 +31,22 @@
#ifndef WebServiceWorker_h
#define WebServiceWorker_h
#include "WebCommon.h"
#include "WebMessagePortChannel.h"
#include "WebVector.h"
namespace blink {
class WebString;
typedef WebVector<class WebMessagePortChannel*> WebMessagePortChannelArray;
class WebServiceWorker {
public:
virtual ~WebServiceWorker() { }
// Callee receives ownership of the passed vector.
// FIXME: Blob refs should be passed to maintain ref counts. crbug.com/351753
virtual void postMessage(const WebString&, WebMessagePortChannelArray*) { BLINK_ASSERT_NOT_REACHED(); }
};
}
......
......@@ -31,6 +31,8 @@
#ifndef WebServiceWorkerContextProxy_h
#define WebServiceWorkerContextProxy_h
#include "public/platform/WebMessagePortChannel.h"
namespace blink {
class WebString;
......@@ -46,6 +48,8 @@ public:
// FIXME: This needs to pass the fetch request info.
virtual void dispatchFetchEvent(int fetchEventID) = 0;
virtual void dispatchMessageEvent(const WebString& message, const WebMessagePortChannelArray& channels) = 0;
virtual void resumeWorkerContext() { }
virtual void attachDevTools() { }
virtual void reattachDevTools(const WebString& savedState) { }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment