mirror of
https://github.com/oven-sh/bun
synced 2026-02-09 10:28:47 +00:00
js: fix serialization of non-transferable objects (#19351)
This commit is contained in:
@@ -2591,7 +2591,7 @@ pub const JSValue = enum(i64) {
|
||||
return Bun__JSValue__deserialize(global, bytes.ptr, bytes.len);
|
||||
}
|
||||
|
||||
extern fn Bun__serializeJSValue(global: *JSC.JSGlobalObject, value: JSValue) SerializedScriptValue.External;
|
||||
extern fn Bun__serializeJSValue(global: *JSC.JSGlobalObject, value: JSValue, forTransfer: bool) SerializedScriptValue.External;
|
||||
extern fn Bun__SerializedScriptSlice__free(*anyopaque) void;
|
||||
|
||||
pub const SerializedScriptValue = struct {
|
||||
@@ -2611,8 +2611,8 @@ pub const JSValue = enum(i64) {
|
||||
|
||||
/// Throws a JS exception and returns null if the serialization fails, otherwise returns a SerializedScriptValue.
|
||||
/// Must be freed when you are done with the bytes.
|
||||
pub inline fn serialize(this: JSValue, global: *JSGlobalObject) ?SerializedScriptValue {
|
||||
const value = Bun__serializeJSValue(global, this);
|
||||
pub inline fn serialize(this: JSValue, global: *JSGlobalObject, forTransfer: bool) ?SerializedScriptValue {
|
||||
const value = Bun__serializeJSValue(global, this, forTransfer);
|
||||
return if (value.bytes) |bytes|
|
||||
.{ .data = bytes[0..value.size], .handle = value.handle.? }
|
||||
else
|
||||
|
||||
@@ -16,14 +16,16 @@ struct SerializedValueSlice {
|
||||
};
|
||||
|
||||
/// Returns a "slice" that also contains a pointer to the SerializedScriptValue. Must be freed by the caller
|
||||
extern "C" SerializedValueSlice Bun__serializeJSValue(JSGlobalObject* globalObject, EncodedJSValue encodedValue)
|
||||
extern "C" SerializedValueSlice Bun__serializeJSValue(JSGlobalObject* globalObject, EncodedJSValue encodedValue, bool forTransferBool)
|
||||
{
|
||||
JSValue value = JSValue::decode(encodedValue);
|
||||
|
||||
Vector<JSC::Strong<JSC::JSObject>> transferList;
|
||||
Vector<RefPtr<MessagePort>> dummyPorts;
|
||||
ExceptionOr<Ref<SerializedScriptValue>> serialized = SerializedScriptValue::create(*globalObject, value, WTFMove(transferList),
|
||||
dummyPorts);
|
||||
auto forStorage = SerializationForStorage::No;
|
||||
auto context = SerializationContext::Default;
|
||||
auto forTransferEnum = forTransferBool ? SerializationForTransfer::Yes : SerializationForTransfer::No;
|
||||
ExceptionOr<Ref<SerializedScriptValue>> serialized = SerializedScriptValue::create(*globalObject, value, WTFMove(transferList), dummyPorts, forStorage, context, forTransferEnum);
|
||||
|
||||
auto& vm = JSC::getVM(globalObject);
|
||||
auto scope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
@@ -1524,8 +1524,7 @@ JSC_DEFINE_HOST_FUNCTION(functionNativeMicrotaskTrampoline,
|
||||
return JSValue::encode(jsUndefined());
|
||||
}
|
||||
|
||||
JSC_DEFINE_HOST_FUNCTION(functionStructuredClone,
|
||||
(JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
JSC_DEFINE_HOST_FUNCTION(functionStructuredClone, (JSC::JSGlobalObject * globalObject, JSC::CallFrame* callFrame))
|
||||
{
|
||||
auto& vm = JSC::getVM(globalObject);
|
||||
auto throwScope = DECLARE_THROW_SCOPE(vm);
|
||||
|
||||
@@ -887,7 +887,7 @@ public:
|
||||
WasmMemoryHandleArray& wasmMemoryHandles,
|
||||
#endif
|
||||
Vector<uint8_t>& out, SerializationContext context, ArrayBufferContentsArray& sharedBuffers,
|
||||
SerializationForStorage forStorage)
|
||||
SerializationForStorage forStorage, SerializationForTransfer forTransfer)
|
||||
{
|
||||
CloneSerializer serializer(lexicalGlobalObject, messagePorts, arrayBuffers,
|
||||
#if ENABLE(OFFSCREEN_CANVAS_IN_WORKERS)
|
||||
@@ -904,7 +904,7 @@ public:
|
||||
wasmModules,
|
||||
wasmMemoryHandles,
|
||||
#endif
|
||||
out, context, sharedBuffers, forStorage);
|
||||
out, context, sharedBuffers, forStorage, forTransfer);
|
||||
return serializer.serialize(value);
|
||||
}
|
||||
|
||||
@@ -989,7 +989,7 @@ private:
|
||||
WasmModuleArray& wasmModules,
|
||||
WasmMemoryHandleArray& wasmMemoryHandles,
|
||||
#endif
|
||||
Vector<uint8_t>& out, SerializationContext context, ArrayBufferContentsArray& sharedBuffers, SerializationForStorage forStorage)
|
||||
Vector<uint8_t>& out, SerializationContext context, ArrayBufferContentsArray& sharedBuffers, SerializationForStorage forStorage, SerializationForTransfer forTransfer)
|
||||
: CloneBase(lexicalGlobalObject)
|
||||
, m_buffer(out)
|
||||
, m_emptyIdentifier(Identifier::fromString(lexicalGlobalObject->vm(), emptyString()))
|
||||
@@ -1004,6 +1004,7 @@ private:
|
||||
, m_serializedVideoFrames(serializedVideoFrames)
|
||||
#endif
|
||||
, m_forStorage(forStorage)
|
||||
, m_forTransfer(forTransfer)
|
||||
{
|
||||
write(CurrentVersion);
|
||||
fillTransferMap(messagePorts, m_transferredMessagePorts);
|
||||
@@ -1829,7 +1830,7 @@ private:
|
||||
dummyModules,
|
||||
dummyMemoryHandles,
|
||||
#endif
|
||||
serializedKey, SerializationContext::Default, dummySharedBuffers, m_forStorage);
|
||||
serializedKey, SerializationContext::Default, dummySharedBuffers, m_forStorage, m_forTransfer);
|
||||
rawKeySerializer.write(key);
|
||||
Vector<uint8_t> wrappedKey;
|
||||
|
||||
@@ -1943,6 +1944,11 @@ private:
|
||||
|
||||
// write bun types
|
||||
if (auto _cloneable = StructuredCloneableSerialize::fromJS(value)) {
|
||||
if (m_forTransfer == SerializationForTransfer::Yes && !SerializedScriptValue::isTransferable(m_lexicalGlobalObject, value)) {
|
||||
write(ObjectTag);
|
||||
write(TerminatorTag);
|
||||
return true;
|
||||
}
|
||||
StructuredCloneableSerialize cloneable = WTFMove(_cloneable.value());
|
||||
write(cloneable.tag);
|
||||
cloneable.write(this, m_lexicalGlobalObject);
|
||||
@@ -2526,6 +2532,7 @@ private:
|
||||
Vector<RefPtr<WebCodecsVideoFrame>>& m_serializedVideoFrames;
|
||||
#endif
|
||||
SerializationForStorage m_forStorage;
|
||||
SerializationForTransfer m_forTransfer;
|
||||
};
|
||||
|
||||
SYSV_ABI void SerializedScriptValue::writeBytesForBun(CloneSerializer* ctx, const uint8_t* data, uint32_t size)
|
||||
@@ -5681,10 +5688,10 @@ static bool canDetachRTCDataChannels(const Vector<Ref<RTCDataChannel>>& channels
|
||||
}
|
||||
#endif
|
||||
|
||||
RefPtr<SerializedScriptValue> SerializedScriptValue::create(JSC::JSGlobalObject& globalObject, JSC::JSValue value, SerializationForStorage forStorage, SerializationErrorMode throwExceptions, SerializationContext serializationContext)
|
||||
RefPtr<SerializedScriptValue> SerializedScriptValue::create(JSC::JSGlobalObject& globalObject, JSC::JSValue value, SerializationForStorage forStorage, SerializationErrorMode throwExceptions, SerializationContext serializationContext, SerializationForTransfer forTransfer)
|
||||
{
|
||||
Vector<RefPtr<MessagePort>> dummyPorts;
|
||||
auto result = create(globalObject, value, {}, dummyPorts, forStorage, throwExceptions, serializationContext);
|
||||
auto result = create(globalObject, value, {}, dummyPorts, forStorage, throwExceptions, serializationContext, forTransfer);
|
||||
// auto result = create(globalObject, value, {}, forStorage, throwExceptions, serializationContext);
|
||||
if (result.hasException())
|
||||
return nullptr;
|
||||
@@ -5696,13 +5703,13 @@ RefPtr<SerializedScriptValue> SerializedScriptValue::create(JSC::JSGlobalObject&
|
||||
// return create(globalObject, value, WTFMove(transferList), messagePorts, forStorage, SerializationErrorMode::NonThrowing, serializationContext);
|
||||
// }
|
||||
|
||||
ExceptionOr<Ref<SerializedScriptValue>> SerializedScriptValue::create(JSGlobalObject& globalObject, JSValue value, Vector<JSC::Strong<JSC::JSObject>>&& transferList, Vector<RefPtr<MessagePort>>& messagePorts, SerializationForStorage forStorage, SerializationContext serializationContext)
|
||||
ExceptionOr<Ref<SerializedScriptValue>> SerializedScriptValue::create(JSGlobalObject& globalObject, JSValue value, Vector<JSC::Strong<JSC::JSObject>>&& transferList, Vector<RefPtr<MessagePort>>& messagePorts, SerializationForStorage forStorage, SerializationContext serializationContext, SerializationForTransfer forTransfer)
|
||||
{
|
||||
return create(globalObject, value, WTFMove(transferList), messagePorts, forStorage, SerializationErrorMode::Throwing, serializationContext);
|
||||
return create(globalObject, value, WTFMove(transferList), messagePorts, forStorage, SerializationErrorMode::Throwing, serializationContext, forTransfer);
|
||||
}
|
||||
|
||||
// ExceptionOr<Ref<SerializedScriptValue>> SerializedScriptValue::create(JSGlobalObject& lexicalGlobalObject, JSValue value, Vector<JSC::Strong<JSC::JSObject>>&& transferList, SerializationForStorage forStorage, SerializationErrorMode throwExceptions, SerializationContext context)
|
||||
ExceptionOr<Ref<SerializedScriptValue>> SerializedScriptValue::create(JSGlobalObject& lexicalGlobalObject, JSValue value, Vector<JSC::Strong<JSC::JSObject>>&& transferList, Vector<RefPtr<MessagePort>>& messagePorts, SerializationForStorage forStorage, SerializationErrorMode throwExceptions, SerializationContext context)
|
||||
ExceptionOr<Ref<SerializedScriptValue>> SerializedScriptValue::create(JSGlobalObject& lexicalGlobalObject, JSValue value, Vector<JSC::Strong<JSC::JSObject>>&& transferList, Vector<RefPtr<MessagePort>>& messagePorts, SerializationForStorage forStorage, SerializationErrorMode throwExceptions, SerializationContext context, SerializationForTransfer forTransfer)
|
||||
{
|
||||
VM& vm = lexicalGlobalObject.vm();
|
||||
Vector<RefPtr<JSC::ArrayBuffer>> arrayBuffers;
|
||||
@@ -5828,7 +5835,7 @@ ExceptionOr<Ref<SerializedScriptValue>> SerializedScriptValue::create(JSGlobalOb
|
||||
wasmModules,
|
||||
wasmMemoryHandles,
|
||||
#endif
|
||||
buffer, context, *sharedBuffers, forStorage);
|
||||
buffer, context, *sharedBuffers, forStorage, forTransfer);
|
||||
|
||||
// Serialize may throw an exception. This code looks weird, but we'll rethrow it
|
||||
// in maybeThrowExceptionIfSerializationFailed (since that may also throw other
|
||||
@@ -5843,8 +5850,9 @@ ExceptionOr<Ref<SerializedScriptValue>> SerializedScriptValue::create(JSGlobalOb
|
||||
return exceptionForSerializationFailure(code);
|
||||
|
||||
auto arrayBufferContentsArray = transferArrayBuffers(vm, arrayBuffers);
|
||||
if (arrayBufferContentsArray.hasException())
|
||||
if (arrayBufferContentsArray.hasException()) {
|
||||
return arrayBufferContentsArray.releaseException();
|
||||
}
|
||||
|
||||
// auto backingStores = ImageBitmap::detachBitmaps(WTFMove(imageBitmaps));
|
||||
|
||||
@@ -6056,9 +6064,9 @@ JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject,
|
||||
maybeThrowExceptionIfSerializationFailed(lexicalGlobalObject, result.second);
|
||||
|
||||
// Rethrow is a bit simpler here since we don't deal with return codes.
|
||||
RETURN_IF_EXCEPTION(scope, jsNull());
|
||||
RETURN_IF_EXCEPTION(scope, {});
|
||||
|
||||
return result.first ? result.first : jsNull();
|
||||
return result.first;
|
||||
}
|
||||
// JSValue SerializedScriptValue::deserialize(JSGlobalObject& lexicalGlobalObject, JSGlobalObject* globalObject, const Vector<String>& blobURLs, const Vector<String>& blobFilePaths, SerializationErrorMode throwExceptions, bool* didFail)
|
||||
// {
|
||||
|
||||
@@ -75,6 +75,8 @@ enum class SerializationContext { Default,
|
||||
WindowPostMessage };
|
||||
enum class SerializationForStorage : bool { No,
|
||||
Yes };
|
||||
enum class SerializationForTransfer : bool { No,
|
||||
Yes };
|
||||
|
||||
using ArrayBufferContentsArray = Vector<JSC::ArrayBufferContents>;
|
||||
#if ENABLE(WEBASSEMBLY)
|
||||
@@ -88,11 +90,12 @@ class SerializedScriptValue : public ThreadSafeRefCounted<SerializedScriptValue>
|
||||
|
||||
public:
|
||||
static SYSV_ABI void writeBytesForBun(CloneSerializer*, const uint8_t*, uint32_t);
|
||||
static SYSV_ABI bool isTransferable(JSC::JSGlobalObject* globalObject, JSC::JSValue value);
|
||||
|
||||
WEBCORE_EXPORT static ExceptionOr<Ref<SerializedScriptValue>> create(JSC::JSGlobalObject&, JSC::JSValue, Vector<JSC::Strong<JSC::JSObject>>&& transfer, Vector<RefPtr<MessagePort>>&, SerializationForStorage = SerializationForStorage::No, SerializationContext = SerializationContext::Default);
|
||||
WEBCORE_EXPORT static ExceptionOr<Ref<SerializedScriptValue>> create(JSC::JSGlobalObject&, JSC::JSValue, Vector<JSC::Strong<JSC::JSObject>>&& transfer, Vector<RefPtr<MessagePort>>&, SerializationForStorage = SerializationForStorage::No, SerializationContext = SerializationContext::Default, SerializationForTransfer = SerializationForTransfer::No);
|
||||
// WEBCORE_EXPORT static ExceptionOr<Ref<SerializedScriptValue>> create(JSC::JSGlobalObject&, JSC::JSValue, Vector<JSC::Strong<JSC::JSObject>>&& transfer, SerializationForStorage = SerializationForStorage::No, SerializationContext = SerializationContext::Default);
|
||||
|
||||
WEBCORE_EXPORT static RefPtr<SerializedScriptValue> create(JSC::JSGlobalObject&, JSC::JSValue, SerializationForStorage = SerializationForStorage::No, SerializationErrorMode = SerializationErrorMode::Throwing, SerializationContext = SerializationContext::Default);
|
||||
WEBCORE_EXPORT static RefPtr<SerializedScriptValue> create(JSC::JSGlobalObject&, JSC::JSValue, SerializationForStorage = SerializationForStorage::No, SerializationErrorMode = SerializationErrorMode::Throwing, SerializationContext = SerializationContext::Default, SerializationForTransfer = SerializationForTransfer::No);
|
||||
|
||||
static RefPtr<SerializedScriptValue> convert(JSC::JSGlobalObject& globalObject, JSC::JSValue value) { return create(globalObject, value, SerializationForStorage::Yes); }
|
||||
|
||||
@@ -148,7 +151,7 @@ private:
|
||||
// Vector<RefPtr<WebCodecsEncodedVideoChunkStorage>>&& = {}, Vector<WebCodecsVideoFrameData>&& = {}
|
||||
// #endif
|
||||
// );
|
||||
static ExceptionOr<Ref<SerializedScriptValue>> create(JSC::JSGlobalObject&, JSC::JSValue, Vector<JSC::Strong<JSC::JSObject>>&& transfer, Vector<RefPtr<MessagePort>>&, SerializationForStorage, SerializationErrorMode, SerializationContext);
|
||||
static ExceptionOr<Ref<SerializedScriptValue>> create(JSC::JSGlobalObject&, JSC::JSValue, Vector<JSC::Strong<JSC::JSObject>>&& transfer, Vector<RefPtr<MessagePort>>&, SerializationForStorage, SerializationErrorMode, SerializationContext, SerializationForTransfer);
|
||||
WEBCORE_EXPORT SerializedScriptValue(Vector<unsigned char>&&, std::unique_ptr<ArrayBufferContentsArray>&& = nullptr
|
||||
#if ENABLE(WEB_RTC)
|
||||
,
|
||||
|
||||
@@ -144,8 +144,7 @@ const advanced = struct {
|
||||
}
|
||||
|
||||
pub fn serialize(_: *IPCData, writer: anytype, global: *JSC.JSGlobalObject, value: JSValue) !usize {
|
||||
const serialized = value.serialize(global) orelse
|
||||
return IPCSerializationError.SerializationFailed;
|
||||
const serialized = value.serialize(global, true) orelse return IPCSerializationError.SerializationFailed;
|
||||
defer serialized.deinit();
|
||||
|
||||
const size: u32 = @intCast(serialized.data.len);
|
||||
@@ -162,8 +161,7 @@ const advanced = struct {
|
||||
}
|
||||
|
||||
pub fn serializeInternal(_: *IPCData, writer: anytype, global: *JSC.JSGlobalObject, value: JSValue) !usize {
|
||||
const serialized = value.serialize(global) orelse
|
||||
return IPCSerializationError.SerializationFailed;
|
||||
const serialized = value.serialize(global, true) orelse return IPCSerializationError.SerializationFailed;
|
||||
defer serialized.deinit();
|
||||
|
||||
const size: u32 = @intCast(serialized.data.len);
|
||||
|
||||
@@ -767,11 +767,9 @@ JSC_DEFINE_HOST_FUNCTION(functionSerialize,
|
||||
bool asNodeBuffer = false;
|
||||
if (optionsObject.isObject()) {
|
||||
JSC::JSObject* options = optionsObject.getObject();
|
||||
if (JSC::JSValue binaryTypeValue = options->getIfPropertyExists(
|
||||
globalObject, JSC::Identifier::fromString(vm, "binaryType"_s))) {
|
||||
if (JSC::JSValue binaryTypeValue = options->getIfPropertyExists(globalObject, JSC::Identifier::fromString(vm, "binaryType"_s))) {
|
||||
if (!binaryTypeValue.isString()) {
|
||||
throwTypeError(globalObject, throwScope,
|
||||
"binaryType must be a string"_s);
|
||||
throwTypeError(globalObject, throwScope, "binaryType must be a string"_s);
|
||||
return {};
|
||||
}
|
||||
|
||||
@@ -782,8 +780,7 @@ JSC_DEFINE_HOST_FUNCTION(functionSerialize,
|
||||
|
||||
Vector<JSC::Strong<JSC::JSObject>> transferList;
|
||||
Vector<RefPtr<MessagePort>> dummyPorts;
|
||||
ExceptionOr<Ref<SerializedScriptValue>> serialized = SerializedScriptValue::create(*globalObject, value, WTFMove(transferList),
|
||||
dummyPorts);
|
||||
ExceptionOr<Ref<SerializedScriptValue>> serialized = SerializedScriptValue::create(*globalObject, value, WTFMove(transferList), dummyPorts);
|
||||
|
||||
if (serialized.hasException()) {
|
||||
WebCore::propagateException(*globalObject, throwScope,
|
||||
|
||||
@@ -248,7 +248,7 @@ export function define(
|
||||
structuredClone = false,
|
||||
...rest
|
||||
} = {} as Partial<ClassDefinition>,
|
||||
): Partial<ClassDefinition> {
|
||||
): ClassDefinition {
|
||||
return new ClassDefinition({
|
||||
...rest,
|
||||
call,
|
||||
|
||||
@@ -1795,7 +1795,7 @@ function generateZig(
|
||||
if (structuredClone) {
|
||||
exports.set("onStructuredCloneSerialize", symbolName(typeName, "onStructuredCloneSerialize"));
|
||||
|
||||
if (structuredClone === "transferable") {
|
||||
if (typeof structuredClone === "object" && structuredClone.transferable) {
|
||||
exports.set("onStructuredCloneTransfer", symbolName(typeName, "onStructuredCloneTransfer"));
|
||||
}
|
||||
|
||||
@@ -2029,7 +2029,7 @@ const JavaScriptCoreBindings = struct {
|
||||
}
|
||||
`;
|
||||
|
||||
if (structuredClone === "transferable") {
|
||||
if (typeof structuredClone === "object" && structuredClone.transferable) {
|
||||
exports.set("structuredClone_transferable", symbolName(typeName, "onStructuredCloneTransfer"));
|
||||
output += `
|
||||
pub fn ${exports.get("structuredClone_transferable")}(thisValue: *${typeName}, globalObject: *JSC.JSGlobalObject, ctx: *anyopaque, write: WriteBytesFn) callconv(JSC.conv) void {
|
||||
@@ -2288,6 +2288,25 @@ ${jsclasses
|
||||
}`;
|
||||
}
|
||||
|
||||
function isTransferableCppImpl() {
|
||||
return `
|
||||
bool WebCore::SerializedScriptValue::isTransferable(JSC::JSGlobalObject* globalObject, JSC::JSValue value)
|
||||
{
|
||||
if (!value.isCell()) return true;
|
||||
auto cell = value.asCell();
|
||||
${classes
|
||||
.map(c => {
|
||||
if (c.structuredClone == null) return "";
|
||||
if (typeof c.structuredClone === "boolean") return "";
|
||||
if (c.structuredClone.transferable) return "";
|
||||
return ` if (JSC::jsDynamicCast<WebCore::JS${c.name}*>(cell)) return false;\n`;
|
||||
})
|
||||
.join("")}
|
||||
return true;
|
||||
}
|
||||
`;
|
||||
}
|
||||
|
||||
function initLazyClasses(initLaterFunctions) {
|
||||
return `
|
||||
|
||||
@@ -2352,7 +2371,7 @@ pub const WriteBytesFn = *const fn(*anyopaque, ptr: [*]const u8, len: u32) callc
|
||||
|
||||
`;
|
||||
|
||||
const classes = [];
|
||||
const classes: ClassDefinition[] = [];
|
||||
for (const file of files) {
|
||||
const result = require(path.resolve(file));
|
||||
if (!(result?.default?.length ?? 0)) continue;
|
||||
@@ -2511,7 +2530,7 @@ fn log_zig_call(typename: []const u8, callframe: *JSC.CallFrame) callconv(bun.ca
|
||||
if (comptime Environment.enable_logs) {
|
||||
zig("<d>{s}<d>({d} args)<r>", .{typename, callframe.arguments().len});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn log_zig_get_internal_properties(typename: []const u8) callconv(bun.callconv_inline) void {
|
||||
if (comptime Environment.enable_logs) {
|
||||
@@ -2602,6 +2621,7 @@ if (!process.env.ONLY_ZIG) {
|
||||
writeCppSerializers(classes),
|
||||
GENERATED_CLASSES_IMPL_FOOTER,
|
||||
jsInheritsCppImpl(),
|
||||
isTransferableCppImpl(),
|
||||
]);
|
||||
|
||||
if (lutTextFile.length) {
|
||||
@@ -2704,7 +2724,7 @@ function getPropertySignatureWithComment(
|
||||
commentLines.push(
|
||||
` Look for a setter like this:
|
||||
* \`\`\`zig
|
||||
* fn ${propDef.accessor.setter}(this: *${classDef.name}, globalThis: *JSC.JSGlobalObject, value: JSC.JSValue) bun.JSError!void
|
||||
* fn ${propDef.accessor.setter}(this: *${classDef.name}, globalThis: *JSC.JSGlobalObject, value: JSC.JSValue) bun.JSError!void
|
||||
* \`\`\``,
|
||||
);
|
||||
if (propDef.cache) {
|
||||
@@ -2854,7 +2874,7 @@ export function generateBuiltinTypes(classes: ClassDefinition[]): string {
|
||||
* Type definitions for Bun's built-in classes implemented in Zig.
|
||||
* Do not edit this file directly.
|
||||
* @generated
|
||||
*
|
||||
*
|
||||
* This namespace does not exist at runtime!
|
||||
*/
|
||||
declare namespace $ZigGeneratedClasses {
|
||||
|
||||
@@ -210,7 +210,7 @@ export function tempDirWithFiles(basename: string, files: DirectoryTree): string
|
||||
return base;
|
||||
}
|
||||
|
||||
export function bunRun(file: string, env?: Record<string, string>) {
|
||||
export function bunRun(file: string, env?: Record<string, string> | NodeJS.ProcessEnv) {
|
||||
var path = require("path");
|
||||
const result = Bun.spawnSync([bunExe(), file], {
|
||||
cwd: path.dirname(file),
|
||||
|
||||
70
test/js/node/cluster.test.ts
Normal file
70
test/js/node/cluster.test.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { bunEnv, bunRun, joinP, tempDirWithFiles } from "harness";
|
||||
|
||||
test("cloneable and transferable equals", () => {
|
||||
const dir = tempDirWithFiles("bun-test", {
|
||||
"index.ts": `
|
||||
import cluster from "cluster";
|
||||
import { expect } from "bun:test";
|
||||
if (cluster.isPrimary) {
|
||||
cluster.settings.serialization = "advanced";
|
||||
const worker = cluster.fork();
|
||||
const original = Uint8Array.from([21, 11, 96, 126, 243, 128, 164]);
|
||||
const buf = Uint8Array.from([21, 11, 96, 126, 243, 128, 164]);
|
||||
const ab = buf.buffer.transfer();
|
||||
expect(ab).toBeInstanceOf(ArrayBuffer);
|
||||
expect(new Uint8Array(ab)).toEqual(original);
|
||||
worker.on("online", function () {
|
||||
worker.send(ab);
|
||||
});
|
||||
worker.on("message", function (data) {
|
||||
worker.kill();
|
||||
expect(data).toBeInstanceOf(ArrayBuffer);
|
||||
expect(new Uint8Array(data)).toEqual(original);
|
||||
process.exit(0);
|
||||
});
|
||||
} else {
|
||||
process.on("message", msg => {
|
||||
console.log("W", msg);
|
||||
process.send!(msg);
|
||||
});
|
||||
}
|
||||
`,
|
||||
});
|
||||
bunRun(joinP(dir, "index.ts"), bunEnv);
|
||||
});
|
||||
|
||||
test("cloneable and non-transferable not-equals", () => {
|
||||
const dir = tempDirWithFiles("bun-test", {
|
||||
"index.ts": `
|
||||
import cluster from "cluster";
|
||||
import { expect } from "bun:test";
|
||||
if (cluster.isPrimary) {
|
||||
cluster.settings.serialization = "advanced";
|
||||
const worker = cluster.fork();
|
||||
const file = Bun.file(import.meta.filename);
|
||||
console.log("P", "O", file);
|
||||
expect(file).toBeInstanceOf(Blob); // Bun.BunFile isnt exposed to JS
|
||||
expect(file.name).toEqual(import.meta.filename);
|
||||
expect(file.type).toEqual("text/javascript;charset=utf-8");
|
||||
worker.on("online", function () {
|
||||
worker.send({ file });
|
||||
});
|
||||
worker.on("message", function (data) {
|
||||
worker.kill();
|
||||
const { file } = data;
|
||||
console.log("P", "M", file);
|
||||
expect(file.name).toBeUndefined();
|
||||
expect(file.type).toBeUndefined();
|
||||
expect(file).toBeEmptyObject();
|
||||
process.exit(0);
|
||||
});
|
||||
} else {
|
||||
process.on("message", msg => {
|
||||
console.log("W", msg);
|
||||
process.send!(msg);
|
||||
});
|
||||
}
|
||||
`,
|
||||
});
|
||||
bunRun(joinP(dir, "index.ts"), bunEnv);
|
||||
});
|
||||
@@ -253,3 +253,50 @@ test("gc", () => {
|
||||
messageChannel.port2;
|
||||
}
|
||||
});
|
||||
|
||||
test("cloneable and transferable equals", async () => {
|
||||
const assert = require("assert");
|
||||
const mc = new MessageChannel();
|
||||
const original = Uint8Array.from([21, 11, 96, 126, 243, 128, 164]);
|
||||
const buf = Uint8Array.from([21, 11, 96, 126, 243, 128, 164]);
|
||||
const ab = buf.buffer.transfer();
|
||||
expect(ab).toBeInstanceOf(ArrayBuffer);
|
||||
expect(new Uint8Array(ab)).toEqual(original);
|
||||
const { promise, resolve, reject } = Promise.withResolvers();
|
||||
mc.port1.onmessage = ({ data }) => {
|
||||
try {
|
||||
expect(data).toBeInstanceOf(ArrayBuffer);
|
||||
expect(new Uint8Array(data)).toEqual(original);
|
||||
mc.port1.close();
|
||||
resolve();
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
};
|
||||
mc.port2.postMessage(ab);
|
||||
await promise;
|
||||
});
|
||||
|
||||
test("cloneable and non-transferable equals", async () => {
|
||||
const assert = require("assert");
|
||||
const mc = new MessageChannel();
|
||||
const file = Bun.file(import.meta.filename);
|
||||
expect(file).toBeInstanceOf(Blob); // Bun.BunFile isnt exposed to JS
|
||||
expect(file.name).toEqual(import.meta.filename);
|
||||
expect(file.type).toEqual("text/javascript;charset=utf-8");
|
||||
const { promise, resolve, reject } = Promise.withResolvers();
|
||||
mc.port1.onmessage = ({ data }) => {
|
||||
try {
|
||||
expect(data).toBeInstanceOf(file.__proto__.constructor);
|
||||
expect(data.name).toEqual(import.meta.filename);
|
||||
expect(data.type).toEqual("text/javascript;charset=utf-8");
|
||||
// expect(data).not.toBeEmptyObject();
|
||||
mc.port1.close();
|
||||
resolve();
|
||||
} catch (e) {
|
||||
reject(e);
|
||||
}
|
||||
};
|
||||
mc.port2.postMessage(file);
|
||||
await promise;
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user