378 lines
12 KiB
Dart
378 lines
12 KiB
Dart
// Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
part of dart._isolate_helper;
|
|
|
|
/// Serialize [message].
|
|
_serializeMessage(message) {
|
|
return new _Serializer().serialize(message);
|
|
}
|
|
|
|
/// Deserialize [message].
|
|
_deserializeMessage(message) {
|
|
return new _Deserializer().deserialize(message);
|
|
}
|
|
|
|
/// Clones the message.
|
|
///
|
|
/// Contrary to a `_deserializeMessage(_serializeMessage(x))` the `_clone`
|
|
/// function will not try to adjust SendPort values and pass them through.
|
|
_clone(message) {
|
|
_Serializer serializer = new _Serializer(serializeSendPorts: false);
|
|
_Deserializer deserializer = new _Deserializer();
|
|
return deserializer.deserialize(serializer.serialize(message));
|
|
}
|
|
|
|
class _Serializer {
|
|
final bool _serializeSendPorts;
|
|
Map<dynamic, int> serializedObjectIds = new Map<dynamic, int>.identity();
|
|
|
|
_Serializer({serializeSendPorts: true})
|
|
: _serializeSendPorts = serializeSendPorts;
|
|
|
|
/// Returns a message that can be transmitted through web-worker channels.
|
|
serialize(x) {
|
|
if (isPrimitive(x)) return serializePrimitive(x);
|
|
|
|
int serializationId = serializedObjectIds[x];
|
|
if (serializationId != null) return makeRef(serializationId);
|
|
|
|
serializationId = serializedObjectIds.length;
|
|
serializedObjectIds[x] = serializationId;
|
|
|
|
if (x is NativeByteBuffer) return serializeByteBuffer(x);
|
|
if (x is NativeTypedData) return serializeTypedData(x);
|
|
if (x is JSIndexable) return serializeJSIndexable(x);
|
|
if (x is InternalMap) return serializeMap(x);
|
|
|
|
if (x is JSObject) return serializeJSObject(x);
|
|
|
|
// We should not have any interceptors any more.
|
|
if (x is Interceptor) unsupported(x);
|
|
|
|
if (x is RawReceivePort) {
|
|
unsupported(x, "RawReceivePorts can't be transmitted:");
|
|
}
|
|
|
|
// SendPorts need their workerIds adjusted (either during serialization or
|
|
// deserialization).
|
|
if (x is _NativeJsSendPort) return serializeJsSendPort(x);
|
|
if (x is _WorkerSendPort) return serializeWorkerSendPort(x);
|
|
|
|
if (x is Function) return serializeClosure(x);
|
|
|
|
return serializeDartObject(x);
|
|
}
|
|
|
|
void unsupported(x, [String message]) {
|
|
if (message == null) message = "Can't transmit:";
|
|
throw new UnsupportedError("$message $x");
|
|
}
|
|
|
|
makeRef(int serializationId) => ["ref", serializationId];
|
|
|
|
bool isPrimitive(x) => x == null || x is String || x is num || x is bool;
|
|
serializePrimitive(primitive) => primitive;
|
|
|
|
serializeByteBuffer(NativeByteBuffer buffer) {
|
|
return ["buffer", buffer];
|
|
}
|
|
|
|
serializeTypedData(NativeTypedData data) {
|
|
return ["typed", data];
|
|
}
|
|
|
|
serializeJSIndexable(JSIndexable indexable) {
|
|
// Strings are JSIndexable but should have been treated earlier.
|
|
assert(indexable is! String);
|
|
List serialized = serializeArray(indexable);
|
|
if (indexable is JSFixedArray) return ["fixed", serialized];
|
|
if (indexable is JSExtendableArray) return ["extendable", serialized];
|
|
// MutableArray check must be last, since JSFixedArray and JSExtendableArray
|
|
// extend JSMutableArray.
|
|
if (indexable is JSMutableArray) return ["mutable", serialized];
|
|
// The only JSArrays left are the const Lists (as in `const [1, 2]`).
|
|
if (indexable is JSArray) return ["const", serialized];
|
|
unsupported(indexable, "Can't serialize indexable: ");
|
|
return null;
|
|
}
|
|
|
|
serializeArray(JSArray x) {
|
|
List serialized = [];
|
|
serialized.length = x.length;
|
|
for (int i = 0; i < x.length; i++) {
|
|
serialized[i] = serialize(x[i]);
|
|
}
|
|
return serialized;
|
|
}
|
|
|
|
serializeArrayInPlace(JSArray x) {
|
|
for (int i = 0; i < x.length; i++) {
|
|
x[i] = serialize(x[i]);
|
|
}
|
|
return x;
|
|
}
|
|
|
|
serializeMap(InternalMap x) {
|
|
Function serializeTearOff = serialize;
|
|
return [
|
|
'map',
|
|
x.keys.map(serializeTearOff).toList(),
|
|
x.values.map(serializeTearOff).toList()
|
|
];
|
|
}
|
|
|
|
serializeJSObject(JSObject x) {
|
|
// Don't serialize objects if their `constructor` property isn't `Object`
|
|
// or undefined/null.
|
|
// A different constructor is taken as a sign that the object has complex
|
|
// internal state, or that it is a function, and won't be serialized.
|
|
if (JS('bool', '!!(#.constructor)', x) &&
|
|
JS('bool', 'x.constructor !== Object')) {
|
|
unsupported(x, "Only plain JS Objects are supported:");
|
|
}
|
|
List keys = JS('JSArray', 'Object.keys(#)', x);
|
|
List values = [];
|
|
values.length = keys.length;
|
|
for (int i = 0; i < keys.length; i++) {
|
|
values[i] = serialize(JS('', '#[#]', x, keys[i]));
|
|
}
|
|
return ['js-object', keys, values];
|
|
}
|
|
|
|
serializeWorkerSendPort(_WorkerSendPort x) {
|
|
if (_serializeSendPorts) {
|
|
return ['sendport', x._workerId, x._isolateId, x._receivePortId];
|
|
}
|
|
return ['raw sendport', x];
|
|
}
|
|
|
|
serializeJsSendPort(_NativeJsSendPort x) {
|
|
if (_serializeSendPorts) {
|
|
int workerId = _globalState.currentManagerId;
|
|
return ['sendport', workerId, x._isolateId, x._receivePort._id];
|
|
}
|
|
return ['raw sendport', x];
|
|
}
|
|
|
|
serializeCapability(CapabilityImpl x) => ['capability', x._id];
|
|
|
|
serializeClosure(Function x) {
|
|
final name = IsolateNatives._getJSFunctionName(x);
|
|
if (name == null) {
|
|
unsupported(x, "Closures can't be transmitted:");
|
|
}
|
|
return ['function', name];
|
|
}
|
|
|
|
serializeDartObject(x) {
|
|
var classExtractor = JS_EMBEDDED_GLOBAL('', CLASS_ID_EXTRACTOR);
|
|
var fieldsExtractor = JS_EMBEDDED_GLOBAL('', CLASS_FIELDS_EXTRACTOR);
|
|
String classId = JS('String', '#(#)', classExtractor, x);
|
|
List fields = JS('JSArray', '#(#)', fieldsExtractor, x);
|
|
return ['dart', classId, serializeArrayInPlace(fields)];
|
|
}
|
|
}
|
|
|
|
class _Deserializer {
|
|
/// When `true`, encodes sendports specially so that they can be adjusted on
|
|
/// the receiving end.
|
|
///
|
|
/// When `false`, sendports are cloned like any other object.
|
|
final bool _adjustSendPorts;
|
|
|
|
List<dynamic> deserializedObjects = new List<dynamic>();
|
|
|
|
_Deserializer({adjustSendPorts: true}) : _adjustSendPorts = adjustSendPorts;
|
|
|
|
/// Returns a message that can be transmitted through web-worker channels.
|
|
deserialize(x) {
|
|
if (isPrimitive(x)) return deserializePrimitive(x);
|
|
|
|
if (x is! JSArray) throw new ArgumentError("Bad serialized message: $x");
|
|
|
|
switch (x.first) {
|
|
case "ref":
|
|
return deserializeRef(x);
|
|
case "buffer":
|
|
return deserializeByteBuffer(x);
|
|
case "typed":
|
|
return deserializeTypedData(x);
|
|
case "fixed":
|
|
return deserializeFixed(x);
|
|
case "extendable":
|
|
return deserializeExtendable(x);
|
|
case "mutable":
|
|
return deserializeMutable(x);
|
|
case "const":
|
|
return deserializeConst(x);
|
|
case "map":
|
|
return deserializeMap(x);
|
|
case "sendport":
|
|
return deserializeSendPort(x);
|
|
case "raw sendport":
|
|
return deserializeRawSendPort(x);
|
|
case "js-object":
|
|
return deserializeJSObject(x);
|
|
case "function":
|
|
return deserializeClosure(x);
|
|
case "dart":
|
|
return deserializeDartObject(x);
|
|
default:
|
|
throw "couldn't deserialize: $x";
|
|
}
|
|
}
|
|
|
|
bool isPrimitive(x) => x == null || x is String || x is num || x is bool;
|
|
deserializePrimitive(x) => x;
|
|
|
|
// ['ref', id].
|
|
deserializeRef(x) {
|
|
assert(x[0] == 'ref');
|
|
int serializationId = x[1];
|
|
return deserializedObjects[serializationId];
|
|
}
|
|
|
|
// ['buffer', <byte buffer>].
|
|
NativeByteBuffer deserializeByteBuffer(x) {
|
|
assert(x[0] == 'buffer');
|
|
NativeByteBuffer result = x[1];
|
|
deserializedObjects.add(result);
|
|
return result;
|
|
}
|
|
|
|
// ['typed', <typed array>].
|
|
NativeTypedData deserializeTypedData(x) {
|
|
assert(x[0] == 'typed');
|
|
NativeTypedData result = x[1];
|
|
deserializedObjects.add(result);
|
|
return result;
|
|
}
|
|
|
|
// Updates the given array in place with its deserialized content.
|
|
List deserializeArrayInPlace(JSArray x) {
|
|
for (int i = 0; i < x.length; i++) {
|
|
x[i] = deserialize(x[i]);
|
|
}
|
|
return x;
|
|
}
|
|
|
|
// ['fixed', <array>].
|
|
List deserializeFixed(x) {
|
|
assert(x[0] == 'fixed');
|
|
List result = x[1];
|
|
deserializedObjects.add(result);
|
|
return new JSArray.fixed(deserializeArrayInPlace(result));
|
|
}
|
|
|
|
// ['extendable', <array>].
|
|
List deserializeExtendable(x) {
|
|
assert(x[0] == 'extendable');
|
|
List result = x[1];
|
|
deserializedObjects.add(result);
|
|
return new JSArray.of(deserializeArrayInPlace(result));
|
|
}
|
|
|
|
// ['mutable', <array>].
|
|
List deserializeMutable(x) {
|
|
assert(x[0] == 'mutable');
|
|
List result = x[1];
|
|
deserializedObjects.add(result);
|
|
return deserializeArrayInPlace(result);
|
|
}
|
|
|
|
// ['const', <array>].
|
|
List deserializeConst(x) {
|
|
assert(x[0] == 'const');
|
|
List result = x[1];
|
|
deserializedObjects.add(result);
|
|
// TODO(floitsch): need to mark list as non-changeable.
|
|
return new JSArray.unmodifiable(deserializeArrayInPlace(result));
|
|
}
|
|
|
|
// ['map', <key-list>, <value-list>].
|
|
Map deserializeMap(InternalMap x) {
|
|
assert(x[0] == 'map');
|
|
List keys = x[1];
|
|
List values = x[2];
|
|
Map result = {};
|
|
deserializedObjects.add(result);
|
|
// We need to keep the order of how objects were serialized.
|
|
// First deserialize all keys, and then only deserialize the values.
|
|
keys = keys.map(deserialize).toList();
|
|
|
|
for (int i = 0; i < keys.length; i++) {
|
|
result[keys[i]] = deserialize(values[i]);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// ['sendport', <managerId>, <isolateId>, <receivePortId>].
|
|
SendPort deserializeSendPort(x) {
|
|
assert(x[0] == 'sendport');
|
|
int managerId = x[1];
|
|
int isolateId = x[2];
|
|
int receivePortId = x[3];
|
|
SendPort result;
|
|
// If two isolates are in the same manager, we use NativeJsSendPorts to
|
|
// deliver messages directly without using postMessage.
|
|
if (managerId == _globalState.currentManagerId) {
|
|
var isolate = _globalState.isolates[isolateId];
|
|
if (isolate == null) return null; // Isolate has been closed.
|
|
var receivePort = isolate.lookup(receivePortId);
|
|
if (receivePort == null) return null; // Port has been closed.
|
|
result = new _NativeJsSendPort(receivePort, isolateId);
|
|
} else {
|
|
result = new _WorkerSendPort(managerId, isolateId, receivePortId);
|
|
}
|
|
deserializedObjects.add(result);
|
|
return result;
|
|
}
|
|
|
|
// ['raw sendport', <sendport>].
|
|
SendPort deserializeRawSendPort(x) {
|
|
assert(x[0] == 'raw sendport');
|
|
SendPort result = x[1];
|
|
deserializedObjects.add(result);
|
|
return result;
|
|
}
|
|
|
|
// ['js-object', <key-list>, <value-list>].
|
|
deserializeJSObject(x) {
|
|
assert(x[0] == 'js-object');
|
|
List keys = x[1];
|
|
List values = x[2];
|
|
var o = JS('', '{}');
|
|
deserializedObjects.add(o);
|
|
for (int i = 0; i < keys.length; i++) {
|
|
JS('', '#[#]=#', o, keys[i], deserialize(values[i]));
|
|
}
|
|
return o;
|
|
}
|
|
|
|
// ['function', <name>].
|
|
Function deserializeClosure(x) {
|
|
assert(x[0] == 'function');
|
|
String name = x[1];
|
|
Function result = IsolateNatives._getJSFunctionFromName(name);
|
|
deserializedObjects.add(result);
|
|
return result;
|
|
}
|
|
|
|
// ['dart', <class-id>, <field-list>].
|
|
deserializeDartObject(x) {
|
|
assert(x[0] == 'dart');
|
|
String classId = x[1];
|
|
List fields = x[2];
|
|
var instanceFromClassId = JS_EMBEDDED_GLOBAL('', INSTANCE_FROM_CLASS_ID);
|
|
var initializeObject = JS_EMBEDDED_GLOBAL('', INITIALIZE_EMPTY_INSTANCE);
|
|
|
|
var emptyInstance = JS('', '#(#)', instanceFromClassId, classId);
|
|
deserializedObjects.add(emptyInstance);
|
|
deserializeArrayInPlace(fields);
|
|
return JS(
|
|
'', '#(#, #, #)', initializeObject, classId, emptyInstance, fields);
|
|
}
|
|
}
|