GeezerVison is showing github.com — buffer: make File cloneable · nodejs/node@17fd327
File treeExpand file treeCollapse file tree Open diff view settings wpt/status/html/webappapis
Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: doc/api/buffer.md +3Lines changed: 3 additions & 0 deletions - Display the source diff
- Display the rich diff
| Original file line number | Diff line number | Diff line change |
|---|
@@ -5094,6 +5094,9 @@ added: | 5094 | 5094 | - v19.2.0 | 5095 | 5095 | - v18.13.0 | 5096 | 5096 | changes: | | 5097 | + - version: REPLACEME | | 5098 | + pr-url: https://github.com/nodejs/node/pull/47613 | | 5099 | + description: Makes File instances cloneable. | 5097 | 5100 | - version: v20.0.0 | 5098 | 5101 | pr-url: https://github.com/nodejs/node/pull/47153 | 5099 | 5102 | description: No longer experimental. | |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/blob.js +1Lines changed: 1 addition & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -504,4 +504,5 @@ module.exports = { | 504 | 504 | isBlob, | 505 | 505 | kHandle, | 506 | 506 | resolveObjectURL, | | 507 | + TransferableBlob, | 507 | 508 | }; |
Collapse file Copy file name to clipboardExpand all lines: lib/internal/file.js +61-11Lines changed: 61 additions & 11 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 2 | 2 |
| 3 | 3 | const { | 4 | 4 | DateNow, | | 5 | + FunctionPrototypeApply, | 5 | 6 | NumberIsNaN, | 6 | 7 | ObjectDefineProperties, | | 8 | + ObjectSetPrototypeOf, | 7 | 9 | StringPrototypeToWellFormed, | | 10 | + Symbol, | 8 | 11 | SymbolToStringTag, | 9 | 12 | } = primordials; | 10 | 13 |
| 11 | 14 | const { | 12 | 15 | Blob, | | 16 | + TransferableBlob, | 13 | 17 | } = require('internal/blob'); | 14 | 18 |
| 15 | 19 | const { | @@ -20,6 +24,7 @@ const { | 20 | 24 |
| 21 | 25 | const { | 22 | 26 | codes: { | | 27 | + ERR_INVALID_THIS, | 23 | 28 | ERR_MISSING_ARGS, | 24 | 29 | }, | 25 | 30 | } = require('internal/errors'); | @@ -28,13 +33,32 @@ const { | 28 | 33 | inspect, | 29 | 34 | } = require('internal/util/inspect'); | 30 | 35 |
| 31 | | -class File extends Blob { | 32 | | - /** @type {string} */ | 33 | | - #name; | | 36 | +const { | | 37 | + kClone, | | 38 | + kDeserialize, | | 39 | +} = require('internal/worker/js_transferable'); | | 40 | + | | 41 | +const kState = Symbol('state'); | | 42 | + | | 43 | +function isFile(object) { | | 44 | + return object?.[kState] !== undefined; | | 45 | +} | 34 | 46 |
| 35 | | - /** @type {number} */ | 36 | | - #lastModified; | | 47 | +class FileState { | | 48 | + name; | | 49 | + lastModified; | | 50 | + | | 51 | + /** | | 52 | + * @param {string} name | | 53 | + * @param {number} lastModified | | 54 | + */ | | 55 | + constructor(name, lastModified) { | | 56 | + this.name = name; | | 57 | + this.lastModified = lastModified; | | 58 | + } | | 59 | +} | 37 | 60 |
| | 61 | +class File extends Blob { | 38 | 62 | constructor(fileBits, fileName, options = kEmptyObject) { | 39 | 63 | if (arguments.length < 2) { | 40 | 64 | throw new ERR_MISSING_ARGS('fileBits', 'fileName'); | @@ -55,16 +79,21 @@ class File extends Blob { | 55 | 79 | lastModified = DateNow(); | 56 | 80 | } | 57 | 81 |
| 58 | | - this.#name = StringPrototypeToWellFormed(`${fileName}`); | 59 | | - this.#lastModified = lastModified; | | 82 | + this[kState] = new FileState(StringPrototypeToWellFormed(`${fileName}`), lastModified); | 60 | 83 | } | 61 | 84 |
| 62 | 85 | get name() { | 63 | | - return this.#name; | | 86 | + if (!isFile(this)) | | 87 | + throw new ERR_INVALID_THIS('File'); | | 88 | + | | 89 | + return this[kState].name; | 64 | 90 | } | 65 | 91 |
| 66 | 92 | get lastModified() { | 67 | | - return this.#lastModified; | | 93 | + if (!isFile(this)) | | 94 | + throw new ERR_INVALID_THIS('File'); | | 95 | + | | 96 | + return this[kState].lastModified; | 68 | 97 | } | 69 | 98 |
| 70 | 99 | [kInspect](depth, options) { | @@ -80,12 +109,32 @@ class File extends Blob { | 80 | 109 | return `File ${inspect({ | 81 | 110 | size: this.size, | 82 | 111 | type: this.type, | 83 | | - name: this.#name, | 84 | | - lastModified: this.#lastModified, | | 112 | + name: this[kState].name, | | 113 | + lastModified: this[kState].lastModified, | 85 | 114 | }, opts)}`; | 86 | 115 | } | | 116 | + | | 117 | + [kClone]() { | | 118 | + return { | | 119 | + data: { ...super[kClone]().data, ...this[kState] }, | | 120 | + deserializeInfo: 'internal/file:TransferableFile', | | 121 | + }; | | 122 | + } | | 123 | + | | 124 | + [kDeserialize](data) { | | 125 | + super[kDeserialize](data); | | 126 | + | | 127 | + this[kState] = new FileState(data.name, data.lastModified); | | 128 | + } | | 129 | +} | | 130 | + | | 131 | +function TransferableFile(handle, length, type = '') { | | 132 | + FunctionPrototypeApply(TransferableBlob, this, [handle, length, type]); | 87 | 133 | } | 88 | 134 |
| | 135 | +ObjectSetPrototypeOf(TransferableFile.prototype, File.prototype); | | 136 | +ObjectSetPrototypeOf(TransferableFile, File); | | 137 | + | 89 | 138 | ObjectDefineProperties(File.prototype, { | 90 | 139 | name: kEnumerableProperty, | 91 | 140 | lastModified: kEnumerableProperty, | @@ -98,4 +147,5 @@ ObjectDefineProperties(File.prototype, { | 98 | 147 |
| 99 | 148 | module.exports = { | 100 | 149 | File, | | 150 | + TransferableFile, | 101 | 151 | }; |
Collapse file Copy file name to clipboardExpand all lines: test/parallel/test-file.js +22Lines changed: 22 additions & 0 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -158,3 +158,25 @@ const { inspect } = require('util'); | 158 | 158 | ); | 159 | 159 | }); | 160 | 160 | } | | 161 | + | | 162 | +(async () => { | | 163 | + // File should be cloneable via structuredClone. | | 164 | + // Refs: https://github.com/nodejs/node/issues/47612 | | 165 | + | | 166 | + const body = ['hello, ', 'world']; | | 167 | + const lastModified = Date.now() - 10_000; | | 168 | + const name = 'hello_world.txt'; | | 169 | + | | 170 | + const file = new File(body, name, { lastModified }); | | 171 | + const clonedFile = structuredClone(file); | | 172 | + | | 173 | + assert.deepStrictEqual(await clonedFile.text(), await file.text()); | | 174 | + assert.deepStrictEqual(clonedFile.lastModified, file.lastModified); | | 175 | + assert.deepStrictEqual(clonedFile.name, file.name); | | 176 | + | | 177 | + const clonedFile2 = structuredClone(clonedFile); | | 178 | + | | 179 | + assert.deepStrictEqual(await clonedFile2.text(), await clonedFile.text()); | | 180 | + assert.deepStrictEqual(clonedFile2.lastModified, clonedFile.lastModified); | | 181 | + assert.deepStrictEqual(clonedFile2.name, clonedFile.name); | | 182 | +})().then(common.mustCall()); |
Collapse file Copy file name to clipboard +1-7Lines changed: 1 addition & 7 deletions | Original file line number | Diff line number | Diff line change |
|---|
| 1 | | -{ | 2 | | - "structured-clone.any.js": { | 3 | | - "fail": { | 4 | | - "expected": ["File basic"] | 5 | | - } | 6 | | - } | 7 | | -} | | 1 | +{} |
|