Commit 99a27564 authored by nhiroki's avatar nhiroki Committed by Commit bot

WebMessaging: Send transferable ArrayBuffers by copy-and-neuter semantics

Before this CL, MessagePort/ServiceWorker/Client can send an ArrayBuffer with
copy semantics, but it cannot send an ArrayBuffer with move semantics even if
it's specified as a transferable object.

  // Copy semantics.
  port.postMessage(arrayBuffer);

  // Move semantics. Currently, this doesn't work and shows a warning message
  // instead. See https://codereview.chromium.org/1301953002
  port.postMessage(arrayBuffer, [arrayBuffer]);

After this CL, they emulate move semantics by copy-and-neuter semantics that
sends an ArrayBuffer by copy semantics and neuters the original ArrayBuffer.

<Context of this change>

Because of Chromium's multi-process architecture, a target object can be in a
different process from a sender object. This makes it difficult to support
zero-copy transfer (move semantics) because it needs to stride over process
memory boundaries. Using shared memory is a possible solution, but that seems to
need architectual changes and introduce complexity. Therefore, as a stopgap, we
support sending a transferable ArrayBuffer with copy-and-neuter semantics.

See this document for more details:
https://docs.google.com/document/d/17o_cjtc3Gk5cDZhoUc37EyhhvBb4wMlCROJvoC2Aokg/edit#

BUG=334408, 511119

