Commit eadc20d4 authored by yutak@chromium.org's avatar yutak@chromium.org

WebSocket: Receive binary message as Blob

https://bugs.webkit.org/show_bug.cgi?id=67115

Reviewed by Kent Tamura.

Source/WebCore:

Make WebSocketChannel be able to receive WebSocket binary messages and deliver them via
didReceiveBinaryData() callback of WebSocketChannelClient.

Tests: http/tests/websocket/tests/hybi/fragmented-binary-frames.html
       http/tests/websocket/tests/hybi/receive-blob.html
       http/tests/websocket/tests/hybi/workers/receive-blob.html

* bindings/js/JSMessageEventCustom.cpp:
(WebCore::JSMessageEvent::data): Convert Blob to JSValue.
* bindings/v8/custom/V8MessageEventCustom.cpp:
(WebCore::V8MessageEvent::dataAccessorGetter): Convert Blob to v8::Value.
* dom/MessageEvent.cpp:
(WebCore::MessageEvent::MessageEvent):
* dom/MessageEvent.h:
Added DataTypeBlob and Blob-related functions.
(WebCore::MessageEvent::create):
(WebCore::MessageEvent::dataAsBlob):
* websockets/ThreadableWebSocketChannelClientWrapper.cpp:
(WebCore::ThreadableWebSocketChannelClientWrapper::didReceiveBinaryData):
(WebCore::ThreadableWebSocketChannelClientWrapper::didReceiveBinaryDataCallback):
* websockets/ThreadableWebSocketChannelClientWrapper.h:
* websockets/WebSocket.cpp:
(WebCore::WebSocket::didReceiveBinaryData):
If binaryType attribute is "blob", construct a Blob using BlobData filled with the given
binary message.
* websockets/WebSocket.h:
* websockets/WebSocketChannel.cpp:
(WebCore::WebSocketChannel::processFrame):
Create continuousFrameData as OwnPtr<> rather than stack-allocated value so we can easily
pass it to didReceiveBinaryData() callback.
Save the content of a fragmented message even if it is a binary opcode.
* websockets/WebSocketChannelClient.h:
(WebCore::WebSocketChannelClient::didReceiveBinaryData):
* websockets/WorkerThreadableWebSocketChannel.cpp:
(WebCore::workerContextDidReceiveBinaryData):
(WebCore::WorkerThreadableWebSocketChannel::Peer::didReceiveBinaryData):
Pass binaryData as PassOwnPtr<Vector<char> > to deliver the value efficiently (without
copying the content) across threads.
* websockets/WorkerThreadableWebSocketChannel.h:

LayoutTests:

* http/tests/websocket/tests/hybi/binary-frames_wsh.py: Added. Used from receive-blob.html.
* http/tests/websocket/tests/hybi/fragmented-binary-frames-expected.txt: Added.
* http/tests/websocket/tests/hybi/fragmented-binary-frames.html:
Added. The content of this test is almost identical to receive-blob.html.
* http/tests/websocket/tests/hybi/fragmented-binary-frames_wsh.py: Added.
* http/tests/websocket/tests/hybi/receive-blob-expected.txt: Added.
* http/tests/websocket/tests/hybi/receive-blob.html:
Added. It is a bit complicated to validate the content of a Blob, because a Blob must be read
asynchronously. FileReader is used to read a blob as an ArrayBuffer, each byte of which is
compared with the expected result.
* http/tests/websocket/tests/hybi/workers/receive-blob-expected.txt: Added.
* http/tests/websocket/tests/hybi/workers/receive-blob.html: Added.
* http/tests/websocket/tests/hybi/workers/resources/binary-frames_wsh.py:
Added. Same as above binary-frames_wsh.py.
* http/tests/websocket/tests/hybi/workers/resources/receive-blob.js: Added.


