mirror of
https://github.com/nodejs/node.git
synced 2024-11-21 10:59:27 +00:00
src: distinguish HTML transferable and cloneable
The HTML structured serialize algorithm treats transferable and
serializable as two different bits. A web platform interface can be
both transferable and serializable.
Splits BaseObject::TransferMode to be able to compose the two bits
and distinguishes the transferable and cloneable.
PR-URL: https://github.com/nodejs/node/pull/47956
Refs: cf13b9b465
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
Reviewed-By: Yagiz Nizipli <yagiz@nizipli.com>
Reviewed-By: Daeyeon Jeong <daeyeon.dev@gmail.com>
This commit is contained in:
parent
3205b1936a
commit
38dee8a1c0
@ -36,7 +36,7 @@
|
||||
|
||||
# Reset this number to 0 on major V8 upgrades.
|
||||
# Increment by one for each non-official patch applied to deps/v8.
|
||||
'v8_embedder_string': '-node.10',
|
||||
'v8_embedder_string': '-node.11',
|
||||
|
||||
##### V8 defaults for Node.js #####
|
||||
|
||||
|
14
deps/v8/include/v8-value-serializer.h
vendored
14
deps/v8/include/v8-value-serializer.h
vendored
@ -75,6 +75,20 @@ class V8_EXPORT ValueSerializer {
|
||||
*/
|
||||
virtual void ThrowDataCloneError(Local<String> message) = 0;
|
||||
|
||||
/**
|
||||
* The embedder overrides this method to enable custom host object filter
|
||||
* with Delegate::IsHostObject.
|
||||
*
|
||||
* This method is called at most once per serializer.
|
||||
*/
|
||||
virtual bool HasCustomHostObject(Isolate* isolate);
|
||||
|
||||
/**
|
||||
* The embedder overrides this method to determine if an JS object is a
|
||||
* host object and needs to be serialized by the host.
|
||||
*/
|
||||
virtual Maybe<bool> IsHostObject(Isolate* isolate, Local<Object> object);
|
||||
|
||||
/**
|
||||
* The embedder overrides this method to write some kind of host object, if
|
||||
* possible. If not, a suitable exception should be thrown and
|
||||
|
13
deps/v8/src/api/api.cc
vendored
13
deps/v8/src/api/api.cc
vendored
@ -3569,6 +3569,19 @@ Maybe<bool> ValueSerializer::Delegate::WriteHostObject(Isolate* v8_isolate,
|
||||
return Nothing<bool>();
|
||||
}
|
||||
|
||||
bool ValueSerializer::Delegate::HasCustomHostObject(Isolate* v8_isolate) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Maybe<bool> ValueSerializer::Delegate::IsHostObject(Isolate* v8_isolate,
|
||||
Local<Object> object) {
|
||||
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
|
||||
i::Handle<i::JSObject> js_object =
|
||||
i::Handle<i::JSObject>::cast(Utils::OpenHandle(*object));
|
||||
return Just<bool>(
|
||||
i::JSObject::GetEmbedderFieldCount(js_object->map(i_isolate)));
|
||||
}
|
||||
|
||||
Maybe<uint32_t> ValueSerializer::Delegate::GetSharedArrayBufferId(
|
||||
Isolate* v8_isolate, Local<SharedArrayBuffer> shared_array_buffer) {
|
||||
i::Isolate* i_isolate = reinterpret_cast<i::Isolate*>(v8_isolate);
|
||||
|
30
deps/v8/src/objects/value-serializer.cc
vendored
30
deps/v8/src/objects/value-serializer.cc
vendored
@ -268,7 +268,12 @@ ValueSerializer::ValueSerializer(Isolate* isolate,
|
||||
zone_(isolate->allocator(), ZONE_NAME),
|
||||
id_map_(isolate->heap(), ZoneAllocationPolicy(&zone_)),
|
||||
array_buffer_transfer_map_(isolate->heap(),
|
||||
ZoneAllocationPolicy(&zone_)) {}
|
||||
ZoneAllocationPolicy(&zone_)) {
|
||||
if (delegate_) {
|
||||
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_);
|
||||
has_custom_host_objects_ = delegate_->HasCustomHostObject(v8_isolate);
|
||||
}
|
||||
}
|
||||
|
||||
ValueSerializer::~ValueSerializer() {
|
||||
if (buffer_) {
|
||||
@ -582,7 +587,11 @@ Maybe<bool> ValueSerializer::WriteJSReceiver(Handle<JSReceiver> receiver) {
|
||||
case JS_TYPED_ARRAY_PROTOTYPE_TYPE:
|
||||
case JS_API_OBJECT_TYPE: {
|
||||
Handle<JSObject> js_object = Handle<JSObject>::cast(receiver);
|
||||
if (JSObject::GetEmbedderFieldCount(js_object->map(isolate_))) {
|
||||
Maybe<bool> is_host_object = IsHostObject(js_object);
|
||||
if (is_host_object.IsNothing()) {
|
||||
return is_host_object;
|
||||
}
|
||||
if (is_host_object.FromJust()) {
|
||||
return WriteHostObject(js_object);
|
||||
} else {
|
||||
return WriteJSObject(js_object);
|
||||
@ -1190,6 +1199,23 @@ Maybe<uint32_t> ValueSerializer::WriteJSObjectPropertiesSlow(
|
||||
return Just(properties_written);
|
||||
}
|
||||
|
||||
Maybe<bool> ValueSerializer::IsHostObject(Handle<JSObject> js_object) {
|
||||
if (!has_custom_host_objects_) {
|
||||
return Just<bool>(
|
||||
JSObject::GetEmbedderFieldCount(js_object->map(isolate_)));
|
||||
}
|
||||
DCHECK_NOT_NULL(delegate_);
|
||||
|
||||
v8::Isolate* v8_isolate = reinterpret_cast<v8::Isolate*>(isolate_);
|
||||
Maybe<bool> result =
|
||||
delegate_->IsHostObject(v8_isolate, Utils::ToLocal(js_object));
|
||||
RETURN_VALUE_IF_SCHEDULED_EXCEPTION(isolate_, Nothing<bool>());
|
||||
DCHECK(!result.IsNothing());
|
||||
|
||||
if (V8_UNLIKELY(out_of_memory_)) return ThrowIfOutOfMemory();
|
||||
return result;
|
||||
}
|
||||
|
||||
Maybe<bool> ValueSerializer::ThrowIfOutOfMemory() {
|
||||
if (out_of_memory_) {
|
||||
return ThrowDataCloneError(MessageTemplate::kDataCloneErrorOutOfMemory);
|
||||
|
3
deps/v8/src/objects/value-serializer.h
vendored
3
deps/v8/src/objects/value-serializer.h
vendored
@ -155,6 +155,8 @@ class ValueSerializer {
|
||||
Maybe<uint32_t> WriteJSObjectPropertiesSlow(
|
||||
Handle<JSObject> object, Handle<FixedArray> keys) V8_WARN_UNUSED_RESULT;
|
||||
|
||||
Maybe<bool> IsHostObject(Handle<JSObject> object);
|
||||
|
||||
/*
|
||||
* Asks the delegate to handle an error that occurred during data cloning, by
|
||||
* throwing an exception appropriate for the host.
|
||||
@ -172,6 +174,7 @@ class ValueSerializer {
|
||||
uint8_t* buffer_ = nullptr;
|
||||
size_t buffer_size_ = 0;
|
||||
size_t buffer_capacity_ = 0;
|
||||
bool has_custom_host_objects_ = false;
|
||||
bool treat_array_buffer_views_as_host_objects_ = false;
|
||||
bool out_of_memory_ = false;
|
||||
Zone zone_;
|
||||
|
@ -2808,7 +2808,18 @@ TEST_F(ValueSerializerTest, UnsupportedHostObject) {
|
||||
|
||||
class ValueSerializerTestWithHostObject : public ValueSerializerTest {
|
||||
protected:
|
||||
ValueSerializerTestWithHostObject() : serializer_delegate_(this) {}
|
||||
ValueSerializerTestWithHostObject() : serializer_delegate_(this) {
|
||||
ON_CALL(serializer_delegate_, HasCustomHostObject)
|
||||
.WillByDefault([this](Isolate* isolate) {
|
||||
return serializer_delegate_
|
||||
.ValueSerializer::Delegate::HasCustomHostObject(isolate);
|
||||
});
|
||||
ON_CALL(serializer_delegate_, IsHostObject)
|
||||
.WillByDefault([this](Isolate* isolate, Local<Object> object) {
|
||||
return serializer_delegate_.ValueSerializer::Delegate::IsHostObject(
|
||||
isolate, object);
|
||||
});
|
||||
}
|
||||
|
||||
static const uint8_t kExampleHostObjectTag;
|
||||
|
||||
@ -2832,6 +2843,9 @@ class ValueSerializerTestWithHostObject : public ValueSerializerTest {
|
||||
public:
|
||||
explicit SerializerDelegate(ValueSerializerTestWithHostObject* test)
|
||||
: test_(test) {}
|
||||
MOCK_METHOD(bool, HasCustomHostObject, (Isolate*), (override));
|
||||
MOCK_METHOD(Maybe<bool>, IsHostObject, (Isolate*, Local<Object> object),
|
||||
(override));
|
||||
MOCK_METHOD(Maybe<bool>, WriteHostObject, (Isolate*, Local<Object> object),
|
||||
(override));
|
||||
void ThrowDataCloneError(Local<String> message) override {
|
||||
@ -3049,6 +3063,43 @@ TEST_F(ValueSerializerTestWithHostObject, DecodeSimpleHostObject) {
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(ValueSerializerTestWithHostObject,
|
||||
RoundTripHostJSObjectWithoutCustomHostObject) {
|
||||
EXPECT_CALL(serializer_delegate_, HasCustomHostObject(isolate()))
|
||||
.WillOnce(Invoke([](Isolate* isolate) { return false; }));
|
||||
RoundTripTest("({ a: { my_host_object: true }, get b() { return this.a; }})");
|
||||
}
|
||||
|
||||
TEST_F(ValueSerializerTestWithHostObject, RoundTripHostJSObject) {
|
||||
EXPECT_CALL(serializer_delegate_, HasCustomHostObject(isolate()))
|
||||
.WillOnce(Invoke([](Isolate* isolate) { return true; }));
|
||||
EXPECT_CALL(serializer_delegate_, IsHostObject(isolate(), _))
|
||||
.WillRepeatedly(Invoke([this](Isolate* isolate, Local<Object> object) {
|
||||
EXPECT_TRUE(object->IsObject());
|
||||
Local<Context> context = isolate->GetCurrentContext();
|
||||
return object->Has(context, StringFromUtf8("my_host_object"));
|
||||
}));
|
||||
EXPECT_CALL(serializer_delegate_, WriteHostObject(isolate(), _))
|
||||
.WillOnce(Invoke([this](Isolate*, Local<Object> object) {
|
||||
EXPECT_TRUE(object->IsObject());
|
||||
WriteExampleHostObjectTag();
|
||||
return Just(true);
|
||||
}));
|
||||
EXPECT_CALL(deserializer_delegate_, ReadHostObject(isolate()))
|
||||
.WillOnce(Invoke([this](Isolate* isolate) {
|
||||
EXPECT_TRUE(ReadExampleHostObjectTag());
|
||||
Local<Context> context = isolate->GetCurrentContext();
|
||||
Local<Object> obj = Object::New(isolate);
|
||||
obj->Set(context, StringFromUtf8("my_host_object"), v8::True(isolate))
|
||||
.Check();
|
||||
return obj;
|
||||
}));
|
||||
RoundTripTest("({ a: { my_host_object: true }, get b() { return this.a; }})");
|
||||
ExpectScriptTrue("!('my_host_object' in result)");
|
||||
ExpectScriptTrue("result.a.my_host_object");
|
||||
ExpectScriptTrue("result.a === result.b");
|
||||
}
|
||||
|
||||
class ValueSerializerTestWithHostArrayBufferView
|
||||
: public ValueSerializerTestWithHostObject {
|
||||
protected:
|
||||
|
@ -58,15 +58,15 @@ const {
|
||||
const assert = require('internal/assert');
|
||||
|
||||
const {
|
||||
messaging_deserialize_symbol: kDeserialize,
|
||||
messaging_transfer_symbol: kTransfer,
|
||||
messaging_transfer_list_symbol: kTransferList,
|
||||
} = internalBinding('symbols');
|
||||
kDeserialize,
|
||||
kTransfer,
|
||||
kTransferList,
|
||||
} = require('internal/worker/js_transferable');
|
||||
|
||||
let _MessageChannel;
|
||||
let makeTransferable;
|
||||
let markTransferMode;
|
||||
|
||||
// Loading the MessageChannel and makeTransferable have to be done lazily
|
||||
// Loading the MessageChannel and markTransferable have to be done lazily
|
||||
// because otherwise we'll end up with a require cycle that ends up with
|
||||
// an incomplete initialization of abort_controller.
|
||||
|
||||
@ -75,10 +75,10 @@ function lazyMessageChannel() {
|
||||
return new _MessageChannel();
|
||||
}
|
||||
|
||||
function lazyMakeTransferable(obj) {
|
||||
makeTransferable ??=
|
||||
require('internal/worker/js_transferable').makeTransferable;
|
||||
return makeTransferable(obj);
|
||||
function lazyMarkTransferMode(obj, cloneable, transferable) {
|
||||
markTransferMode ??=
|
||||
require('internal/worker/js_transferable').markTransferMode;
|
||||
markTransferMode(obj, cloneable, transferable);
|
||||
}
|
||||
|
||||
const clearTimeoutRegistry = new SafeFinalizationRegistry(clearTimeout);
|
||||
@ -355,7 +355,10 @@ function createAbortSignal(init = kEmptyObject) {
|
||||
signal[kAborted] = aborted;
|
||||
signal[kReason] = reason;
|
||||
signal[kComposite] = composite;
|
||||
return transferable ? lazyMakeTransferable(signal) : signal;
|
||||
if (transferable) {
|
||||
lazyMarkTransferMode(signal, false, true);
|
||||
}
|
||||
return signal;
|
||||
}
|
||||
|
||||
function abortSignal(signal, reason) {
|
||||
@ -411,7 +414,8 @@ class AbortController {
|
||||
function transferableAbortSignal(signal) {
|
||||
if (signal?.[kAborted] === undefined)
|
||||
throw new ERR_INVALID_ARG_TYPE('signal', 'AbortSignal', signal);
|
||||
return lazyMakeTransferable(signal);
|
||||
lazyMarkTransferMode(signal, false, true);
|
||||
return signal;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -32,7 +32,7 @@ const {
|
||||
const { URL } = require('internal/url');
|
||||
|
||||
const {
|
||||
makeTransferable,
|
||||
markTransferMode,
|
||||
kClone,
|
||||
kDeserialize,
|
||||
} = require('internal/worker/js_transferable');
|
||||
@ -136,6 +136,8 @@ class Blob {
|
||||
* @constructs {Blob}
|
||||
*/
|
||||
constructor(sources = [], options) {
|
||||
markTransferMode(this, true, false);
|
||||
|
||||
if (sources === null ||
|
||||
typeof sources[SymbolIterator] !== 'function' ||
|
||||
typeof sources === 'string') {
|
||||
@ -167,9 +169,6 @@ class Blob {
|
||||
type = `${type}`;
|
||||
this[kType] = RegExpPrototypeExec(disallowedTypeCharacters, type) !== null ?
|
||||
'' : StringPrototypeToLowerCase(type);
|
||||
|
||||
// eslint-disable-next-line no-constructor-return
|
||||
return makeTransferable(this);
|
||||
}
|
||||
|
||||
[kInspect](depth, options) {
|
||||
@ -385,16 +384,19 @@ class Blob {
|
||||
}
|
||||
|
||||
function ClonedBlob() {
|
||||
return makeTransferable(ReflectConstruct(function() {}, [], Blob));
|
||||
return ReflectConstruct(function() {
|
||||
markTransferMode(this, true, false);
|
||||
}, [], Blob);
|
||||
}
|
||||
ClonedBlob.prototype[kDeserialize] = () => {};
|
||||
|
||||
function createBlob(handle, length, type = '') {
|
||||
return makeTransferable(ReflectConstruct(function() {
|
||||
return ReflectConstruct(function() {
|
||||
markTransferMode(this, true, false);
|
||||
this[kHandle] = handle;
|
||||
this[kType] = type;
|
||||
this[kLength] = length;
|
||||
}, [], Blob));
|
||||
}, [], Blob);
|
||||
}
|
||||
|
||||
ObjectDefineProperty(Blob.prototype, SymbolToStringTag, {
|
||||
|
@ -20,7 +20,7 @@ const {
|
||||
} = require('internal/socketaddress');
|
||||
|
||||
const {
|
||||
JSTransferable,
|
||||
markTransferMode,
|
||||
kClone,
|
||||
kDeserialize,
|
||||
} = require('internal/worker/js_transferable');
|
||||
@ -36,9 +36,9 @@ const {
|
||||
|
||||
const { validateInt32, validateString } = require('internal/validators');
|
||||
|
||||
class BlockList extends JSTransferable {
|
||||
class BlockList {
|
||||
constructor() {
|
||||
super();
|
||||
markTransferMode(this, true, false);
|
||||
this[kHandle] = new BlockListHandle();
|
||||
this[kHandle][owner_symbol] = this;
|
||||
}
|
||||
@ -148,9 +148,9 @@ class BlockList extends JSTransferable {
|
||||
}
|
||||
}
|
||||
|
||||
class InternalBlockList extends JSTransferable {
|
||||
class InternalBlockList {
|
||||
constructor(handle) {
|
||||
super();
|
||||
markTransferMode(this, true, false);
|
||||
this[kHandle] = handle;
|
||||
if (handle !== undefined)
|
||||
handle[owner_symbol] = this;
|
||||
|
@ -57,7 +57,7 @@ const {
|
||||
} = require('internal/util/types');
|
||||
|
||||
const {
|
||||
makeTransferable,
|
||||
markTransferMode,
|
||||
kClone,
|
||||
kDeserialize,
|
||||
} = require('internal/worker/js_transferable');
|
||||
@ -706,7 +706,7 @@ ObjectDefineProperties(CryptoKey.prototype, {
|
||||
// All internal code must use new InternalCryptoKey to create
|
||||
// CryptoKey instances. The CryptoKey class is exposed to end
|
||||
// user code but is not permitted to be constructed directly.
|
||||
// Using makeTransferable also allows the CryptoKey to be
|
||||
// Using markTransferMode also allows the CryptoKey to be
|
||||
// cloned to Workers.
|
||||
class InternalCryptoKey {
|
||||
constructor(
|
||||
@ -714,6 +714,7 @@ class InternalCryptoKey {
|
||||
algorithm,
|
||||
keyUsages,
|
||||
extractable) {
|
||||
markTransferMode(this, true, false);
|
||||
// Using symbol properties here currently instead of private
|
||||
// properties because (for now) the performance penalty of
|
||||
// private fields is still too high.
|
||||
@ -721,9 +722,6 @@ class InternalCryptoKey {
|
||||
this[kAlgorithm] = algorithm;
|
||||
this[kExtractable] = extractable;
|
||||
this[kKeyUsages] = keyUsages;
|
||||
|
||||
// eslint-disable-next-line no-constructor-return
|
||||
return makeTransferable(this);
|
||||
}
|
||||
|
||||
[kClone]() {
|
||||
|
@ -48,7 +48,7 @@ const {
|
||||
} = require('internal/errors');
|
||||
|
||||
const {
|
||||
JSTransferable,
|
||||
markTransferMode,
|
||||
kClone,
|
||||
kDeserialize,
|
||||
} = require('internal/worker/js_transferable');
|
||||
@ -94,16 +94,16 @@ function getFlags(options = kEmptyObject) {
|
||||
return flags;
|
||||
}
|
||||
|
||||
class InternalX509Certificate extends JSTransferable {
|
||||
class InternalX509Certificate {
|
||||
[kInternalState] = new SafeMap();
|
||||
|
||||
constructor(handle) {
|
||||
super();
|
||||
markTransferMode(this, true, false);
|
||||
this[kHandle] = handle;
|
||||
}
|
||||
}
|
||||
|
||||
class X509Certificate extends JSTransferable {
|
||||
class X509Certificate {
|
||||
[kInternalState] = new SafeMap();
|
||||
|
||||
constructor(buffer) {
|
||||
@ -115,7 +115,7 @@ class X509Certificate extends JSTransferable {
|
||||
['string', 'Buffer', 'TypedArray', 'DataView'],
|
||||
buffer);
|
||||
}
|
||||
super();
|
||||
markTransferMode(this, true, false);
|
||||
this[kHandle] = parseX509(buffer);
|
||||
}
|
||||
|
||||
|
@ -10,11 +10,7 @@ const {
|
||||
ObjectDefineProperties,
|
||||
ObjectDefineProperty,
|
||||
ObjectGetOwnPropertyDescriptor,
|
||||
ObjectGetOwnPropertyDescriptors,
|
||||
ObjectSetPrototypeOf,
|
||||
ObjectValues,
|
||||
ReflectApply,
|
||||
SafeArrayIterator,
|
||||
SafeFinalizationRegistry,
|
||||
SafeMap,
|
||||
SafeWeakMap,
|
||||
@ -1114,30 +1110,9 @@ function defineEventHandler(emitter, name, event = name) {
|
||||
});
|
||||
}
|
||||
|
||||
const EventEmitterMixin = (Superclass) => {
|
||||
class MixedEventEmitter extends Superclass {
|
||||
constructor(...args) {
|
||||
args = new SafeArrayIterator(args);
|
||||
super(...args);
|
||||
FunctionPrototypeCall(EventEmitter, this);
|
||||
}
|
||||
}
|
||||
const protoProps = ObjectGetOwnPropertyDescriptors(EventEmitter.prototype);
|
||||
delete protoProps.constructor;
|
||||
const propertiesValues = ObjectValues(protoProps);
|
||||
for (let i = 0; i < propertiesValues.length; i++) {
|
||||
// We want to use null-prototype objects to not rely on globally mutable
|
||||
// %Object.prototype%.
|
||||
ObjectSetPrototypeOf(propertiesValues[i], null);
|
||||
}
|
||||
ObjectDefineProperties(MixedEventEmitter.prototype, protoProps);
|
||||
return MixedEventEmitter;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
Event,
|
||||
CustomEvent,
|
||||
EventEmitterMixin,
|
||||
EventTarget,
|
||||
NodeEventTarget,
|
||||
defineEventHandler,
|
||||
|
@ -90,7 +90,7 @@ const {
|
||||
lazyDOMException,
|
||||
promisify,
|
||||
} = require('internal/util');
|
||||
const { EventEmitterMixin } = require('internal/event_target');
|
||||
const EventEmitter = require('events');
|
||||
const { StringDecoder } = require('string_decoder');
|
||||
const { kFSWatchStart, watch } = require('internal/fs/watchers');
|
||||
const nonNativeWatcher = require('internal/fs/recursive_watch');
|
||||
@ -110,7 +110,7 @@ const kLocked = Symbol('kLocked');
|
||||
const { kUsePromises } = binding;
|
||||
const { Interface } = require('internal/readline/interface');
|
||||
const {
|
||||
JSTransferable, kDeserialize, kTransfer, kTransferList,
|
||||
kDeserialize, kTransfer, kTransferList, markTransferMode,
|
||||
} = require('internal/worker/js_transferable');
|
||||
|
||||
const getDirectoryEntriesPromise = promisify(getDirents);
|
||||
@ -130,12 +130,13 @@ function lazyFsStreams() {
|
||||
return fsStreams ??= require('internal/fs/streams');
|
||||
}
|
||||
|
||||
class FileHandle extends EventEmitterMixin(JSTransferable) {
|
||||
class FileHandle extends EventEmitter {
|
||||
/**
|
||||
* @param {InternalFSBinding.FileHandle | undefined} filehandle
|
||||
*/
|
||||
constructor(filehandle) {
|
||||
super();
|
||||
markTransferMode(this, false, true);
|
||||
this[kHandle] = filehandle;
|
||||
this[kFd] = filehandle ? filehandle.fd : -1;
|
||||
|
||||
|
@ -45,7 +45,7 @@ const kRecordable = Symbol('kRecordable');
|
||||
const {
|
||||
kClone,
|
||||
kDeserialize,
|
||||
makeTransferable,
|
||||
markTransferMode,
|
||||
} = require('internal/worker/js_transferable');
|
||||
|
||||
function isHistogram(object) {
|
||||
@ -319,21 +319,23 @@ class RecordableHistogram extends Histogram {
|
||||
}
|
||||
|
||||
function internalHistogram(handle) {
|
||||
return makeTransferable(ReflectConstruct(
|
||||
return ReflectConstruct(
|
||||
function() {
|
||||
markTransferMode(this, true, false);
|
||||
this[kHandle] = handle;
|
||||
this[kMap] = new SafeMap();
|
||||
}, [], Histogram));
|
||||
}, [], Histogram);
|
||||
}
|
||||
internalHistogram.prototype[kDeserialize] = () => {};
|
||||
|
||||
function internalRecordableHistogram(handle) {
|
||||
return makeTransferable(ReflectConstruct(
|
||||
return ReflectConstruct(
|
||||
function() {
|
||||
markTransferMode(this, true, false);
|
||||
this[kHandle] = handle;
|
||||
this[kMap] = new SafeMap();
|
||||
this[kRecordable] = true;
|
||||
}, [], RecordableHistogram));
|
||||
}, [], RecordableHistogram);
|
||||
}
|
||||
internalRecordableHistogram.prototype[kDeserialize] = () => {};
|
||||
|
||||
|
@ -32,7 +32,7 @@ const {
|
||||
} = require('internal/util');
|
||||
|
||||
const {
|
||||
makeTransferable,
|
||||
markTransferMode,
|
||||
} = require('internal/worker/js_transferable');
|
||||
|
||||
const kEnabled = Symbol('kEnabled');
|
||||
@ -79,12 +79,13 @@ function monitorEventLoopDelay(options = kEmptyObject) {
|
||||
const { resolution = 10 } = options;
|
||||
validateInteger(resolution, 'options.resolution', 1);
|
||||
|
||||
return makeTransferable(ReflectConstruct(
|
||||
return ReflectConstruct(
|
||||
function() {
|
||||
markTransferMode(this, true, false);
|
||||
this[kEnabled] = false;
|
||||
this[kHandle] = createELDHistogram(resolution);
|
||||
this[kMap] = new SafeMap();
|
||||
}, [], ELDHistogram));
|
||||
}, [], ELDHistogram);
|
||||
}
|
||||
|
||||
module.exports = monitorEventLoopDelay;
|
||||
|
@ -32,7 +32,7 @@ const {
|
||||
const { inspect } = require('internal/util/inspect');
|
||||
|
||||
const {
|
||||
JSTransferable,
|
||||
markTransferMode,
|
||||
kClone,
|
||||
kDeserialize,
|
||||
} = require('internal/worker/js_transferable');
|
||||
@ -40,13 +40,14 @@ const {
|
||||
const kHandle = Symbol('kHandle');
|
||||
const kDetail = Symbol('kDetail');
|
||||
|
||||
class SocketAddress extends JSTransferable {
|
||||
class SocketAddress {
|
||||
static isSocketAddress(value) {
|
||||
return value?.[kHandle] !== undefined;
|
||||
}
|
||||
|
||||
constructor(options = kEmptyObject) {
|
||||
super();
|
||||
markTransferMode(this, true, false);
|
||||
|
||||
validateObject(options, 'options');
|
||||
let { family = 'ipv4' } = options;
|
||||
const {
|
||||
@ -139,9 +140,10 @@ class SocketAddress extends JSTransferable {
|
||||
}
|
||||
}
|
||||
|
||||
class InternalSocketAddress extends JSTransferable {
|
||||
class InternalSocketAddress {
|
||||
constructor(handle) {
|
||||
super();
|
||||
markTransferMode(this, true, false);
|
||||
|
||||
this[kHandle] = handle;
|
||||
this[kDetail] = this[kHandle]?.detail({
|
||||
address: undefined,
|
||||
|
@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
const {
|
||||
makeTransferable,
|
||||
markTransferMode,
|
||||
kClone,
|
||||
kDeserialize,
|
||||
} = require('internal/worker/js_transferable');
|
||||
@ -23,8 +23,7 @@ class E {
|
||||
class F extends E {
|
||||
constructor(b) {
|
||||
super(b);
|
||||
/* eslint-disable-next-line no-constructor-return */
|
||||
return makeTransferable(this);
|
||||
markTransferMode(this, true, false);
|
||||
}
|
||||
|
||||
[kClone]() {
|
||||
|
@ -69,7 +69,7 @@ const {
|
||||
kDeserialize,
|
||||
kTransfer,
|
||||
kTransferList,
|
||||
makeTransferable,
|
||||
markTransferMode,
|
||||
} = require('internal/worker/js_transferable');
|
||||
|
||||
const {
|
||||
@ -242,6 +242,7 @@ class ReadableStream {
|
||||
* @param {QueuingStrategy} [strategy]
|
||||
*/
|
||||
constructor(source = {}, strategy = kEmptyObject) {
|
||||
markTransferMode(this, false, true);
|
||||
if (source === null)
|
||||
throw new ERR_INVALID_ARG_VALUE('source', 'Object', source);
|
||||
this[kState] = {
|
||||
@ -287,9 +288,6 @@ class ReadableStream {
|
||||
extractHighWaterMark(highWaterMark, 1),
|
||||
extractSizeAlgorithm(size));
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-constructor-return
|
||||
return makeTransferable(this);
|
||||
}
|
||||
|
||||
get [kIsDisturbed]() {
|
||||
@ -645,8 +643,9 @@ ObjectDefineProperties(ReadableStream.prototype, {
|
||||
});
|
||||
|
||||
function TransferredReadableStream() {
|
||||
return makeTransferable(ReflectConstruct(
|
||||
return ReflectConstruct(
|
||||
function() {
|
||||
markTransferMode(this, false, true);
|
||||
this[kType] = 'ReadableStream';
|
||||
this[kState] = {
|
||||
disturbed: false,
|
||||
@ -661,7 +660,7 @@ function TransferredReadableStream() {
|
||||
};
|
||||
this[kIsClosedPromise] = createDeferredPromise();
|
||||
},
|
||||
[], ReadableStream));
|
||||
[], ReadableStream);
|
||||
}
|
||||
TransferredReadableStream.prototype[kDeserialize] = () => {};
|
||||
|
||||
@ -1209,6 +1208,7 @@ function createReadableByteStreamController() {
|
||||
function createTeeReadableStream(start, pull, cancel) {
|
||||
return ReflectConstruct(
|
||||
function() {
|
||||
markTransferMode(this, false, true);
|
||||
this[kType] = 'ReadableStream';
|
||||
this[kState] = {
|
||||
disturbed: false,
|
||||
@ -1231,7 +1231,6 @@ function createTeeReadableStream(start, pull, cancel) {
|
||||
}),
|
||||
1,
|
||||
() => 1);
|
||||
return makeTransferable(this);
|
||||
}, [], ReadableStream,
|
||||
);
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ const {
|
||||
const assert = require('internal/assert');
|
||||
|
||||
const {
|
||||
makeTransferable,
|
||||
markTransferMode,
|
||||
kClone,
|
||||
kDeserialize,
|
||||
} = require('internal/worker/js_transferable');
|
||||
@ -49,13 +49,12 @@ const {
|
||||
class CloneableDOMException extends DOMException {
|
||||
constructor(message, name) {
|
||||
super(message, name);
|
||||
markTransferMode(this, true, false);
|
||||
this[kDeserialize]({
|
||||
message: this.message,
|
||||
name: this.name,
|
||||
code: this.code,
|
||||
});
|
||||
// eslint-disable-next-line no-constructor-return
|
||||
return makeTransferable(this);
|
||||
}
|
||||
|
||||
[kClone]() {
|
||||
@ -95,11 +94,10 @@ class CloneableDOMException extends DOMException {
|
||||
}
|
||||
|
||||
function InternalCloneableDOMException() {
|
||||
return makeTransferable(
|
||||
ReflectConstruct(
|
||||
CloneableDOMException,
|
||||
[],
|
||||
DOMException));
|
||||
return ReflectConstruct(
|
||||
CloneableDOMException,
|
||||
[],
|
||||
DOMException);
|
||||
}
|
||||
InternalCloneableDOMException[kDeserialize] = () => {};
|
||||
|
||||
|
@ -34,7 +34,7 @@ const {
|
||||
kDeserialize,
|
||||
kTransfer,
|
||||
kTransferList,
|
||||
makeTransferable,
|
||||
markTransferMode,
|
||||
} = require('internal/worker/js_transferable');
|
||||
|
||||
const {
|
||||
@ -120,6 +120,7 @@ class TransformStream {
|
||||
transformer = null,
|
||||
writableStrategy = kEmptyObject,
|
||||
readableStrategy = kEmptyObject) {
|
||||
markTransferMode(this, false, true);
|
||||
const readableType = transformer?.readableType;
|
||||
const writableType = transformer?.writableType;
|
||||
const start = transformer?.start;
|
||||
@ -170,9 +171,6 @@ class TransformStream {
|
||||
} else {
|
||||
startPromise.resolve();
|
||||
}
|
||||
|
||||
// eslint-disable-next-line no-constructor-return
|
||||
return makeTransferable(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -247,8 +245,9 @@ ObjectDefineProperties(TransformStream.prototype, {
|
||||
});
|
||||
|
||||
function TransferredTransformStream() {
|
||||
return makeTransferable(ReflectConstruct(
|
||||
return ReflectConstruct(
|
||||
function() {
|
||||
markTransferMode(this, false, true);
|
||||
this[kType] = 'TransformStream';
|
||||
this[kState] = {
|
||||
readable: undefined,
|
||||
@ -262,7 +261,7 @@ function TransferredTransformStream() {
|
||||
controller: undefined,
|
||||
};
|
||||
},
|
||||
[], TransformStream));
|
||||
[], TransformStream);
|
||||
}
|
||||
TransferredTransformStream.prototype[kDeserialize] = () => {};
|
||||
|
||||
|
@ -44,7 +44,7 @@ const {
|
||||
kDeserialize,
|
||||
kTransfer,
|
||||
kTransferList,
|
||||
makeTransferable,
|
||||
markTransferMode,
|
||||
} = require('internal/worker/js_transferable');
|
||||
|
||||
const {
|
||||
@ -153,6 +153,7 @@ class WritableStream {
|
||||
* @param {QueuingStrategy} [strategy]
|
||||
*/
|
||||
constructor(sink = null, strategy = kEmptyObject) {
|
||||
markTransferMode(this, false, true);
|
||||
const type = sink?.type;
|
||||
if (type !== undefined)
|
||||
throw new ERR_INVALID_ARG_VALUE.RangeError('type', type);
|
||||
@ -208,9 +209,6 @@ class WritableStream {
|
||||
sink,
|
||||
highWaterMark,
|
||||
size);
|
||||
|
||||
// eslint-disable-next-line no-constructor-return
|
||||
return makeTransferable(this);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -328,8 +326,9 @@ ObjectDefineProperties(WritableStream.prototype, {
|
||||
});
|
||||
|
||||
function TransferredWritableStream() {
|
||||
return makeTransferable(ReflectConstruct(
|
||||
return ReflectConstruct(
|
||||
function() {
|
||||
markTransferMode(this, false, true);
|
||||
this[kType] = 'WritableStream';
|
||||
this[kState] = {
|
||||
close: createDeferredPromise(),
|
||||
@ -373,7 +372,7 @@ function TransferredWritableStream() {
|
||||
this[kIsClosedPromise] = createDeferredPromise();
|
||||
this[kControllerErrorFunction] = () => {};
|
||||
},
|
||||
[], WritableStream));
|
||||
[], WritableStream);
|
||||
}
|
||||
TransferredWritableStream.prototype[kDeserialize] = () => {};
|
||||
|
||||
|
@ -1,12 +1,6 @@
|
||||
'use strict';
|
||||
const {
|
||||
Error,
|
||||
ObjectDefineProperties,
|
||||
ObjectGetOwnPropertyDescriptors,
|
||||
ObjectGetPrototypeOf,
|
||||
ObjectSetPrototypeOf,
|
||||
ObjectValues,
|
||||
ReflectConstruct,
|
||||
StringPrototypeSplit,
|
||||
} = primordials;
|
||||
const {
|
||||
@ -16,9 +10,18 @@ const {
|
||||
messaging_transfer_list_symbol,
|
||||
} = internalBinding('symbols');
|
||||
const {
|
||||
JSTransferable,
|
||||
setDeserializerCreateObjectFunction,
|
||||
} = internalBinding('messaging');
|
||||
const {
|
||||
privateSymbols: {
|
||||
transfer_mode_private_symbol,
|
||||
},
|
||||
constants: {
|
||||
kDisallowCloneAndTransfer,
|
||||
kTransferable,
|
||||
kCloneable,
|
||||
},
|
||||
} = internalBinding('util');
|
||||
|
||||
function setup() {
|
||||
// Register the handler that will be used when deserializing JS-based objects
|
||||
@ -39,26 +42,57 @@ function setup() {
|
||||
});
|
||||
}
|
||||
|
||||
function makeTransferable(obj) {
|
||||
// If the object is already transferable, skip all this.
|
||||
if (obj instanceof JSTransferable) return obj;
|
||||
const inst = ReflectConstruct(JSTransferable, [], obj.constructor);
|
||||
const properties = ObjectGetOwnPropertyDescriptors(obj);
|
||||
const propertiesValues = ObjectValues(properties);
|
||||
for (let i = 0; i < propertiesValues.length; i++) {
|
||||
// We want to use null-prototype objects to not rely on globally mutable
|
||||
// %Object.prototype%.
|
||||
ObjectSetPrototypeOf(propertiesValues[i], null);
|
||||
}
|
||||
ObjectDefineProperties(inst, properties);
|
||||
ObjectSetPrototypeOf(inst, ObjectGetPrototypeOf(obj));
|
||||
return inst;
|
||||
/**
|
||||
* Mark an object as being transferable or customized cloneable in
|
||||
* `.postMessage()`.
|
||||
* This should only applied to host objects like Web API interfaces, Node.js'
|
||||
* built-in objects.
|
||||
* Objects marked as cloneable and transferable should implement the method
|
||||
* `@@kClone` and `@@kTransfer` respectively. Method `@@kDeserialize` is
|
||||
* required to deserialize the data to a new instance.
|
||||
*
|
||||
* Example implementation of a cloneable interface (assuming its located in
|
||||
* `internal/my_interface.js`):
|
||||
*
|
||||
* ```
|
||||
* class MyInterface {
|
||||
* constructor(...args) {
|
||||
* markTransferMode(this, true);
|
||||
* this.args = args;
|
||||
* }
|
||||
* [kDeserialize](data) {
|
||||
* this.args = data.args;
|
||||
* }
|
||||
* [kClone]() {
|
||||
* return {
|
||||
* data: { args: this.args },
|
||||
* deserializeInfo: 'internal/my_interface:MyInterface',
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* module.exports = {
|
||||
* MyInterface,
|
||||
* };
|
||||
* ```
|
||||
* @param {object} obj Host objects that can be either cloned or transferred.
|
||||
* @param {boolean} [cloneable] if the object can be cloned and `@@kClone` is
|
||||
* implemented.
|
||||
* @param {boolean} [transferable] if the object can be transferred and
|
||||
* `@@kTransfer` is implemented.
|
||||
*/
|
||||
function markTransferMode(obj, cloneable = false, transferable = false) {
|
||||
if ((typeof obj !== 'object' && typeof obj !== 'function') || obj === null)
|
||||
return; // This object is a primitive and therefore already untransferable.
|
||||
let mode = kDisallowCloneAndTransfer;
|
||||
if (cloneable) mode |= kCloneable;
|
||||
if (transferable) mode |= kTransferable;
|
||||
obj[transfer_mode_private_symbol] = mode;
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
makeTransferable,
|
||||
markTransferMode,
|
||||
setup,
|
||||
JSTransferable,
|
||||
kClone: messaging_clone_symbol,
|
||||
kDeserialize: messaging_deserialize_symbol,
|
||||
kTransfer: messaging_transfer_symbol,
|
||||
|
@ -1,15 +1,20 @@
|
||||
#include "base_object.h"
|
||||
#include "env-inl.h"
|
||||
#include "node_messaging.h"
|
||||
#include "node_realm-inl.h"
|
||||
|
||||
namespace node {
|
||||
|
||||
using v8::Context;
|
||||
using v8::FunctionCallbackInfo;
|
||||
using v8::FunctionTemplate;
|
||||
using v8::HandleScope;
|
||||
using v8::Just;
|
||||
using v8::Local;
|
||||
using v8::Maybe;
|
||||
using v8::Object;
|
||||
using v8::Value;
|
||||
using v8::ValueDeserializer;
|
||||
using v8::WeakCallbackInfo;
|
||||
using v8::WeakCallbackType;
|
||||
|
||||
@ -93,6 +98,28 @@ Local<FunctionTemplate> BaseObject::MakeLazilyInitializedJSTemplate(
|
||||
return t;
|
||||
}
|
||||
|
||||
BaseObject::TransferMode BaseObject::GetTransferMode() const {
|
||||
return TransferMode::kDisallowCloneAndTransfer;
|
||||
}
|
||||
|
||||
std::unique_ptr<worker::TransferData> BaseObject::TransferForMessaging() {
|
||||
return {};
|
||||
}
|
||||
|
||||
std::unique_ptr<worker::TransferData> BaseObject::CloneForMessaging() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
Maybe<std::vector<BaseObjectPtr<BaseObject>>> BaseObject::NestedTransferables()
|
||||
const {
|
||||
return Just(std::vector<BaseObjectPtr<BaseObject>>{});
|
||||
}
|
||||
|
||||
Maybe<bool> BaseObject::FinalizeTransferRead(Local<Context> context,
|
||||
ValueDeserializer* deserializer) {
|
||||
return Just(true);
|
||||
}
|
||||
|
||||
BaseObject::PointerData* BaseObject::pointer_data() {
|
||||
if (!has_pointer_data()) {
|
||||
PointerData* metadata = new PointerData();
|
||||
|
@ -135,11 +135,11 @@ class BaseObject : public MemoryRetainer {
|
||||
// method of MessagePorts (and, by extension, Workers).
|
||||
// GetTransferMode() returns a transfer mode that indicates how to deal with
|
||||
// the current object:
|
||||
// - kUntransferable:
|
||||
// No transfer is possible, either because this type of BaseObject does
|
||||
// not know how to be transferred, or because it is not in a state in
|
||||
// which it is possible to do so (e.g. because it has already been
|
||||
// transferred).
|
||||
// - kDisallowCloneAndTransfer:
|
||||
// No transfer or clone is possible, either because this type of
|
||||
// BaseObject does not know how to be transferred, or because it is not
|
||||
// in a state in which it is possible to do so (e.g. because it has
|
||||
// already been transferred).
|
||||
// - kTransferable:
|
||||
// This object can be transferred in a destructive fashion, i.e. will be
|
||||
// rendered unusable on the sending side of the channel in the process
|
||||
@ -155,15 +155,20 @@ class BaseObject : public MemoryRetainer {
|
||||
// This object can be cloned without being modified.
|
||||
// CloneForMessaging() will be called to get a representation of the
|
||||
// object that is used for subsequent deserialization, unless the
|
||||
// object is listed in transferList, in which case TransferForMessaging()
|
||||
// is attempted first.
|
||||
// object is listed in transferList and is kTransferable, in which case
|
||||
// TransferForMessaging() is attempted first.
|
||||
// - kTransferableAndCloneable:
|
||||
// This object can be transferred or cloned.
|
||||
// After a successful clone, FinalizeTransferRead() is called on the receiving
|
||||
// end, and can read deserialize JS data possibly serialized by a previous
|
||||
// FinalizeTransferWrite() call.
|
||||
enum class TransferMode {
|
||||
kUntransferable,
|
||||
kTransferable,
|
||||
kCloneable
|
||||
// By default, a BaseObject is kDisallowCloneAndTransfer and a JS Object is
|
||||
// kCloneable unless otherwise specified.
|
||||
enum TransferMode : uint32_t {
|
||||
kDisallowCloneAndTransfer = 0,
|
||||
kTransferable = 1 << 0,
|
||||
kCloneable = 1 << 1,
|
||||
kTransferableAndCloneable = kTransferable | kCloneable,
|
||||
};
|
||||
virtual TransferMode GetTransferMode() const;
|
||||
virtual std::unique_ptr<worker::TransferData> TransferForMessaging();
|
||||
|
@ -493,7 +493,6 @@ X509Certificate::X509CertificateTransferData::Deserialize(
|
||||
Unwrap<X509Certificate>(handle.As<Object>()));
|
||||
}
|
||||
|
||||
|
||||
BaseObject::TransferMode X509Certificate::GetTransferMode() const {
|
||||
return BaseObject::TransferMode::kCloneable;
|
||||
}
|
||||
|
@ -21,6 +21,8 @@
|
||||
V(arrow_message_private_symbol, "node:arrowMessage") \
|
||||
V(contextify_context_private_symbol, "node:contextify:context") \
|
||||
V(decorated_private_symbol, "node:decorated") \
|
||||
V(transfer_mode_private_symbol, "node:transfer_mode") \
|
||||
V(js_transferable_wrapper_private_symbol, "node:js_transferable_wrapper") \
|
||||
V(napi_type_tag, "node:napi:type_tag") \
|
||||
V(napi_wrapper, "node:napi:wrapper") \
|
||||
V(untransferable_object_private_symbol, "node:untransferableObject") \
|
||||
@ -357,6 +359,7 @@
|
||||
V(http2ping_constructor_template, v8::ObjectTemplate) \
|
||||
V(i18n_converter_template, v8::ObjectTemplate) \
|
||||
V(intervalhistogram_constructor_template, v8::FunctionTemplate) \
|
||||
V(js_transferable_constructor_template, v8::FunctionTemplate) \
|
||||
V(libuv_stream_wrap_ctor_template, v8::FunctionTemplate) \
|
||||
V(message_port_constructor_template, v8::FunctionTemplate) \
|
||||
V(microtask_queue_ctor_template, v8::FunctionTemplate) \
|
||||
|
@ -137,7 +137,7 @@ class HistogramBase : public BaseObject, public HistogramImpl {
|
||||
v8::Local<v8::Object> wrap,
|
||||
std::shared_ptr<Histogram> histogram);
|
||||
|
||||
TransferMode GetTransferMode() const override {
|
||||
BaseObject::TransferMode GetTransferMode() const override {
|
||||
return TransferMode::kCloneable;
|
||||
}
|
||||
std::unique_ptr<worker::TransferData> CloneForMessaging() const override;
|
||||
@ -213,7 +213,7 @@ class IntervalHistogram : public HandleWrap, public HistogramImpl {
|
||||
static void Start(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static void Stop(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
|
||||
TransferMode GetTransferMode() const override {
|
||||
BaseObject::TransferMode GetTransferMode() const override {
|
||||
return TransferMode::kCloneable;
|
||||
}
|
||||
std::unique_ptr<worker::TransferData> CloneForMessaging() const override;
|
||||
|
@ -392,7 +392,7 @@ Blob::BlobTransferData::Deserialize(
|
||||
}
|
||||
|
||||
BaseObject::TransferMode Blob::GetTransferMode() const {
|
||||
return BaseObject::TransferMode::kCloneable;
|
||||
return TransferMode::kCloneable;
|
||||
}
|
||||
|
||||
std::unique_ptr<worker::TransferData> Blob::CloneForMessaging() const {
|
||||
|
@ -300,13 +300,14 @@ void FileHandle::MemoryInfo(MemoryTracker* tracker) const {
|
||||
tracker->TrackField("current_read", current_read_);
|
||||
}
|
||||
|
||||
FileHandle::TransferMode FileHandle::GetTransferMode() const {
|
||||
return reading_ || closing_ || closed_ ?
|
||||
TransferMode::kUntransferable : TransferMode::kTransferable;
|
||||
BaseObject::TransferMode FileHandle::GetTransferMode() const {
|
||||
return reading_ || closing_ || closed_
|
||||
? TransferMode::kDisallowCloneAndTransfer
|
||||
: TransferMode::kTransferable;
|
||||
}
|
||||
|
||||
std::unique_ptr<worker::TransferData> FileHandle::TransferForMessaging() {
|
||||
CHECK_NE(GetTransferMode(), TransferMode::kUntransferable);
|
||||
CHECK_NE(GetTransferMode(), TransferMode::kDisallowCloneAndTransfer);
|
||||
auto ret = std::make_unique<TransferData>(fd_);
|
||||
closed_ = true;
|
||||
return ret;
|
||||
|
@ -381,7 +381,7 @@ class FileHandle final : public AsyncWrap, public StreamBase {
|
||||
FileHandle(const FileHandle&&) = delete;
|
||||
FileHandle& operator=(const FileHandle&&) = delete;
|
||||
|
||||
TransferMode GetTransferMode() const override;
|
||||
BaseObject::TransferMode GetTransferMode() const override;
|
||||
std::unique_ptr<worker::TransferData> TransferForMessaging() override;
|
||||
|
||||
private:
|
||||
|
@ -42,32 +42,12 @@ using v8::WasmModuleObject;
|
||||
namespace node {
|
||||
|
||||
using BaseObjectList = std::vector<BaseObjectPtr<BaseObject>>;
|
||||
using TransferMode = BaseObject::TransferMode;
|
||||
|
||||
// Hack to have WriteHostObject inform ReadHostObject that the value
|
||||
// should be treated as a regular JS object. Used to transfer process.env.
|
||||
static const uint32_t kNormalObject = static_cast<uint32_t>(-1);
|
||||
|
||||
BaseObject::TransferMode BaseObject::GetTransferMode() const {
|
||||
return BaseObject::TransferMode::kUntransferable;
|
||||
}
|
||||
|
||||
std::unique_ptr<worker::TransferData> BaseObject::TransferForMessaging() {
|
||||
return CloneForMessaging();
|
||||
}
|
||||
|
||||
std::unique_ptr<worker::TransferData> BaseObject::CloneForMessaging() const {
|
||||
return {};
|
||||
}
|
||||
|
||||
Maybe<BaseObjectList> BaseObject::NestedTransferables() const {
|
||||
return Just(BaseObjectList {});
|
||||
}
|
||||
|
||||
Maybe<bool> BaseObject::FinalizeTransferRead(
|
||||
Local<Context> context, ValueDeserializer* deserializer) {
|
||||
return Just(true);
|
||||
}
|
||||
|
||||
namespace worker {
|
||||
|
||||
Maybe<bool> TransferData::FinalizeTransferWrite(
|
||||
@ -95,7 +75,8 @@ class DeserializerDelegate : public ValueDeserializer::Delegate {
|
||||
const std::vector<Local<SharedArrayBuffer>>& shared_array_buffers,
|
||||
const std::vector<CompiledWasmModule>& wasm_modules,
|
||||
const std::optional<SharedValueConveyor>& shared_value_conveyor)
|
||||
: host_objects_(host_objects),
|
||||
: env_(env),
|
||||
host_objects_(host_objects),
|
||||
shared_array_buffers_(shared_array_buffers),
|
||||
wasm_modules_(wasm_modules),
|
||||
shared_value_conveyor_(shared_value_conveyor) {}
|
||||
@ -107,7 +88,12 @@ class DeserializerDelegate : public ValueDeserializer::Delegate {
|
||||
return MaybeLocal<Object>();
|
||||
if (id != kNormalObject) {
|
||||
CHECK_LT(id, host_objects_.size());
|
||||
return host_objects_[id]->object(isolate);
|
||||
Local<Object> object = host_objects_[id]->object(isolate);
|
||||
if (env_->js_transferable_constructor_template()->HasInstance(object)) {
|
||||
return Unwrap<JSTransferable>(object)->target();
|
||||
} else {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
EscapableHandleScope scope(isolate);
|
||||
Local<Context> context = isolate->GetCurrentContext();
|
||||
@ -139,6 +125,7 @@ class DeserializerDelegate : public ValueDeserializer::Delegate {
|
||||
ValueDeserializer* deserializer = nullptr;
|
||||
|
||||
private:
|
||||
Environment* env_;
|
||||
const std::vector<BaseObjectPtr<BaseObject>>& host_objects_;
|
||||
const std::vector<Local<SharedArrayBuffer>>& shared_array_buffers_;
|
||||
const std::vector<CompiledWasmModule>& wasm_modules_;
|
||||
@ -317,12 +304,27 @@ class SerializerDelegate : public ValueSerializer::Delegate {
|
||||
ThrowDataCloneException(context_, message);
|
||||
}
|
||||
|
||||
bool HasCustomHostObject(Isolate* isolate) override { return true; }
|
||||
|
||||
Maybe<bool> IsHostObject(Isolate* isolate, Local<Object> object) override {
|
||||
if (BaseObject::IsBaseObject(object)) {
|
||||
return Just(true);
|
||||
}
|
||||
|
||||
return Just(JSTransferable::IsJSTransferable(env_, context_, object));
|
||||
}
|
||||
|
||||
Maybe<bool> WriteHostObject(Isolate* isolate, Local<Object> object) override {
|
||||
if (BaseObject::IsBaseObject(object)) {
|
||||
return WriteHostObject(
|
||||
BaseObjectPtr<BaseObject> { Unwrap<BaseObject>(object) });
|
||||
}
|
||||
|
||||
if (JSTransferable::IsJSTransferable(env_, context_, object)) {
|
||||
JSTransferable* js_transferable = JSTransferable::Wrap(env_, object);
|
||||
return WriteHostObject(BaseObjectPtr<BaseObject>{js_transferable});
|
||||
}
|
||||
|
||||
// Convert process.env to a regular object.
|
||||
auto env_proxy_ctor_template = env_->env_proxy_ctor_template();
|
||||
if (!env_proxy_ctor_template.IsEmpty() &&
|
||||
@ -374,10 +376,11 @@ class SerializerDelegate : public ValueSerializer::Delegate {
|
||||
for (uint32_t i = 0; i < host_objects_.size(); i++) {
|
||||
BaseObjectPtr<BaseObject> host_object = std::move(host_objects_[i]);
|
||||
std::unique_ptr<TransferData> data;
|
||||
if (i < first_cloned_object_index_)
|
||||
if (i < first_cloned_object_index_) {
|
||||
data = host_object->TransferForMessaging();
|
||||
if (!data)
|
||||
} else {
|
||||
data = host_object->CloneForMessaging();
|
||||
}
|
||||
if (!data) return Nothing<bool>();
|
||||
if (data->FinalizeTransferWrite(context, serializer).IsNothing())
|
||||
return Nothing<bool>();
|
||||
@ -416,24 +419,22 @@ class SerializerDelegate : public ValueSerializer::Delegate {
|
||||
private:
|
||||
Maybe<bool> WriteHostObject(BaseObjectPtr<BaseObject> host_object) {
|
||||
BaseObject::TransferMode mode = host_object->GetTransferMode();
|
||||
if (mode == BaseObject::TransferMode::kUntransferable) {
|
||||
if (mode == TransferMode::kDisallowCloneAndTransfer) {
|
||||
ThrowDataCloneError(env_->clone_unsupported_type_str());
|
||||
return Nothing<bool>();
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < host_objects_.size(); i++) {
|
||||
if (host_objects_[i] == host_object) {
|
||||
serializer->WriteUint32(i);
|
||||
return Just(true);
|
||||
if (mode & TransferMode::kTransferable) {
|
||||
for (uint32_t i = 0; i < host_objects_.size(); i++) {
|
||||
if (host_objects_[i] == host_object) {
|
||||
serializer->WriteUint32(i);
|
||||
return Just(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mode == BaseObject::TransferMode::kTransferable) {
|
||||
THROW_ERR_MISSING_TRANSFERABLE_IN_TRANSFER_LIST(env_);
|
||||
return Nothing<bool>();
|
||||
}
|
||||
|
||||
CHECK_EQ(mode, BaseObject::TransferMode::kCloneable);
|
||||
uint32_t index = host_objects_.size();
|
||||
if (first_cloned_object_index_ == SIZE_MAX)
|
||||
first_cloned_object_index_ = index;
|
||||
@ -471,21 +472,19 @@ Maybe<bool> Message::Serialize(Environment* env,
|
||||
|
||||
std::vector<Local<ArrayBuffer>> array_buffers;
|
||||
for (uint32_t i = 0; i < transfer_list_v.length(); ++i) {
|
||||
Local<Value> entry = transfer_list_v[i];
|
||||
if (entry->IsObject()) {
|
||||
// See https://github.com/nodejs/node/pull/30339#issuecomment-552225353
|
||||
// for details.
|
||||
bool untransferable;
|
||||
if (!entry.As<Object>()->HasPrivate(
|
||||
context,
|
||||
env->untransferable_object_private_symbol())
|
||||
.To(&untransferable)) {
|
||||
return Nothing<bool>();
|
||||
}
|
||||
if (untransferable) {
|
||||
ThrowDataCloneException(context, env->transfer_unsupported_type_str());
|
||||
return Nothing<bool>();
|
||||
}
|
||||
Local<Value> entry_val = transfer_list_v[i];
|
||||
if (!entry_val->IsObject()) {
|
||||
// Only object can be transferred.
|
||||
THROW_ERR_INVALID_TRANSFER_OBJECT(env);
|
||||
return Nothing<bool>();
|
||||
}
|
||||
Local<Object> entry = entry_val.As<Object>();
|
||||
// See https://github.com/nodejs/node/pull/30339#issuecomment-552225353
|
||||
// for details.
|
||||
if (entry->HasPrivate(context, env->untransferable_object_private_symbol())
|
||||
.ToChecked()) {
|
||||
ThrowDataCloneException(context, env->transfer_unsupported_type_str());
|
||||
return Nothing<bool>();
|
||||
}
|
||||
|
||||
// Currently, we support ArrayBuffers and BaseObjects for which
|
||||
@ -519,49 +518,55 @@ Maybe<bool> Message::Serialize(Environment* env,
|
||||
array_buffers.push_back(ab);
|
||||
serializer.TransferArrayBuffer(id, ab);
|
||||
continue;
|
||||
} else if (entry->IsObject() &&
|
||||
BaseObject::IsBaseObject(entry.As<Object>())) {
|
||||
// Check if the source MessagePort is being transferred.
|
||||
if (!source_port.IsEmpty() && entry == source_port) {
|
||||
ThrowDataCloneException(
|
||||
context,
|
||||
FIXED_ONE_BYTE_STRING(env->isolate(),
|
||||
"Transfer list contains source port"));
|
||||
return Nothing<bool>();
|
||||
}
|
||||
BaseObjectPtr<BaseObject> host_object {
|
||||
Unwrap<BaseObject>(entry.As<Object>()) };
|
||||
if (env->message_port_constructor_template()->HasInstance(entry) &&
|
||||
(!host_object ||
|
||||
static_cast<MessagePort*>(host_object.get())->IsDetached())) {
|
||||
ThrowDataCloneException(
|
||||
context,
|
||||
FIXED_ONE_BYTE_STRING(
|
||||
env->isolate(),
|
||||
"MessagePort in transfer list is already detached"));
|
||||
return Nothing<bool>();
|
||||
}
|
||||
if (std::find(delegate.host_objects_.begin(),
|
||||
delegate.host_objects_.end(),
|
||||
host_object) != delegate.host_objects_.end()) {
|
||||
ThrowDataCloneException(
|
||||
context,
|
||||
String::Concat(env->isolate(),
|
||||
FIXED_ONE_BYTE_STRING(
|
||||
env->isolate(),
|
||||
"Transfer list contains duplicate "),
|
||||
entry.As<Object>()->GetConstructorName()));
|
||||
return Nothing<bool>();
|
||||
}
|
||||
if (host_object && host_object->GetTransferMode() ==
|
||||
BaseObject::TransferMode::kTransferable) {
|
||||
delegate.AddHostObject(host_object);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
THROW_ERR_INVALID_TRANSFER_OBJECT(env);
|
||||
return Nothing<bool>();
|
||||
// Check if the source MessagePort is being transferred.
|
||||
if (!source_port.IsEmpty() && entry == source_port) {
|
||||
ThrowDataCloneException(
|
||||
context,
|
||||
FIXED_ONE_BYTE_STRING(env->isolate(),
|
||||
"Transfer list contains source port"));
|
||||
return Nothing<bool>();
|
||||
}
|
||||
BaseObjectPtr<BaseObject> host_object;
|
||||
if (BaseObject::IsBaseObject(entry)) {
|
||||
host_object = BaseObjectPtr<BaseObject>{Unwrap<BaseObject>(entry)};
|
||||
} else {
|
||||
if (!JSTransferable::IsJSTransferable(env, context, entry)) {
|
||||
THROW_ERR_INVALID_TRANSFER_OBJECT(env);
|
||||
return Nothing<bool>();
|
||||
}
|
||||
JSTransferable* js_transferable = JSTransferable::Wrap(env, entry);
|
||||
host_object = BaseObjectPtr<BaseObject>{js_transferable};
|
||||
}
|
||||
|
||||
if (env->message_port_constructor_template()->HasInstance(entry) &&
|
||||
(!host_object ||
|
||||
static_cast<MessagePort*>(host_object.get())->IsDetached())) {
|
||||
ThrowDataCloneException(
|
||||
context,
|
||||
FIXED_ONE_BYTE_STRING(
|
||||
env->isolate(),
|
||||
"MessagePort in transfer list is already detached"));
|
||||
return Nothing<bool>();
|
||||
}
|
||||
if (std::find(delegate.host_objects_.begin(),
|
||||
delegate.host_objects_.end(),
|
||||
host_object) != delegate.host_objects_.end()) {
|
||||
ThrowDataCloneException(
|
||||
context,
|
||||
String::Concat(
|
||||
env->isolate(),
|
||||
FIXED_ONE_BYTE_STRING(env->isolate(),
|
||||
"Transfer list contains duplicate "),
|
||||
entry->GetConstructorName()));
|
||||
return Nothing<bool>();
|
||||
}
|
||||
if (host_object &&
|
||||
host_object->GetTransferMode() == TransferMode::kTransferable) {
|
||||
delegate.AddHostObject(host_object);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (delegate.AddNestedHostObjects().IsNothing())
|
||||
return Nothing<bool>();
|
||||
@ -878,9 +883,8 @@ std::unique_ptr<MessagePortData> MessagePort::Detach() {
|
||||
}
|
||||
|
||||
BaseObject::TransferMode MessagePort::GetTransferMode() const {
|
||||
if (IsDetached())
|
||||
return BaseObject::TransferMode::kUntransferable;
|
||||
return BaseObject::TransferMode::kTransferable;
|
||||
if (IsDetached()) return TransferMode::kDisallowCloneAndTransfer;
|
||||
return TransferMode::kTransferable;
|
||||
}
|
||||
|
||||
std::unique_ptr<TransferData> MessagePort::TransferForMessaging() {
|
||||
@ -1185,80 +1189,112 @@ Local<FunctionTemplate> GetMessagePortConstructorTemplate(Environment* env) {
|
||||
return GetMessagePortConstructorTemplate(env);
|
||||
}
|
||||
|
||||
JSTransferable::JSTransferable(Environment* env, Local<Object> obj)
|
||||
// static
|
||||
JSTransferable* JSTransferable::Wrap(Environment* env, Local<Object> target) {
|
||||
Local<Context> context = env->context();
|
||||
Local<Value> wrapper_val =
|
||||
target->GetPrivate(context, env->js_transferable_wrapper_private_symbol())
|
||||
.ToLocalChecked();
|
||||
DCHECK(wrapper_val->IsObject() || wrapper_val->IsUndefined());
|
||||
JSTransferable* wrapper;
|
||||
if (wrapper_val->IsObject()) {
|
||||
wrapper = Unwrap<JSTransferable>(wrapper_val);
|
||||
} else {
|
||||
Local<Object> wrapper_obj = env->js_transferable_constructor_template()
|
||||
->GetFunction(context)
|
||||
.ToLocalChecked()
|
||||
->NewInstance(context)
|
||||
.ToLocalChecked();
|
||||
wrapper = new JSTransferable(env, wrapper_obj, target);
|
||||
target
|
||||
->SetPrivate(
|
||||
context, env->js_transferable_wrapper_private_symbol(), wrapper_obj)
|
||||
.ToChecked();
|
||||
}
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
// static
|
||||
bool JSTransferable::IsJSTransferable(Environment* env,
|
||||
v8::Local<v8::Context> context,
|
||||
v8::Local<v8::Object> object) {
|
||||
return object->HasPrivate(context, env->transfer_mode_private_symbol())
|
||||
.ToChecked();
|
||||
}
|
||||
|
||||
JSTransferable::JSTransferable(Environment* env,
|
||||
Local<Object> obj,
|
||||
Local<Object> target)
|
||||
: BaseObject(env, obj) {
|
||||
MakeWeak();
|
||||
target_.Reset(env->isolate(), target);
|
||||
target_.SetWeak();
|
||||
}
|
||||
|
||||
void JSTransferable::New(const FunctionCallbackInfo<Value>& args) {
|
||||
CHECK(args.IsConstructCall());
|
||||
new JSTransferable(Environment::GetCurrent(args), args.This());
|
||||
Local<Object> JSTransferable::target() const {
|
||||
return target_.Get(env()->isolate());
|
||||
}
|
||||
|
||||
JSTransferable::TransferMode JSTransferable::GetTransferMode() const {
|
||||
BaseObject::TransferMode JSTransferable::GetTransferMode() const {
|
||||
// Implement `kClone in this ? kCloneable : kTransferable`.
|
||||
HandleScope handle_scope(env()->isolate());
|
||||
errors::TryCatchScope ignore_exceptions(env());
|
||||
|
||||
bool has_clone;
|
||||
if (!object()->Has(env()->context(),
|
||||
env()->messaging_clone_symbol()).To(&has_clone)) {
|
||||
return TransferMode::kUntransferable;
|
||||
Local<Value> transfer_mode_val =
|
||||
target()
|
||||
->GetPrivate(env()->context(), env()->transfer_mode_private_symbol())
|
||||
.ToLocalChecked();
|
||||
if (!transfer_mode_val->IsUint32()) {
|
||||
return TransferMode::kDisallowCloneAndTransfer;
|
||||
}
|
||||
|
||||
return has_clone ? TransferMode::kCloneable : TransferMode::kTransferable;
|
||||
return static_cast<TransferMode>(transfer_mode_val.As<v8::Uint32>()->Value());
|
||||
}
|
||||
|
||||
std::unique_ptr<TransferData> JSTransferable::TransferForMessaging() {
|
||||
return TransferOrClone(TransferMode::kTransferable);
|
||||
return TransferOrClone<TransferMode::kTransferable>();
|
||||
}
|
||||
|
||||
std::unique_ptr<TransferData> JSTransferable::CloneForMessaging() const {
|
||||
return TransferOrClone(TransferMode::kCloneable);
|
||||
return TransferOrClone<TransferMode::kCloneable>();
|
||||
}
|
||||
|
||||
std::unique_ptr<TransferData> JSTransferable::TransferOrClone(
|
||||
TransferMode mode) const {
|
||||
template <TransferMode mode>
|
||||
std::unique_ptr<TransferData> JSTransferable::TransferOrClone() const {
|
||||
// Call `this[symbol]()` where `symbol` is `kClone` or `kTransfer`,
|
||||
// which should return an object with `data` and `deserializeInfo` properties;
|
||||
// `data` is written to the serializer later, and `deserializeInfo` is stored
|
||||
// on the `TransferData` instance as a string.
|
||||
HandleScope handle_scope(env()->isolate());
|
||||
Local<Context> context = env()->isolate()->GetCurrentContext();
|
||||
Local<Symbol> method_name = mode == TransferMode::kCloneable ?
|
||||
env()->messaging_clone_symbol() : env()->messaging_transfer_symbol();
|
||||
Local<Symbol> method_name = mode == TransferMode::kCloneable
|
||||
? env()->messaging_clone_symbol()
|
||||
: env()->messaging_transfer_symbol();
|
||||
|
||||
Local<Value> method;
|
||||
if (!object()->Get(context, method_name).ToLocal(&method)) {
|
||||
if (!target()->Get(context, method_name).ToLocal(&method) ||
|
||||
!method->IsFunction()) {
|
||||
return {};
|
||||
}
|
||||
if (method->IsFunction()) {
|
||||
Local<Value> result_v;
|
||||
if (!method.As<Function>()->Call(
|
||||
context, object(), 0, nullptr).ToLocal(&result_v)) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (result_v->IsObject()) {
|
||||
Local<Object> result = result_v.As<Object>();
|
||||
Local<Value> data;
|
||||
Local<Value> deserialize_info;
|
||||
if (!result->Get(context, env()->data_string()).ToLocal(&data) ||
|
||||
!result->Get(context, env()->deserialize_info_string())
|
||||
.ToLocal(&deserialize_info)) {
|
||||
return {};
|
||||
}
|
||||
Utf8Value deserialize_info_str(env()->isolate(), deserialize_info);
|
||||
if (*deserialize_info_str == nullptr) return {};
|
||||
return std::make_unique<Data>(
|
||||
*deserialize_info_str, Global<Value>(env()->isolate(), data));
|
||||
}
|
||||
Local<Value> result_v;
|
||||
if (!method.As<Function>()
|
||||
->Call(context, target(), 0, nullptr)
|
||||
.ToLocal(&result_v) ||
|
||||
!result_v->IsObject()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
if (mode == TransferMode::kTransferable)
|
||||
return TransferOrClone(TransferMode::kCloneable);
|
||||
else
|
||||
Local<Object> result = result_v.As<Object>();
|
||||
Local<Value> data;
|
||||
Local<Value> deserialize_info;
|
||||
if (!result->Get(context, env()->data_string()).ToLocal(&data) ||
|
||||
!result->Get(context, env()->deserialize_info_string())
|
||||
.ToLocal(&deserialize_info)) {
|
||||
return {};
|
||||
}
|
||||
Utf8Value deserialize_info_str(env()->isolate(), deserialize_info);
|
||||
if (*deserialize_info_str == nullptr) return {};
|
||||
return std::make_unique<Data>(*deserialize_info_str,
|
||||
Global<Value>(env()->isolate(), data));
|
||||
}
|
||||
|
||||
Maybe<BaseObjectList>
|
||||
@ -1269,14 +1305,15 @@ JSTransferable::NestedTransferables() const {
|
||||
Local<Symbol> method_name = env()->messaging_transfer_list_symbol();
|
||||
|
||||
Local<Value> method;
|
||||
if (!object()->Get(context, method_name).ToLocal(&method)) {
|
||||
if (!target()->Get(context, method_name).ToLocal(&method)) {
|
||||
return Nothing<BaseObjectList>();
|
||||
}
|
||||
if (!method->IsFunction()) return Just(BaseObjectList {});
|
||||
|
||||
Local<Value> list_v;
|
||||
if (!method.As<Function>()->Call(
|
||||
context, object(), 0, nullptr).ToLocal(&list_v)) {
|
||||
if (!method.As<Function>()
|
||||
->Call(context, target(), 0, nullptr)
|
||||
.ToLocal(&list_v)) {
|
||||
return Nothing<BaseObjectList>();
|
||||
}
|
||||
if (!list_v->IsArray()) return Just(BaseObjectList {});
|
||||
@ -1287,8 +1324,19 @@ JSTransferable::NestedTransferables() const {
|
||||
Local<Value> value;
|
||||
if (!list->Get(context, i).ToLocal(&value))
|
||||
return Nothing<BaseObjectList>();
|
||||
if (value->IsObject() && BaseObject::IsBaseObject(value.As<Object>()))
|
||||
ret.emplace_back(Unwrap<BaseObject>(value));
|
||||
if (!value->IsObject()) {
|
||||
continue;
|
||||
}
|
||||
Local<Object> obj = value.As<Object>();
|
||||
if (BaseObject::IsBaseObject(obj)) {
|
||||
ret.emplace_back(Unwrap<BaseObject>(obj));
|
||||
continue;
|
||||
}
|
||||
if (!JSTransferable::IsJSTransferable(env(), context, obj)) {
|
||||
continue;
|
||||
}
|
||||
JSTransferable* js_transferable = JSTransferable::Wrap(env(), obj);
|
||||
ret.emplace_back(js_transferable);
|
||||
}
|
||||
return Just(ret);
|
||||
}
|
||||
@ -1303,12 +1351,12 @@ Maybe<bool> JSTransferable::FinalizeTransferRead(
|
||||
|
||||
Local<Symbol> method_name = env()->messaging_deserialize_symbol();
|
||||
Local<Value> method;
|
||||
if (!object()->Get(context, method_name).ToLocal(&method)) {
|
||||
if (!target()->Get(context, method_name).ToLocal(&method)) {
|
||||
return Nothing<bool>();
|
||||
}
|
||||
if (!method->IsFunction()) return Just(true);
|
||||
|
||||
if (method.As<Function>()->Call(context, object(), 1, &data).IsEmpty()) {
|
||||
if (method.As<Function>()->Call(context, target(), 1, &data).IsEmpty()) {
|
||||
return Nothing<bool>();
|
||||
}
|
||||
return Just(true);
|
||||
@ -1342,11 +1390,15 @@ BaseObjectPtr<BaseObject> JSTransferable::Data::Deserialize(
|
||||
if (!env->messaging_deserialize_create_object()
|
||||
->Call(context, Null(env->isolate()), 1, &info)
|
||||
.ToLocal(&ret) ||
|
||||
!ret->IsObject() || !BaseObject::IsBaseObject(ret.As<Object>())) {
|
||||
!ret->IsObject()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return BaseObjectPtr<BaseObject> { Unwrap<BaseObject>(ret) };
|
||||
if (!JSTransferable::IsJSTransferable(env, context, ret.As<Object>())) {
|
||||
return {};
|
||||
}
|
||||
JSTransferable* js_transferable = JSTransferable::Wrap(env, ret.As<Object>());
|
||||
return BaseObjectPtr<BaseObject>{js_transferable};
|
||||
}
|
||||
|
||||
Maybe<bool> JSTransferable::Data::FinalizeTransferWrite(
|
||||
@ -1521,13 +1573,11 @@ static void InitMessaging(Local<Object> target,
|
||||
}
|
||||
|
||||
{
|
||||
Local<FunctionTemplate> t =
|
||||
NewFunctionTemplate(isolate, JSTransferable::New);
|
||||
Local<FunctionTemplate> t = FunctionTemplate::New(isolate);
|
||||
t->InstanceTemplate()->SetInternalFieldCount(
|
||||
JSTransferable::kInternalFieldCount);
|
||||
t->SetClassName(OneByteString(isolate, "JSTransferable"));
|
||||
SetConstructorFunction(
|
||||
context, target, "JSTransferable", t, SetConstructorFunctionFlag::NONE);
|
||||
env->isolate_data()->set_js_transferable_constructor_template(t);
|
||||
}
|
||||
|
||||
SetConstructorFunction(context,
|
||||
@ -1564,7 +1614,6 @@ static void InitMessaging(Local<Object> target,
|
||||
static void RegisterExternalReferences(ExternalReferenceRegistry* registry) {
|
||||
registry->Register(MessageChannel);
|
||||
registry->Register(BroadcastChannel);
|
||||
registry->Register(JSTransferable::New);
|
||||
registry->Register(MessagePort::New);
|
||||
registry->Register(MessagePort::PostMessage);
|
||||
registry->Register(MessagePort::Start);
|
||||
|
@ -290,7 +290,7 @@ class MessagePort : public HandleWrap {
|
||||
// NULL pointer to the C++ MessagePort object is also detached.
|
||||
inline bool IsDetached() const;
|
||||
|
||||
TransferMode GetTransferMode() const override;
|
||||
BaseObject::TransferMode GetTransferMode() const override;
|
||||
std::unique_ptr<TransferData> TransferForMessaging() override;
|
||||
|
||||
void MemoryInfo(MemoryTracker* tracker) const override;
|
||||
@ -319,15 +319,17 @@ class MessagePort : public HandleWrap {
|
||||
friend class MessagePortData;
|
||||
};
|
||||
|
||||
// Provide a base class from which JS classes that should be transferable or
|
||||
// cloneable by postMessage() can inherit.
|
||||
// Provide a wrapper class created when a built-in JS classes that being
|
||||
// transferable or cloneable by postMessage().
|
||||
// See e.g. FileHandle in internal/fs/promises.js for an example.
|
||||
class JSTransferable : public BaseObject {
|
||||
public:
|
||||
JSTransferable(Environment* env, v8::Local<v8::Object> obj);
|
||||
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
|
||||
static JSTransferable* Wrap(Environment* env, v8::Local<v8::Object> target);
|
||||
static bool IsJSTransferable(Environment* env,
|
||||
v8::Local<v8::Context> context,
|
||||
v8::Local<v8::Object> object);
|
||||
|
||||
TransferMode GetTransferMode() const override;
|
||||
BaseObject::TransferMode GetTransferMode() const override;
|
||||
std::unique_ptr<TransferData> TransferForMessaging() override;
|
||||
std::unique_ptr<TransferData> CloneForMessaging() const override;
|
||||
v8::Maybe<std::vector<BaseObjectPtr<BaseObject>>>
|
||||
@ -340,8 +342,17 @@ class JSTransferable : public BaseObject {
|
||||
SET_MEMORY_INFO_NAME(JSTransferable)
|
||||
SET_SELF_SIZE(JSTransferable)
|
||||
|
||||
v8::Local<v8::Object> target() const;
|
||||
|
||||
private:
|
||||
std::unique_ptr<TransferData> TransferOrClone(TransferMode mode) const;
|
||||
JSTransferable(Environment* env,
|
||||
v8::Local<v8::Object> obj,
|
||||
v8::Local<v8::Object> target);
|
||||
|
||||
template <TransferMode mode>
|
||||
std::unique_ptr<TransferData> TransferOrClone() const;
|
||||
|
||||
v8::Global<v8::Object> target_;
|
||||
|
||||
class Data : public TransferData {
|
||||
public:
|
||||
|
@ -176,7 +176,7 @@ class SocketAddressBase : public BaseObject {
|
||||
SET_MEMORY_INFO_NAME(SocketAddressBase)
|
||||
SET_SELF_SIZE(SocketAddressBase)
|
||||
|
||||
TransferMode GetTransferMode() const override {
|
||||
BaseObject::TransferMode GetTransferMode() const override {
|
||||
return TransferMode::kCloneable;
|
||||
}
|
||||
std::unique_ptr<worker::TransferData> CloneForMessaging() const override;
|
||||
@ -367,7 +367,7 @@ class SocketAddressBlockListWrap : public BaseObject {
|
||||
SET_MEMORY_INFO_NAME(SocketAddressBlockListWrap)
|
||||
SET_SELF_SIZE(SocketAddressBlockListWrap)
|
||||
|
||||
TransferMode GetTransferMode() const override {
|
||||
BaseObject::TransferMode GetTransferMode() const override {
|
||||
return TransferMode::kCloneable;
|
||||
}
|
||||
std::unique_ptr<worker::TransferData> CloneForMessaging() const override;
|
||||
|
@ -474,6 +474,20 @@ void Initialize(Local<Object> target,
|
||||
V(SKIP_SYMBOLS);
|
||||
#undef V
|
||||
|
||||
#define V(name) \
|
||||
constants \
|
||||
->Set( \
|
||||
context, \
|
||||
FIXED_ONE_BYTE_STRING(isolate, #name), \
|
||||
Integer::New(isolate, \
|
||||
static_cast<int32_t>(BaseObject::TransferMode::name))) \
|
||||
.Check();
|
||||
|
||||
V(kDisallowCloneAndTransfer);
|
||||
V(kTransferable);
|
||||
V(kCloneable);
|
||||
#undef V
|
||||
|
||||
target->Set(context, env->constants_string(), constants).Check();
|
||||
}
|
||||
|
||||
|
@ -4,9 +4,6 @@
|
||||
const common = require('../common');
|
||||
|
||||
const assert = require('assert');
|
||||
const {
|
||||
JSTransferable,
|
||||
} = require('internal/worker/js_transferable');
|
||||
const { E, F } = require('internal/test/transfer');
|
||||
|
||||
// Tests that F is transferable even tho it does not directly,
|
||||
@ -17,7 +14,6 @@ const mc = new MessageChannel();
|
||||
mc.port1.onmessageerror = common.mustNotCall();
|
||||
|
||||
mc.port1.onmessage = common.mustCall(({ data }) => {
|
||||
assert(!(data instanceof JSTransferable));
|
||||
assert(data instanceof F);
|
||||
assert(data instanceof E);
|
||||
assert.strictEqual(data.b, 1);
|
@ -31,7 +31,7 @@ const {
|
||||
} = require('internal/webstreams/util');
|
||||
|
||||
const {
|
||||
makeTransferable,
|
||||
markTransferMode,
|
||||
kClone,
|
||||
kTransfer,
|
||||
kDeserialize,
|
||||
@ -324,7 +324,7 @@ const theData = 'hello';
|
||||
|
||||
port2.postMessage(readable, [readable]);
|
||||
|
||||
const notActuallyTransferable = makeTransferable({
|
||||
const notActuallyTransferable = {
|
||||
[kClone]() {
|
||||
return {
|
||||
data: {},
|
||||
@ -332,7 +332,8 @@ const theData = 'hello';
|
||||
};
|
||||
},
|
||||
[kDeserialize]: common.mustNotCall(),
|
||||
});
|
||||
};
|
||||
markTransferMode(notActuallyTransferable, true, false);
|
||||
|
||||
controller.enqueue(notActuallyTransferable);
|
||||
}
|
||||
@ -351,7 +352,7 @@ const theData = 'hello';
|
||||
|
||||
const writable = new WritableStream(source);
|
||||
|
||||
const notActuallyTransferable = makeTransferable({
|
||||
const notActuallyTransferable = {
|
||||
[kClone]() {
|
||||
return {
|
||||
data: {},
|
||||
@ -359,7 +360,8 @@ const theData = 'hello';
|
||||
};
|
||||
},
|
||||
[kDeserialize]: common.mustNotCall(),
|
||||
});
|
||||
};
|
||||
markTransferMode(notActuallyTransferable, true, false);
|
||||
|
||||
port1.onmessage = common.mustCall(({ data }) => {
|
||||
const writer = data.getWriter();
|
||||
|
@ -3,16 +3,16 @@
|
||||
const common = require('../common');
|
||||
const assert = require('assert');
|
||||
const {
|
||||
JSTransferable, kTransfer, kTransferList
|
||||
markTransferMode, kTransfer, kTransferList
|
||||
} = require('internal/worker/js_transferable');
|
||||
const { MessageChannel } = require('worker_threads');
|
||||
|
||||
// Transferring a JSTransferable that refers to another, untransferable, value
|
||||
// in its transfer list should not crash hard.
|
||||
|
||||
class OuterTransferable extends JSTransferable {
|
||||
class OuterTransferable {
|
||||
constructor() {
|
||||
super();
|
||||
markTransferMode(this, false, true);
|
||||
// Create a detached MessagePort at this.inner
|
||||
const c = new MessageChannel();
|
||||
this.inner = c.port1;
|
||||
|
Loading…
Reference in New Issue
Block a user