Skip to content

Commit

Permalink
buffer: make File cloneable
Browse files Browse the repository at this point in the history
  • Loading branch information
KhafraDev committed Apr 23, 2023
1 parent 95e40b5 commit 82e6b71
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 11 deletions.
64 changes: 53 additions & 11 deletions lib/internal/file.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ const {
DateNow,
NumberIsNaN,
ObjectDefineProperties,
ReflectConstruct,
Symbol,
SymbolToStringTag,
} = primordials;

Expand All @@ -21,19 +23,28 @@ const {
const {
codes: {
ERR_MISSING_ARGS,
ERR_INVALID_THIS,
},
} = require('internal/errors');

const {
inspect,
} = require('internal/util/inspect');

class File extends Blob {
/** @type {string} */
#name;
const {
makeTransferable,
kClone,
kDeserialize,
} = require('internal/worker/js_transferable');

/** @type {number} */
#lastModified;
const kState = Symbol('state');

function isFile(object) {
return object?.[kState] !== undefined;
}

class File extends Blob {
[kState] = { __proto__: null };

constructor(fileBits, fileName, options = kEmptyObject) {
if (arguments.length < 2) {
Expand All @@ -55,16 +66,24 @@ class File extends Blob {
lastModified = DateNow();
}

this.#name = toUSVString(fileName);
this.#lastModified = lastModified;
this[kState].name = toUSVString(fileName);
this[kState].lastModified = lastModified;

makeTransferable(this);
}

get name() {
return this.#name;
if (!isFile(this))
throw new ERR_INVALID_THIS('File');

return this[kState].name;
}

get lastModified() {
return this.#lastModified;
if (!isFile(this))
throw new ERR_INVALID_THIS('File');

return this[kState].lastModified;
}

[kInspect](depth, options) {
Expand All @@ -80,11 +99,33 @@ class File extends Blob {
return `File ${inspect({
size: this.size,
type: this.type,
name: this.#name,
lastModified: this.#lastModified,
name: this[kState].name,
lastModified: this[kState].lastModified,
}, opts)}`;
}

[kClone]() {
return {
data: { ...super[kClone]().data, ...this[kState] },
deserializeInfo: 'internal/file:ClonedFile',
};
}

[kDeserialize](data) {
super[kDeserialize](data);

this[kState] = {
__proto__: null,
name: data.name,
lastModified: data.lastModified,
};
}
}

function ClonedFile() {
return makeTransferable(ReflectConstruct(function() {}, [], File));
}
ClonedFile.prototype[kDeserialize] = () => {};

ObjectDefineProperties(File.prototype, {
name: kEnumerableProperty,
Expand All @@ -98,4 +139,5 @@ ObjectDefineProperties(File.prototype, {

module.exports = {
File,
ClonedFile,
};
16 changes: 16 additions & 0 deletions test/parallel/test-file.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,3 +158,19 @@ const { inspect } = require('util');
);
});
}

(async () => {
// File should be cloneable via structuredClone.
// Refs: https://github.com/nodejs/node/issues/47612

const body = ['hello, ', 'world'];
const lastModified = Date.now() - 10_000;
const name = 'hello_world.txt';

const file = new File(body, name, { lastModified });
const clonedFile = structuredClone(file);

assert.deepStrictEqual(await file.text(), await clonedFile.text());
assert.deepStrictEqual(file.lastModified, clonedFile.lastModified);
assert.deepStrictEqual(file.name, clonedFile.name);
})().then(common.mustCall());

0 comments on commit 82e6b71

Please sign in to comment.