git-svn-id: http://svn.webkit.org/repository/webkit/trunk@94041 268f45cc-cd09-0410-ab3c-d52691b4dbfc
parent e28a4437
2011-08-29 Yuta Kitamura <yutak@chromium.org>
WebSocket: Receive binary message as Blob
https://bugs.webkit.org/show_bug.cgi?id=67115
Reviewed by Kent Tamura.
* http/tests/websocket/tests/hybi/binary-frames_wsh.py: Added. Used from receive-blob.html.
* http/tests/websocket/tests/hybi/fragmented-binary-frames-expected.txt: Added.
* http/tests/websocket/tests/hybi/fragmented-binary-frames.html:
Added. The content of this test is almost identical to receive-blob.html.
* http/tests/websocket/tests/hybi/fragmented-binary-frames_wsh.py: Added.
* http/tests/websocket/tests/hybi/receive-blob-expected.txt: Added.
* http/tests/websocket/tests/hybi/receive-blob.html:
Added. It is a bit complicated to validate the content of a Blob, because a Blob must be read
asynchronously. FileReader is used to read a blob as an ArrayBuffer, each byte of which is
compared with the expected result.
* http/tests/websocket/tests/hybi/workers/receive-blob-expected.txt: Added.
* http/tests/websocket/tests/hybi/workers/receive-blob.html: Added.
* http/tests/websocket/tests/hybi/workers/resources/binary-frames_wsh.py:
Added. Same as above binary-frames_wsh.py.
* http/tests/websocket/tests/hybi/workers/resources/receive-blob.js: Added.
2011-08-29 Ryosuke Niwa <rniwa@webkit.org>
Add a test for lastChangeWasUserEdit in HTMLInputElement and HTMLTextAreaElement
from mod_pywebsocket import common
from mod_pywebsocket import stream
def web_socket_do_extra_handshake(request):
pass
def web_socket_transfer_data(request):
messages_to_send = ['Hello, world!', '', all_distinct_bytes()]
for message in messages_to_send:
# FIXME: Should use better API to send binary messages when pywebsocket supports it.
header = stream.create_header(common.OPCODE_BINARY, len(message), 1, 0, 0, 0, 0)
request.connection.write(header + message)
def all_distinct_bytes():
return ''.join([chr(i) for i in xrange(256)])
WebSocket: Receive fragmented binary messages.
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
PASS ws.binaryType is "blob"
PASS receivedMessages.length is 4
Checking message #0.
PASS responseType is "[object Blob]"
PASS actualArray.length is 13
PASS Passed: Message #0.
Checking message #1.
PASS responseType is "[object Blob]"
PASS actualArray.length is 13
PASS Passed: Message #1.
Checking message #2.
PASS responseType is "[object Blob]"
PASS actualArray.length is 0
PASS Passed: Message #2.
Checking message #3.
PASS responseType is "[object Blob]"
PASS actualArray.length is 256
PASS Passed: Message #3.
PASS successfullyParsed is true
TEST COMPLETE
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="../../../../js-test-resources/js-test-style.css">
<script src="../../../../js-test-resources/js-test-pre.js"></script>
</head>
<body>
<div id="description"></div>
<div id="console"></div>
<script>
description("WebSocket: Receive fragmented binary messages.");
window.jsTestIsAsync = true;
if (window.layoutTestController)
layoutTestController.overridePreference("WebKitHixie76WebSocketProtocolEnabled", 0);
function createHelloWorldValue()
{
var hello = "Hello, world!";
var array = new Uint8Array(hello.length);
for (var i = 0; i < hello.length; ++i)
array[i] = hello.charCodeAt(i);
return array.buffer;
}
function createEmptyValue()
{
return new ArrayBuffer(0);
}
// Create an ArrayBuffer containing all distinct bytes ("\x00" to "\xFF").
function createAllBytesValue()
{
var array = new Uint8Array(256);
for (var i = 0; i < 256; ++i)
array[i] = i;
return array.buffer;
}
var ws = new WebSocket("ws://127.0.0.1:8880/websocket/tests/hybi/fragmented-binary-frames");
shouldBeEqualToString("ws.binaryType", "blob");
var closeEvent;
var receivedMessages = [];
var expectedValues = [createHelloWorldValue(), createHelloWorldValue(), createEmptyValue(), createAllBytesValue()];
ws.onmessage = function(event)
{
receivedMessages.push(event.data);
};
ws.onclose = function(event)
{
closeEvent = event;
shouldEvaluateTo("receivedMessages.length", expectedValues.length);
check(0);
};
var responseType;
function check(index)
{
if (index == expectedValues.length) {
finishJSTest();
return;
}
debug("Checking message #" + index + ".");
responseType = '' + receivedMessages[index];
shouldBeEqualToString("responseType", "[object Blob]");
var reader = new FileReader();
reader.readAsArrayBuffer(receivedMessages[index]);
reader.onload = function(event)
{
checkArrayBuffer(index, reader.result, expectedValues[index]);
check(index + 1);
};
reader.onerror = function(event)
{
testFailed("Failed to read blob: error code = " + reader.error.code);
check(index + 1);
};
}
var actualArray;
var expectedArray;
function checkArrayBuffer(testIndex, actual, expected)
{
actualArray = new Uint8Array(actual);
expectedArray = new Uint8Array(expected);
shouldEvaluateTo("actualArray.length", expectedArray.length);
// Print only the first mismatched byte in order not to flood console.
for (var i = 0; i < expectedArray.length; ++i) {
if (actualArray[i] != expectedArray[i]) {
testFailed("Value mismatch: actualArray[" + i + "] = " + actualArray[i] + ", expectedArray[" + i + "] = " + expectedArray[i]);
return;
}
}
testPassed("Passed: Message #" + testIndex + ".");
}
var successfullyParsed = true;
</script>
<script src="../../../../js-test-resources/js-test-post.js"></script>
</body>
</html>
from mod_pywebsocket import common
from mod_pywebsocket import stream
def web_socket_do_extra_handshake(request):
pass
def web_socket_transfer_data(request):
messages_to_send = [['Hello, ', 'world!'],
['', 'Hello, ', '', 'world!', ''],
['', '', ''],
[chr(i) for i in xrange(256)]]
for message_list in messages_to_send:
for index, message in enumerate(message_list):
# FIXME: Should use better API to send binary messages when pywebsocket supports it.
if index == 0:
opcode = common.OPCODE_BINARY
else:
opcode = common.OPCODE_CONTINUATION
if index < len(message_list) - 1:
final = 0
else:
final = 1
header = stream.create_header(opcode, len(message), final, 0, 0, 0, 0)
request.connection.write(header + message)
WebSocket: Receive Blobs.
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
PASS ws.binaryType is "blob"
PASS receivedMessages.length is 3
Checking message #0.
PASS responseType is "[object Blob]"
PASS actualArray.length is 13
PASS Passed: Message #0.
Checking message #1.
PASS responseType is "[object Blob]"
PASS actualArray.length is 0
PASS Passed: Message #1.
Checking message #2.
PASS responseType is "[object Blob]"
PASS actualArray.length is 256
PASS Passed: Message #2.
PASS successfullyParsed is true
TEST COMPLETE
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="../../../../js-test-resources/js-test-style.css">
<script src="../../../../js-test-resources/js-test-pre.js"></script>
</head>
<body>
<div id="description"></div>
<div id="console"></div>
<script>
description("WebSocket: Receive Blobs.");
window.jsTestIsAsync = true;
if (window.layoutTestController)
layoutTestController.overridePreference("WebKitHixie76WebSocketProtocolEnabled", 0);
function createHelloWorldValue()
{
var hello = "Hello, world!";
var array = new Uint8Array(hello.length);
for (var i = 0; i < hello.length; ++i)
array[i] = hello.charCodeAt(i);
return array.buffer;
}
function createEmptyValue()
{
return new ArrayBuffer(0);
}
// Create an ArrayBuffer containing all distinct bytes ("\x00" to "\xFF").
function createAllBytesValue()
{
var array = new Uint8Array(256);
for (var i = 0; i < 256; ++i)
array[i] = i;
return array.buffer;
}
var ws = new WebSocket("ws://127.0.0.1:8880/websocket/tests/hybi/binary-frames");
shouldBeEqualToString("ws.binaryType", "blob");
var closeEvent;
var receivedMessages = [];
var expectedValues = [createHelloWorldValue(), createEmptyValue(), createAllBytesValue()];
ws.onmessage = function(event)
{
receivedMessages.push(event.data);
};
ws.onclose = function(event)
{
closeEvent = event;
shouldEvaluateTo("receivedMessages.length", expectedValues.length);
check(0);
};
var responseType;
function check(index)
{
if (index == expectedValues.length) {
finishJSTest();
return;
}
debug("Checking message #" + index + ".");
responseType = '' + receivedMessages[index];
shouldBeEqualToString("responseType", "[object Blob]");
var reader = new FileReader();
reader.readAsArrayBuffer(receivedMessages[index]);
reader.onload = function(event)
{
checkArrayBuffer(index, reader.result, expectedValues[index]);
check(index + 1);
};
reader.onerror = function(event)
{
testFailed("Failed to read blob: error code = " + reader.error.code);
check(index + 1);
};
}
var actualArray;
var expectedArray;
function checkArrayBuffer(testIndex, actual, expected)
{
actualArray = new Uint8Array(actual);
expectedArray = new Uint8Array(expected);
shouldEvaluateTo("actualArray.length", expectedArray.length);
// Print only the first mismatched byte in order not to flood console.
for (var i = 0; i < expectedArray.length; ++i) {
if (actualArray[i] != expectedArray[i]) {
testFailed("Value mismatch: actualArray[" + i + "] = " + actualArray[i] + ", expectedArray[" + i + "] = " + expectedArray[i]);
return;
}
}
testPassed("Passed: Message #" + testIndex + ".");
}
var successfullyParsed = true;
</script>
<script src="../../../../js-test-resources/js-test-post.js"></script>
</body>
</html>
WebSocket: Receive Blobs in Web Workers.
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
PASS PASS: ws.binaryType is "blob"
PASS PASS: receivedMessages.length is 3
INFO: Checking message #0.
PASS PASS: responseType is "[object Blob]"
PASS PASS: actualArray.length is 13
PASS PASS: Passed: Message #0.
INFO: Checking message #1.
PASS PASS: responseType is "[object Blob]"
PASS PASS: actualArray.length is 0
PASS PASS: Passed: Message #1.
INFO: Checking message #2.
PASS PASS: responseType is "[object Blob]"
PASS PASS: actualArray.length is 256
PASS PASS: Passed: Message #2.
DONE
PASS successfullyParsed is true
TEST COMPLETE
<!DOCTYPE html>
<html>
<head>
<link rel="stylesheet" href="../../../../../js-test-resources/js-test-style.css">
<script src="../../../../../js-test-resources/js-test-pre.js"></script>
</head>
<body>
<div id="description"></div>
<div id="console"></div>
<script type="text/javascript">
description("WebSocket: Receive Blobs in Web Workers.");
window.jsTestIsAsync = true;
if (window.layoutTestController)
layoutTestController.overridePreference("WebKitHixie76WebSocketProtocolEnabled", 0);
function startsWith(str, prefix)
{
return str.indexOf(prefix) == 0;
}
var worker = new Worker("resources/receive-blob.js");
worker.onmessage = function (event)
{
var message = event.data;
if (startsWith(message, "PASS"))
testPassed(message);
else if (startsWith(message, "FAIL"))
testFailed(message)
else
debug(message);
if (message === "DONE")
finishJSTest();
};
var successfullyParsed = true;
</script>
<script src="../../../../../js-test-resources/js-test-post.js"></script>
</body>
</html>
from mod_pywebsocket import common
from mod_pywebsocket import stream
def web_socket_do_extra_handshake(request):
pass
def web_socket_transfer_data(request):
messages_to_send = ['Hello, world!', '', all_distinct_bytes()]
for message in messages_to_send:
# FIXME: Should use better API to send binary messages when pywebsocket supports it.
header = stream.create_header(common.OPCODE_BINARY, len(message), 1, 0, 0, 0, 0)
request.connection.write(header + message)
def all_distinct_bytes():
return ''.join([chr(i) for i in xrange(256)])
function createHelloWorldValue()
{
var hello = "Hello, world!";
var array = new Uint8Array(hello.length);
for (var i = 0; i < hello.length; ++i)
array[i] = hello.charCodeAt(i);
return array.buffer;
}
function createEmptyValue()
{
return new ArrayBuffer(0);
}
// Create an ArrayBuffer containing all distinct bytes ("\x00" to "\xFF").
function createAllBytesValue()
{
var array = new Uint8Array(256);
for (var i = 0; i < 256; ++i)
array[i] = i;
return array.buffer;
}
var ws = new WebSocket("ws://127.0.0.1:8880/websocket/tests/hybi/workers/resources/binary-frames");
if (ws.binaryType === "blob")
postMessage("PASS: ws.binaryType is \"blob\"");
else
postMessage("FAIL: ws.binaryType should be \"blob\" but was \"" + ws.binaryType + "\"");
var receivedMessages = [];
var expectedValues = [createHelloWorldValue(), createEmptyValue(), createAllBytesValue()];
ws.onmessage = function(event)
{
receivedMessages.push(event.data);
};
ws.onclose = function(closeEvent)
{
if (receivedMessages.length === expectedValues.length)
postMessage("PASS: receivedMessages.length is " + expectedValues.length);
else
postMessage("FAIL: receivedMessages.length should be " + expectedValues.length + " but was " + receivedMessages.length);
check(0);
};
function check(index)
{
if (index == expectedValues.length) {
postMessage("DONE");
return;
}
postMessage("INFO: Checking message #" + index + ".");
var responseType = '' + receivedMessages[index];
if (responseType === "[object Blob]")
postMessage("PASS: responseType is \"[object Blob]\"");
else
postMessage("FAIL: responseType should be \"[object Blob]\" but was \"" + responseType + "\"");
var reader = new FileReader();
reader.readAsArrayBuffer(receivedMessages[index]);
reader.onload = function(event)
{
checkArrayBuffer(index, reader.result, expectedValues[index]);
check(index + 1);
};
reader.onerror = function(event)
{
postMessage("FAIL: Failed to read blob: error code = " + reader.error.code);
check(index + 1);
};
}
function checkArrayBuffer(testIndex, actual, expected)
{
var actualArray = new Uint8Array(actual);
var expectedArray = new Uint8Array(expected);
if (actualArray.length === expectedArray.length)
postMessage("PASS: actualArray.length is " + expectedArray.length);
else
postMessage("FAIL: actualArray.length should be " + expectedArray.length + " but was " + actualArray.length);
// Print only the first mismatched byte in order not to flood console.
for (var i = 0; i < expectedArray.length; ++i) {
if (actualArray[i] != expectedArray[i]) {
postMessage("FAIL: Value mismatch: actualArray[" + i + "] = " + actualArray[i] + ", expectedArray[" + i + "] = " + expectedArray[i]);
return;
}
}
postMessage("PASS: Passed: Message #" + testIndex + ".");
}
2011-08-29 Yuta Kitamura <yutak@chromium.org>
WebSocket: Receive binary message as Blob
https://bugs.webkit.org/show_bug.cgi?id=67115
Reviewed by Kent Tamura.
Make WebSocketChannel be able to receive WebSocket binary messages and deliver them via
didReceiveBinaryData() callback of WebSocketChannelClient.
Tests: http/tests/websocket/tests/hybi/fragmented-binary-frames.html
http/tests/websocket/tests/hybi/receive-blob.html
http/tests/websocket/tests/hybi/workers/receive-blob.html
* bindings/js/JSMessageEventCustom.cpp:
(WebCore::JSMessageEvent::data): Convert Blob to JSValue.
* bindings/v8/custom/V8MessageEventCustom.cpp:
(WebCore::V8MessageEvent::dataAccessorGetter): Convert Blob to v8::Value.
* dom/MessageEvent.cpp:
(WebCore::MessageEvent::MessageEvent):
* dom/MessageEvent.h:
Added DataTypeBlob and Blob-related functions.
(WebCore::MessageEvent::create):
(WebCore::MessageEvent::dataAsBlob):
* websockets/ThreadableWebSocketChannelClientWrapper.cpp:
(WebCore::ThreadableWebSocketChannelClientWrapper::didReceiveBinaryData):
(WebCore::ThreadableWebSocketChannelClientWrapper::didReceiveBinaryDataCallback):
* websockets/ThreadableWebSocketChannelClientWrapper.h:
* websockets/WebSocket.cpp:
(WebCore::WebSocket::didReceiveBinaryData):
If binaryType attribute is "blob", construct a Blob using BlobData filled with the given
binary message.
* websockets/WebSocket.h:
* websockets/WebSocketChannel.cpp:
(WebCore::WebSocketChannel::processFrame):
Create continuousFrameData as OwnPtr<> rather than stack-allocated value so we can easily
pass it to didReceiveBinaryData() callback.
Save the content of a fragmented message even if it is a binary opcode.
* websockets/WebSocketChannelClient.h:
(WebCore::WebSocketChannelClient::didReceiveBinaryData):
* websockets/WorkerThreadableWebSocketChannel.cpp:
(WebCore::workerContextDidReceiveBinaryData):
(WebCore::WorkerThreadableWebSocketChannel::Peer::didReceiveBinaryData):
Pass binaryData as PassOwnPtr<Vector<char> > to deliver the value efficiently (without
copying the content) across threads.
* websockets/WorkerThreadableWebSocketChannel.h:
2011-08-29 Ryosuke Niwa <rniwa@webkit.org>
Add a test for lastChangeWasUserEdit in HTMLInputElement and HTMLTextAreaElement
......@@ -31,6 +31,7 @@
#include "config.h"
#include "JSMessageEvent.h"
#include "JSBlob.h"
#include "JSDOMBinding.h"
#include "JSDOMWindow.h"
#include "JSEventTarget.h"
......@@ -60,6 +61,10 @@ JSValue JSMessageEvent::data(ExecState* exec) const
case MessageEvent::DataTypeString:
result = jsString(exec, event->dataAsString());
break;
case MessageEvent::DataTypeBlob:
result = toJS(exec, globalObject(), event->dataAsBlob());
break;
}
// Save the result so we don't have to deserialize the value again.
......
......@@ -35,6 +35,7 @@
#include "SerializedScriptValue.h"
#include "V8Binding.h"
#include "V8Blob.h"
#include "V8DOMWindow.h"
#include "V8MessagePort.h"
#include "V8MessagePortCustom.h"
......@@ -61,6 +62,10 @@ v8::Handle<v8::Value> V8MessageEvent::dataAccessorGetter(v8::Local<v8::String> n
result = v8::String::New(fromWebCoreString(stringValue), stringValue.length());
break;
}
case MessageEvent::DataTypeBlob:
result = toV8(event->dataAsBlob());