From 25b35be50dd59d00e126591dd24d06824e2c50cf Mon Sep 17 00:00:00 2001 From: Luca Casonato Date: Thu, 4 Feb 2021 15:05:36 +0100 Subject: [PATCH] refactor: rewrite File implementation (#9334) --- op_crates/fetch/{21_blob.js => 21_file.js} | 53 +++++++++++++++++++++- op_crates/fetch/26_fetch.js | 36 +++------------ op_crates/fetch/internal.d.ts | 7 ++- op_crates/fetch/lib.rs | 4 +- runtime/js/99_main.js | 6 +-- test_util/wpt | 2 +- tools/wpt/expectation.json | 3 ++ 7 files changed, 71 insertions(+), 40 deletions(-) rename op_crates/fetch/{21_blob.js => 21_file.js} (85%) diff --git a/op_crates/fetch/21_blob.js b/op_crates/fetch/21_file.js similarity index 85% rename from op_crates/fetch/21_blob.js rename to op_crates/fetch/21_file.js index 552441b21b..d5160ece21 100644 --- a/op_crates/fetch/21_blob.js +++ b/op_crates/fetch/21_file.js @@ -141,7 +141,7 @@ /** @type {Uint8Array} */ [_byteSequence]; - /** + /** * @param {BlobPart[]} [blobParts] * @param {BlobPropertyBag} [options] */ @@ -287,8 +287,57 @@ } } - window.__bootstrap.blob = { + const _Name = Symbol("[[Name]]"); + const _LastModfied = Symbol("[[LastModified]]"); + + class File extends Blob { + /** @type {string} */ + [_Name]; + /** @type {number} */ + [_LastModfied]; + + /** + * @param {BlobPart[]} fileBits + * @param {string} fileName + * @param {FilePropertyBag} [options] + */ + constructor(fileBits, fileName, options) { + if (fileBits === undefined) { + throw new TypeError( + "Failed to construct 'File'. 2 arguments required, but first not specified.", + ); + } + if (fileName === undefined) { + throw new TypeError( + "Failed to construct 'File'. 2 arguments required, but second not specified.", + ); + } + super(fileBits, { endings: options?.endings, type: options?.type }); + /** @type {string} */ + this[_Name] = String(fileName).replaceAll("/", ":"); + if (options?.lastModified === undefined) { + /** @type {number} */ + this[_LastModfied] = new Date().getTime(); + } else { + /** @type {number} */ + this[_LastModfied] = Number(options.lastModified); + } + } + + /** @returns {string} */ + get name() { + return this[_Name]; + } + + /** @returns {number} */ + get lastModified() { + return this[_LastModfied]; + } + } + + window.__bootstrap.file = { Blob, _byteSequence, + File, }; })(this); diff --git a/op_crates/fetch/26_fetch.js b/op_crates/fetch/26_fetch.js index 47d701f3c9..52ae91e835 100644 --- a/op_crates/fetch/26_fetch.js +++ b/op_crates/fetch/26_fetch.js @@ -21,7 +21,7 @@ window.__bootstrap.streams; const { DomIterableMixin } = window.__bootstrap.domIterable; const { Headers } = window.__bootstrap.headers; - const { Blob, _byteSequence } = window.__bootstrap.blob; + const { Blob, _byteSequence, File } = window.__bootstrap.file; const MAX_SIZE = 2 ** 32 - 2; @@ -226,42 +226,19 @@ const dataSymbol = Symbol("data"); - class DomFile extends Blob { - /** - * @param {globalThis.BlobPart[]} fileBits - * @param {string} fileName - * @param {FilePropertyBag | undefined} options - */ - constructor( - fileBits, - fileName, - options, - ) { - const { lastModified = Date.now(), ...blobPropertyBag } = options ?? {}; - super(fileBits, blobPropertyBag); - - // 4.1.2.1 Replace any "/" character (U+002F SOLIDUS) - // with a ":" (U + 003A COLON) - this.name = String(fileName).replace(/\u002F/g, "\u003A"); - // 4.1.3.3 If lastModified is not provided, set lastModified to the current - // date and time represented in number of milliseconds since the Unix Epoch. - this.lastModified = lastModified; - } - } - /** * @param {Blob | string} value * @param {string | undefined} filename * @returns {FormDataEntryValue} */ function parseFormDataValue(value, filename) { - if (value instanceof DomFile) { - return new DomFile([value], filename || value.name, { + if (value instanceof File) { + return new File([value], filename || value.name, { type: value.type, lastModified: value.lastModified, }); } else if (value instanceof Blob) { - return new DomFile([value], filename || "blob", { + return new File([value], filename || "blob", { type: value.type, }); } else { @@ -408,7 +385,7 @@ */ getBody() { for (const [fieldName, fieldValue] of this.formData.entries()) { - if (fieldValue instanceof DomFile) { + if (fieldValue instanceof File) { this.#writeFile(fieldName, fieldValue); } else this.#writeField(fieldName, fieldValue); } @@ -487,7 +464,7 @@ /** * @param {string} field - * @param {DomFile} value + * @param {File} value * @returns {void} */ #writeFile = (field, value) => { @@ -1493,7 +1470,6 @@ } window.__bootstrap.fetch = { - File: DomFile, FormData, setBaseUrl, fetch, diff --git a/op_crates/fetch/internal.d.ts b/op_crates/fetch/internal.d.ts index 5fb30f5034..a474d499cb 100644 --- a/op_crates/fetch/internal.d.ts +++ b/op_crates/fetch/internal.d.ts @@ -19,11 +19,14 @@ declare namespace globalThis { Headers: typeof Headers; }; - declare var blob: { + declare var file: { Blob: typeof Blob & { - [globalThis.__bootstrap.blob._byteSequence]: Uint8Array; + [globalThis.__bootstrap.file._byteSequence]: Uint8Array; }; _byteSequence: unique symbol; + File: typeof File & { + [globalThis.__bootstrap.file._byteSequence]: Uint8Array; + }; }; declare var streams: { diff --git a/op_crates/fetch/lib.rs b/op_crates/fetch/lib.rs index 23f356a96f..157ce2fb22 100644 --- a/op_crates/fetch/lib.rs +++ b/op_crates/fetch/lib.rs @@ -67,8 +67,8 @@ pub fn init(isolate: &mut JsRuntime) { include_str!("20_headers.js"), ), ( - "deno:op_crates/fetch/21_blob.js", - include_str!("21_blob.js"), + "deno:op_crates/fetch/21_file.js", + include_str!("21_file.js"), ), ( "deno:op_crates/fetch/26_fetch.js", diff --git a/runtime/js/99_main.js b/runtime/js/99_main.js index c3ca7b772c..c6cc99dd29 100644 --- a/runtime/js/99_main.js +++ b/runtime/js/99_main.js @@ -27,7 +27,7 @@ delete Object.prototype.__proto__; const streams = window.__bootstrap.streams; const fileReader = window.__bootstrap.fileReader; const webSocket = window.__bootstrap.webSocket; - const blob = window.__bootstrap.blob; + const file = window.__bootstrap.file; const fetch = window.__bootstrap.fetch; const prompt = window.__bootstrap.prompt; const denoNs = window.__bootstrap.denoNs; @@ -198,7 +198,7 @@ delete Object.prototype.__proto__; // https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope const windowOrWorkerGlobalScope = { - Blob: util.nonEnumerable(blob.Blob), + Blob: util.nonEnumerable(file.Blob), ByteLengthQueuingStrategy: util.nonEnumerable( streams.ByteLengthQueuingStrategy, ), @@ -211,7 +211,7 @@ delete Object.prototype.__proto__; ErrorEvent: util.nonEnumerable(ErrorEvent), Event: util.nonEnumerable(Event), EventTarget: util.nonEnumerable(EventTarget), - File: util.nonEnumerable(fetch.File), + File: util.nonEnumerable(file.File), FileReader: util.nonEnumerable(fileReader.FileReader), FormData: util.nonEnumerable(fetch.FormData), Headers: util.nonEnumerable(headers.Headers), diff --git a/test_util/wpt b/test_util/wpt index 4e9ee672ed..81837c9a0f 160000 --- a/test_util/wpt +++ b/test_util/wpt @@ -1 +1 @@ -Subproject commit 4e9ee672edb2764c51904739bf8397b959b3a85c +Subproject commit 81837c9a0fb4a11a7bc3b062219c758b238f1b39 diff --git a/tools/wpt/expectation.json b/tools/wpt/expectation.json index 307021cd8e..72416736bf 100644 --- a/tools/wpt/expectation.json +++ b/tools/wpt/expectation.json @@ -1030,6 +1030,9 @@ ], "Blob-slice-overflow.any.js": true, "Blob-slice.any.js": true + }, + "file": { + "File-constructor.any.js": true } } } \ No newline at end of file