Review-Url: https://codereview.chromium.org/2414333003
Cr-Commit-Position: refs/heads/master@{#427015}
parent 0500571c
CONSOLE WARNING: line 32: MessagePort cannot send an ArrayBuffer as a transferable object yet. See http://crbug.com/334408
CONSOLE WARNING: line 41: MessagePort cannot send an ArrayBuffer as a transferable object yet. See http://crbug.com/334408
Tests various use cases when cloning MessagePorts. Tests various use cases when cloning MessagePorts.
On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE". On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
......
<!DOCTYPE html>
<script src="../../resources/testharness.js"></script>
<script src="../../resources/testharnessreport.js"></script>
<body>
<script id="worker" type="javascript/worker">
var messageHandler = function(port, e) {
var text_decoder = new TextDecoder;
port.postMessage({
content: text_decoder.decode(e.data),
byteLength: e.data.byteLength
});
// Send back the array buffer.
port.postMessage(e.data, [e.data.buffer]);
port.postMessage({
content: text_decoder.decode(e.data),
byteLength: e.data.byteLength
});
};
// For Worker.
self.addEventListener('message', e => {
var port = e.ports[0];
port.onmessage = messageHandler.bind(null, port);
});
// For SharedWorker.
self.addEventListener('connect', e => {
var port = e.ports[0];
port.onmessage = messageHandler.bind(null, port);
});
</script>
<script>
async_test(t => {
var message = 'Hello, world!';
var text_encoder = new TextEncoder;
var text_decoder = new TextDecoder;
var channel = new MessageChannel;
channel.port1.onmessage = function(e) {
assert_equals(e.data.byteLength, message.length);
assert_equals(text_decoder.decode(e.data), message);
t.done();
};
channel.port2.onmessage = function(e) {
assert_equals(e.data.byteLength, message.length);
assert_equals(text_decoder.decode(e.data), message);
channel.port2.postMessage(e.data, [e.data.buffer]);
assert_equals(e.data.byteLength, 0);
assert_equals(text_decoder.decode(e.data), '');
};
var ab = text_encoder.encode(message);
assert_equals(ab.byteLength, message.length);
channel.port1.postMessage(ab, [ab.buffer]);
assert_equals(ab.byteLength, 0);
assert_equals(text_decoder.decode(ab.data), '');
}, 'Send a transferable ArrayBuffer within a same frame');
async_test(t => {
var message = 'Hello, world!';
var text_encoder = new TextEncoder;
var text_decoder = new TextDecoder;
var frame = document.createElement('iframe');
frame.src = 'resources/empty.html';
frame.onload = function() {
frame.contentWindow.onmessage = function(evt) {
var port = evt.data;
port.onmessage = function(e) {
assert_equals(e.data.byteLength, message.length);
assert_equals(text_decoder.decode(e.data), message);
port.postMessage(e.data, [e.data.buffer]);
assert_equals(e.data.byteLength, 0);
assert_equals(text_decoder.decode(e.data), '');
};
};
var channel = new MessageChannel;
channel.port1.onmessage = function(e) {
assert_equals(e.data.byteLength, message.length);
assert_equals(text_decoder.decode(e.data), message);
t.done();
};
frame.contentWindow.postMessage(channel.port2, '*', [channel.port2]);
var ab = text_encoder.encode(message);
assert_equals(ab.byteLength, message.length);
channel.port1.postMessage(ab, [ab.buffer]);
assert_equals(ab.byteLength, 0);
assert_equals(text_decoder.decode(ab), '');
};
document.body.appendChild(frame);
}, 'Send a transferable ArrayBuffer over frames');
promise_test(t => {
var message = 'Hello, world!';
var text_encoder = new TextEncoder;
var text_decoder = new TextDecoder;
var channel = new MessageChannel;
var blob = new Blob([document.querySelector('#worker').textContent]);
var worker = new Worker(URL.createObjectURL(blob));
worker.postMessage(undefined, [channel.port2]);
var ab = text_encoder.encode(message);
assert_equals(ab.byteLength, message.length);
channel.port1.postMessage(ab, [ab.buffer]);
assert_equals(
ab.byteLength, 0,
'Byte length of a neutered buffer should be zero.');
return new Promise(resolve => { channel.port1.onmessage = resolve; })
.then(e => {
// Verify the integrity of the transferred array buffer.
assert_equals(e.data.content, message);
assert_equals(e.data.byteLength, message.length);
return new Promise(resolve => { channel.port1.onmessage = resolve; })
})
.then(e => {
// Verify the integrity of the array buffer sent back from Worker via
// MessagePort.
assert_equals(text_decoder.decode(e.data), message);
assert_equals(e.data.byteLength, message.length);
return new Promise(resolve => { channel.port1.onmessage = resolve; })
})
.then(e => {
// Verify that the array buffer on ServiceWorker is neutered.
assert_equals(e.data.content, '');
assert_equals(e.data.byteLength, 0);
});
}, 'Send a transferable ArrayBuffer to Worker');
promise_test(t => {
var message = 'Hello, world!';
var text_encoder = new TextEncoder;
var text_decoder = new TextDecoder;
var blob = new Blob([document.querySelector('#worker').textContent]);
var worker = new SharedWorker(URL.createObjectURL(blob));
var ab = text_encoder.encode(message);
assert_equals(ab.byteLength, message.length);
worker.port.postMessage(ab, [ab.buffer]);
assert_equals(
ab.byteLength, 0,
'Byte length of a neutered buffer should be zero.');
return new Promise(resolve => { worker.port.onmessage = resolve; })
.then(e => {
// Verify the integrity of the transferred array buffer.
assert_equals(e.data.content, message);
assert_equals(e.data.byteLength, message.length);
return new Promise(resolve => { worker.port.onmessage = resolve; })
})
.then(e => {
// Verify the integrity of the array buffer sent back from Worker via
// MessagePort.
assert_equals(text_decoder.decode(e.data), message);
assert_equals(e.data.byteLength, message.length);
return new Promise(resolve => { worker.port.onmessage = resolve; })
})
.then(e => {
// Verify that the array buffer on ServiceWorker is neutered.
assert_equals(e.data.content, '');
assert_equals(e.data.byteLength, 0);
});
}, 'Send a transferable ArrayBuffer to SharedWorker');
</script>
</body>
...@@ -52,4 +52,90 @@ promise_test(t => { ...@@ -52,4 +52,90 @@ promise_test(t => {
}); });
}, 'postMessage to a ServiceWorker (and back via MessagePort)'); }, 'postMessage to a ServiceWorker (and back via MessagePort)');
promise_test(t => {
var script = 'resources/postmessage-transferables-worker.js';
var scope = 'resources/blank.html';
var sw = navigator.serviceWorker;
var message = 'Hello, world!';
var text_encoder = new TextEncoder;
var text_decoder = new TextDecoder;
return service_worker_unregister_and_register(t, script, scope)
.then(r => {
add_completion_callback(() => r.unregister());
var ab = text_encoder.encode(message);
assert_equals(ab.byteLength, message.length);
r.installing.postMessage(ab, [ab.buffer]);
assert_equals(text_decoder.decode(ab), '');
assert_equals(ab.byteLength, 0);
return new Promise(resolve => { sw.onmessage = resolve; });
})
.then(e => {
// Verify the integrity of the transferred array buffer.
assert_equals(e.data.content, message);
assert_equals(e.data.byteLength, message.length);
return new Promise(resolve => { sw.onmessage = resolve; });
})
.then(e => {
// Verify the integrity of the array buffer sent back from
// ServiceWorker via Client.postMessage.
assert_equals(text_decoder.decode(e.data), message);
assert_equals(e.data.byteLength, message.length);
return new Promise(resolve => { sw.onmessage = resolve; });
})
.then(e => {
// Verify that the array buffer on ServiceWorker is neutered.
assert_equals(e.data.content, '');
assert_equals(e.data.byteLength, 0);
});
}, 'postMessage a transferable ArrayBuffer between ServiceWorker and Client');
promise_test(t => {
var script = 'resources/postmessage-transferables-worker.js';
var scope = 'resources/blank.html';
var message = 'Hello, world!';
var text_encoder = new TextEncoder;
var text_decoder = new TextDecoder;
var port;
return service_worker_unregister_and_register(t, script, scope)
.then(r => {
add_completion_callback(() => r.unregister());
var channel = new MessageChannel;
port = channel.port1;
r.installing.postMessage(undefined, [channel.port2]);
var ab = text_encoder.encode(message);
assert_equals(ab.byteLength, message.length);
port.postMessage(ab, [ab.buffer]);
assert_equals(text_decoder.decode(ab), '');
assert_equals(ab.byteLength, 0);
return new Promise(resolve => { port.onmessage = resolve; });
})
.then(e => {
// Verify the integrity of the transferred array buffer.
assert_equals(e.data.content, message);
assert_equals(e.data.byteLength, message.length);
return new Promise(resolve => { port.onmessage = resolve; });
})
.then(e => {
// Verify the integrity of the array buffer sent back from
// ServiceWorker via Client.postMessage.
assert_equals(text_decoder.decode(e.data), message);
assert_equals(e.data.byteLength, message.length);
return new Promise(resolve => { port.onmessage = resolve; });
})
.then(e => {
// Verify that the array buffer on ServiceWorker is neutered.
assert_equals(e.data.content, '');
assert_equals(e.data.byteLength, 0);
});
}, 'postMessage a transferable ArrayBuffer between ServiceWorker and Client' +
' over MessagePort');
</script> </script>
var messageHandler = function(port, e) {
var text_decoder = new TextDecoder;
port.postMessage({
content: text_decoder.decode(e.data),
byteLength: e.data.byteLength
});
// Send back the array buffer via Client.postMessage.
port.postMessage(e.data, [e.data.buffer]);
port.postMessage({
content: text_decoder.decode(e.data),
byteLength: e.data.byteLength
});
};
self.addEventListener('message', e => {
if (e.ports[0]) {
// Wait for messages sent via MessagePort.
e.ports[0].onmessage = messageHandler.bind(null, e.ports[0]);
return;
}
messageHandler(e.source, e);
});
...@@ -149,7 +149,7 @@ void SerializedScriptValue::toWireBytes(Vector<char>& result) const { ...@@ -149,7 +149,7 @@ void SerializedScriptValue::toWireBytes(Vector<char>& result) const {
} }
} }
static void acculumateArrayBuffersForAllWorlds( static void accumulateArrayBuffersForAllWorlds(
v8::Isolate* isolate, v8::Isolate* isolate,
DOMArrayBuffer* object, DOMArrayBuffer* object,
Vector<v8::Local<v8::ArrayBuffer>, 4>& buffers) { Vector<v8::Local<v8::ArrayBuffer>, 4>& buffers) {
...@@ -230,64 +230,8 @@ void SerializedScriptValue::transferArrayBuffers( ...@@ -230,64 +230,8 @@ void SerializedScriptValue::transferArrayBuffers(
v8::Isolate* isolate, v8::Isolate* isolate,
const ArrayBufferArray& arrayBuffers, const ArrayBufferArray& arrayBuffers,
ExceptionState& exceptionState) { ExceptionState& exceptionState) {
if (!arrayBuffers.size()) m_arrayBufferContentsArray =
return; transferArrayBufferContents(isolate, arrayBuffers, exceptionState);
for (size_t i = 0; i < arrayBuffers.size(); ++i) {
if (arrayBuffers[i]->isNeutered()) {
exceptionState.throwDOMException(
DataCloneError, "ArrayBuffer at index " + String::number(i) +
" is already neutered.");
return;
}
}
std::unique_ptr<ArrayBufferContentsArray> contents =
wrapUnique(new ArrayBufferContentsArray(arrayBuffers.size()));
HeapHashSet<Member<DOMArrayBufferBase>> visited;
for (size_t i = 0; i < arrayBuffers.size(); ++i) {
if (visited.contains(arrayBuffers[i]))
continue;
visited.add(arrayBuffers[i]);
if (arrayBuffers[i]->isShared()) {
bool result = arrayBuffers[i]->shareContentsWith(contents->at(i));
if (!result) {
exceptionState.throwDOMException(
DataCloneError, "SharedArrayBuffer at index " + String::number(i) +
" could not be transferred.");
return;
}
} else {
Vector<v8::Local<v8::ArrayBuffer>, 4> bufferHandles;
v8::HandleScope handleScope(isolate);
acculumateArrayBuffersForAllWorlds(
isolate, static_cast<DOMArrayBuffer*>(arrayBuffers[i].get()),
bufferHandles);
bool isNeuterable = true;
for (size_t j = 0; j < bufferHandles.size(); ++j)
isNeuterable &= bufferHandles[j]->IsNeuterable();
DOMArrayBufferBase* toTransfer = arrayBuffers[i];
if (!isNeuterable)
toTransfer =
DOMArrayBuffer::create(arrayBuffers[i]->buffer()->data(),
arrayBuffers[i]->buffer()->byteLength());
bool result = toTransfer->transfer(contents->at(i));
if (!result) {
exceptionState.throwDOMException(
DataCloneError, "ArrayBuffer at index " + String::number(i) +
" could not be transferred.");
return;
}
if (isNeuterable)
for (size_t j = 0; j < bufferHandles.size(); ++j)
bufferHandles[j]->Neuter();
}
}
m_arrayBufferContentsArray = std::move(contents);
} }
v8::Local<v8::Value> SerializedScriptValue::deserialize( v8::Local<v8::Value> SerializedScriptValue::deserialize(
...@@ -402,6 +346,74 @@ bool SerializedScriptValue::extractTransferables( ...@@ -402,6 +346,74 @@ bool SerializedScriptValue::extractTransferables(
return true; return true;
} }
std::unique_ptr<ArrayBufferContentsArray>
SerializedScriptValue::transferArrayBufferContents(
v8::Isolate* isolate,
const ArrayBufferArray& arrayBuffers,
ExceptionState& exceptionState) {
if (!arrayBuffers.size())
return nullptr;
for (auto it = arrayBuffers.begin(); it != arrayBuffers.end(); ++it) {
DOMArrayBufferBase* arrayBuffer = *it;
if (arrayBuffer->isNeutered()) {
size_t index = std::distance(arrayBuffers.begin(), it);
exceptionState.throwDOMException(
DataCloneError, "ArrayBuffer at index " + String::number(index) +
" is already neutered.");
return nullptr;
}
}
std::unique_ptr<ArrayBufferContentsArray> contents =
wrapUnique(new ArrayBufferContentsArray(arrayBuffers.size()));
HeapHashSet<Member<DOMArrayBufferBase>> visited;
for (auto it = arrayBuffers.begin(); it != arrayBuffers.end(); ++it) {
DOMArrayBufferBase* arrayBuffer = *it;
if (visited.contains(arrayBuffer))
continue;
visited.add(arrayBuffer);
size_t index = std::distance(arrayBuffers.begin(), it);
if (arrayBuffer->isShared()) {
if (!arrayBuffer->shareContentsWith(contents->at(index))) {
exceptionState.throwDOMException(DataCloneError,
"SharedArrayBuffer at index " +
String::number(index) +
" could not be transferred.");
return nullptr;
}
} else {
Vector<v8::Local<v8::ArrayBuffer>, 4> bufferHandles;
v8::HandleScope handleScope(isolate);
accumulateArrayBuffersForAllWorlds(
isolate, static_cast<DOMArrayBuffer*>(it->get()), bufferHandles);
bool isNeuterable = true;
for (const auto& bufferHandle : bufferHandles)
isNeuterable &= bufferHandle->IsNeuterable();
DOMArrayBufferBase* toTransfer = arrayBuffer;
if (!isNeuterable) {
toTransfer = DOMArrayBuffer::create(
arrayBuffer->buffer()->data(), arrayBuffer->buffer()->byteLength());
}
if (!toTransfer->transfer(contents->at(index))) {
exceptionState.throwDOMException(
DataCloneError, "ArrayBuffer at index " + String::number(index) +
" could not be transferred.");
return nullptr;
}
if (isNeuterable) {
for (const auto& bufferHandle : bufferHandles)
bufferHandle->Neuter();
}
}
}
return contents;
}
void SerializedScriptValue::registerMemoryAllocatedWithCurrentScriptContext() { void SerializedScriptValue::registerMemoryAllocatedWithCurrentScriptContext() {
if (m_externallyAllocatedMemory) if (m_externallyAllocatedMemory)
return; return;
...@@ -410,8 +422,4 @@ void SerializedScriptValue::registerMemoryAllocatedWithCurrentScriptContext() { ...@@ -410,8 +422,4 @@ void SerializedScriptValue::registerMemoryAllocatedWithCurrentScriptContext() {
m_externallyAllocatedMemory); m_externallyAllocatedMemory);
} }
bool SerializedScriptValue::containsTransferableArrayBuffer() const {
return m_arrayBufferContentsArray && !m_arrayBufferContentsArray->isEmpty();
}
} // namespace blink } // namespace blink
...@@ -114,6 +114,14 @@ class CORE_EXPORT SerializedScriptValue ...@@ -114,6 +114,14 @@ class CORE_EXPORT SerializedScriptValue
Transferables&, Transferables&,
ExceptionState&); ExceptionState&);
// Helper function which pulls ArrayBufferContents out of an ArrayBufferArray
// and neuters the ArrayBufferArray. Returns nullptr if there is an
// exception.
static std::unique_ptr<ArrayBufferContentsArray> transferArrayBufferContents(
v8::Isolate*,
const ArrayBufferArray&,
ExceptionState&);
// Informs the V8 about external memory allocated and owned by this object. // Informs the V8 about external memory allocated and owned by this object.
// Large values should contribute to GC counters to eventually trigger a GC, // Large values should contribute to GC counters to eventually trigger a GC,
// otherwise flood of postMessage() can cause OOM. // otherwise flood of postMessage() can cause OOM.
...@@ -121,9 +129,6 @@ class CORE_EXPORT SerializedScriptValue ...@@ -121,9 +129,6 @@ class CORE_EXPORT SerializedScriptValue
// The memory registration is revoked automatically in destructor. // The memory registration is revoked automatically in destructor.
void registerMemoryAllocatedWithCurrentScriptContext(); void registerMemoryAllocatedWithCurrentScriptContext();
// Returns true if the value contains a transferable ArrayBuffer.
bool containsTransferableArrayBuffer() const;
String& data() { return m_data; } String& data() { return m_data; }
BlobDataHandleMap& blobDataHandles() { return m_blobDataHandles; } BlobDataHandleMap& blobDataHandles() { return m_blobDataHandles; }
ArrayBufferContentsArray* getArrayBufferContentsArray() { ArrayBufferContentsArray* getArrayBufferContentsArray() {
......
...@@ -172,6 +172,7 @@ def method_context(interface, method, is_visible=True): ...@@ -172,6 +172,7 @@ def method_context(interface, method, is_visible=True):
if is_post_message: if is_post_message:
includes.add('bindings/core/v8/SerializedScriptValueFactory.h') includes.add('bindings/core/v8/SerializedScriptValueFactory.h')
includes.add('bindings/core/v8/Transferables.h') includes.add('bindings/core/v8/Transferables.h')
includes.add('core/dom/DOMArrayBufferBase.h')
if 'LenientThis' in extended_attributes: if 'LenientThis' in extended_attributes:
raise Exception('[LenientThis] is not supported for operations.') raise Exception('[LenientThis] is not supported for operations.')
......
...@@ -435,6 +435,7 @@ static void postMessageImpl(const char* interfaceName, {{cpp_class}}* instance, ...@@ -435,6 +435,7 @@ static void postMessageImpl(const char* interfaceName, {{cpp_class}}* instance,
exceptionState.throwTypeError(ExceptionMessages::notEnoughArguments({{method.number_of_required_arguments}}, info.Length())); exceptionState.throwTypeError(ExceptionMessages::notEnoughArguments({{method.number_of_required_arguments}}, info.Length()));
return; return;
} }
Transferables transferables; Transferables transferables;
if (info.Length() > 1) { if (info.Length() > 1) {
const int transferablesArgIndex = 1; const int transferablesArgIndex = 1;
...@@ -442,9 +443,34 @@ static void postMessageImpl(const char* interfaceName, {{cpp_class}}* instance, ...@@ -442,9 +443,34 @@ static void postMessageImpl(const char* interfaceName, {{cpp_class}}* instance,
return; return;
} }
} }
RefPtr<SerializedScriptValue> message = SerializedScriptValue::serialize(info.GetIsolate(), info[0], &transferables, nullptr, exceptionState);
if (exceptionState.hadException()) RefPtr<SerializedScriptValue> message;
return; if (instance->canTransferArrayBuffer()) {
// This instance supports sending array buffers by move semantics.
message = SerializedScriptValue::serialize(info.GetIsolate(), info[0], &transferables, nullptr, exceptionState);
if (exceptionState.hadException())
return;
} else {
// This instance doesn't support sending array buffers by move
// semantics. Emulate it by copy-and-neuter semantics that sends array
// buffers by copy semantics and then neuters the original array
// buffers.
// Clear references to array buffers from transferables so that the
// serializer can consider the array buffers as non-transferable and
// copy them into the message.
ArrayBufferArray transferableArrayBuffers = transferables.arrayBuffers;
transferables.arrayBuffers.clear();
message = SerializedScriptValue::serialize(info.GetIsolate(), info[0], &transferables, nullptr, exceptionState);
if (exceptionState.hadException())
return;
// Neuter the original array buffers on the sender context.
SerializedScriptValue::transferArrayBufferContents(info.GetIsolate(), transferableArrayBuffers, exceptionState);
if (exceptionState.hadException())
return;
}
// FIXME: Only pass context/exceptionState if instance really requires it. // FIXME: Only pass context/exceptionState if instance really requires it.
ExecutionContext* context = currentExecutionContext(info.GetIsolate()); ExecutionContext* context = currentExecutionContext(info.GetIsolate());
instance->postMessage(context, message.release(), transferables.messagePorts, exceptionState); instance->postMessage(context, message.release(), transferables.messagePorts, exceptionState);
......
...@@ -67,6 +67,7 @@ ...@@ -67,6 +67,7 @@
#include "bindings/core/v8/VoidCallbackFunction.h" #include "bindings/core/v8/VoidCallbackFunction.h"
#include "core/HTMLNames.h" #include "core/HTMLNames.h"
#include "core/dom/ClassCollection.h" #include "core/dom/ClassCollection.h"
#include "core/dom/DOMArrayBufferBase.h"
#include "core/dom/Document.h" #include "core/dom/Document.h"
#include "core/dom/FlexibleArrayBufferView.h" #include "core/dom/FlexibleArrayBufferView.h"
#include "core/dom/TagCollection.h" #include "core/dom/TagCollection.h"
...@@ -11535,6 +11536,7 @@ static void postMessageImpl(const char* interfaceName, TestObject* instance, con ...@@ -11535,6 +11536,7 @@ static void postMessageImpl(const char* interfaceName, TestObject* instance, con
exceptionState.throwTypeError(ExceptionMessages::notEnoughArguments(1, info.Length())); exceptionState.throwTypeError(ExceptionMessages::notEnoughArguments(1, info.Length()));
return; return;
} }
Transferables transferables; Transferables transferables;
if (info.Length() > 1) { if (info.Length() > 1) {
const int transferablesArgIndex = 1; const int transferablesArgIndex = 1;
...@@ -11542,9 +11544,34 @@ static void postMessageImpl(const char* interfaceName, TestObject* instance, con ...@@ -11542,9 +11544,34 @@ static void postMessageImpl(const char* interfaceName, TestObject* instance, con
return; return;
} }
} }
RefPtr<SerializedScriptValue> message = SerializedScriptValue::serialize(info.GetIsolate(), info[0], &transferables, nullptr, exceptionState);
if (exceptionState.hadException()) RefPtr<SerializedScriptValue> message;
return; if (instance->canTransferArrayBuffer()) {
// This instance supports sending array buffers by move semantics.
message = SerializedScriptValue::serialize(info.GetIsolate(), info[0], &transferables, nullptr, exceptionState);
if (exceptionState.hadException())
return;
} else {
// This instance doesn't support sending array buffers by move
// semantics. Emulate it by copy-and-neuter semantics that sends array
// buffers by copy semantics and then neuters the original array
// buffers.
// Clear references to array buffers from transferables so that the
// serializer can consider the array buffers as non-transferable and
// copy them into the message.
ArrayBufferArray transferableArrayBuffers = transferables.arrayBuffers;
transferables.arrayBuffers.clear();
message = SerializedScriptValue::serialize(info.GetIsolate(), info[0], &transferables, nullptr, exceptionState);
if (exceptionState.hadException())
return;
// Neuter the original array buffers on the sender context.
SerializedScriptValue::transferArrayBufferContents(info.GetIsolate(), transferableArrayBuffers, exceptionState);
if (exceptionState.hadException())
return;
}
// FIXME: Only pass context/exceptionState if instance really requires it. // FIXME: Only pass context/exceptionState if instance really requires it.
ExecutionContext* context = currentExecutionContext(info.GetIsolate()); ExecutionContext* context = currentExecutionContext(info.GetIsolate());
instance->postMessage(context, message.release(), transferables.messagePorts, exceptionState); instance->postMessage(context, message.release(), transferables.messagePorts, exceptionState);
......
...@@ -35,7 +35,6 @@ ...@@ -35,7 +35,6 @@
#include "core/dom/ExecutionContextTask.h" #include "core/dom/ExecutionContextTask.h"
#include "core/events/MessageEvent.h" #include "core/events/MessageEvent.h"
#include "core/frame/LocalDOMWindow.h" #include "core/frame/LocalDOMWindow.h"
#include "core/inspector/ConsoleMessage.h"
#include "core/workers/WorkerGlobalScope.h" #include "core/workers/WorkerGlobalScope.h"
#include "public/platform/WebString.h" #include "public/platform/WebString.h"
#include "wtf/Functional.h" #include "wtf/Functional.h"
...@@ -86,12 +85,6 @@ void MessagePort::postMessage(ExecutionContext* context, ...@@ -86,12 +85,6 @@ void MessagePort::postMessage(ExecutionContext* context,
if (exceptionState.hadException()) if (exceptionState.hadException())
return; return;
if (message->containsTransferableArrayBuffer())
getExecutionContext()->addConsoleMessage(ConsoleMessage::create(
JSMessageSource, WarningMessageLevel,
"MessagePort cannot send an ArrayBuffer as a transferable object yet. "
"See http://crbug.com/334408"));
WebString messageString = message->toWireString(); WebString messageString = message->toWireString();
std::unique_ptr<WebMessagePortChannelArray> webChannels = std::unique_ptr<WebMessagePortChannelArray> webChannels =
toWebMessagePortChannelArray(std::move(channels)); toWebMessagePortChannelArray(std::move(channels));
......
...@@ -67,6 +67,7 @@ class CORE_EXPORT MessagePort : public EventTargetWithInlineData, ...@@ -67,6 +67,7 @@ class CORE_EXPORT MessagePort : public EventTargetWithInlineData,
PassRefPtr<SerializedScriptValue> message, PassRefPtr<SerializedScriptValue> message,
const MessagePortArray&, const MessagePortArray&,
ExceptionState&); ExceptionState&);
static bool canTransferArrayBuffer() { return false; }
void start(); void start();
void close(); void close();
......
...@@ -64,6 +64,8 @@ class CORE_EXPORT DedicatedWorkerGlobalScope final : public WorkerGlobalScope { ...@@ -64,6 +64,8 @@ class CORE_EXPORT DedicatedWorkerGlobalScope final : public WorkerGlobalScope {
const MessagePortArray&, const MessagePortArray&,
ExceptionState&); ExceptionState&);
static bool canTransferArrayBuffer() { return true; }
DEFINE_ATTRIBUTE_EVENT_LISTENER(message); DEFINE_ATTRIBUTE_EVENT_LISTENER(message);
DedicatedWorkerThread* thread() const; DedicatedWorkerThread* thread() const;
......
...@@ -34,6 +34,7 @@ class CORE_EXPORT InProcessWorkerBase : public AbstractWorker, ...@@ -34,6 +34,7 @@ class CORE_EXPORT InProcessWorkerBase : public AbstractWorker,
PassRefPtr<SerializedScriptValue> message, PassRefPtr<SerializedScriptValue> message,
const MessagePortArray&, const MessagePortArray&,
ExceptionState&); ExceptionState&);
static bool canTransferArrayBuffer() { return true; }
void terminate(); void terminate();
// ActiveDOMObject // ActiveDOMObject
......
...@@ -37,6 +37,7 @@ class MODULES_EXPORT CompositorWorkerGlobalScope final ...@@ -37,6 +37,7 @@ class MODULES_EXPORT CompositorWorkerGlobalScope final
PassRefPtr<SerializedScriptValue>, PassRefPtr<SerializedScriptValue>,
const MessagePortArray&, const MessagePortArray&,
ExceptionState&); ExceptionState&);
static bool canTransferArrayBuffer() { return true; }
DEFINE_ATTRIBUTE_EVENT_LISTENER(message); DEFINE_ATTRIBUTE_EVENT_LISTENER(message);
int requestAnimationFrame(FrameRequestCallback*); int requestAnimationFrame(FrameRequestCallback*);
......
...@@ -34,7 +34,6 @@ ...@@ -34,7 +34,6 @@
#include "core/dom/ExceptionCode.h" #include "core/dom/ExceptionCode.h"
#include "core/dom/MessagePort.h" #include "core/dom/MessagePort.h"
#include "core/events/Event.h" #include "core/events/Event.h"
#include "core/inspector/ConsoleMessage.h"
#include "modules/EventTargetModules.h" #include "modules/EventTargetModules.h"
#include "modules/serviceworkers/ServiceWorkerContainerClient.h" #include "modules/serviceworkers/ServiceWorkerContainerClient.h"
#include "public/platform/WebMessagePortChannel.h" #include "public/platform/WebMessagePortChannel.h"
...@@ -73,12 +72,6 @@ void ServiceWorker::postMessage(ExecutionContext* context, ...@@ -73,12 +72,6 @@ void ServiceWorker::postMessage(ExecutionContext* context,
return; return;
} }
if (message->containsTransferableArrayBuffer())
context->addConsoleMessage(ConsoleMessage::create(
JSMessageSource, WarningMessageLevel,
"ServiceWorker cannot send an ArrayBuffer as a transferable object "
"yet. See http://crbug.com/511119"));
WebString messageString = message->toWireString(); WebString messageString = message->toWireString();
std::unique_ptr<WebMessagePortChannelArray> webChannels = std::unique_ptr<WebMessagePortChannelArray> webChannels =
MessagePort::toWebMessagePortChannelArray(std::move(channels)); MessagePort::toWebMessagePortChannelArray(std::move(channels));
......
...@@ -65,6 +65,7 @@ class MODULES_EXPORT ServiceWorker final : public AbstractWorker, ...@@ -65,6 +65,7 @@ class MODULES_EXPORT ServiceWorker final : public AbstractWorker,
PassRefPtr<SerializedScriptValue> message, PassRefPtr<SerializedScriptValue> message,
const MessagePortArray&, const MessagePortArray&,
ExceptionState&); ExceptionState&);
static bool canTransferArrayBuffer() { return false; }
String scriptURL() const; String scriptURL() const;
String state() const; String state() const;
......
...@@ -8,7 +8,6 @@ ...@@ -8,7 +8,6 @@
#include "bindings/core/v8/CallbackPromiseAdapter.h" #include "bindings/core/v8/CallbackPromiseAdapter.h"
#include "bindings/core/v8/ExceptionState.h" #include "bindings/core/v8/ExceptionState.h"
#include "bindings/core/v8/SerializedScriptValue.h" #include "bindings/core/v8/SerializedScriptValue.h"
#include "core/inspector/ConsoleMessage.h"
#include "modules/serviceworkers/ServiceWorkerGlobalScopeClient.h" #include "modules/serviceworkers/ServiceWorkerGlobalScopeClient.h"
#include "public/platform/WebString.h" #include "public/platform/WebString.h"
#include "wtf/RefPtr.h" #include "wtf/RefPtr.h"
...@@ -74,12 +73,6 @@ void ServiceWorkerClient::postMessage(ExecutionContext* context, ...@@ -74,12 +73,6 @@ void ServiceWorkerClient::postMessage(ExecutionContext* context,
if (exceptionState.hadException()) if (exceptionState.hadException())
return; return;
if (message->containsTransferableArrayBuffer())
context->addConsoleMessage(ConsoleMessage::create(
JSMessageSource, WarningMessageLevel,
"ServiceWorkerClient cannot send an ArrayBuffer as a transferable "
"object yet. See http://crbug.com/511119"));
WebString messageString = message->toWireString(); WebString messageString = message->toWireString();
std::unique_ptr<WebMessagePortChannelArray> webChannels = std::unique_ptr<WebMessagePortChannelArray> webChannels =
MessagePort::toWebMessagePortChannelArray(std::move(channels)); MessagePort::toWebMessagePortChannelArray(std::move(channels));
......
...@@ -44,6 +44,8 @@ class MODULES_EXPORT ServiceWorkerClient ...@@ -44,6 +44,8 @@ class MODULES_EXPORT ServiceWorkerClient
const MessagePortArray&, const MessagePortArray&,
ExceptionState&); ExceptionState&);
static bool canTransferArrayBuffer() { return false; }
DEFINE_INLINE_VIRTUAL_TRACE() {} DEFINE_INLINE_VIRTUAL_TRACE() {}
protected: protected:
......
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