diff --git a/Libraries/LibWeb/FileAPI/File.cpp b/Libraries/LibWeb/FileAPI/File.cpp index a93094fc82d..d898d395f00 100644 --- a/Libraries/LibWeb/FileAPI/File.cpp +++ b/Libraries/LibWeb/FileAPI/File.cpp @@ -58,19 +58,30 @@ WebIDL::ExceptionOr> File::create(JS::Realm& realm, Vectortype; - // NOTE: The spec is out of date, and we are supposed to call into the MimeType parser here. - auto maybe_parsed_type = Web::MimeSniff::MimeType::parse(options->type); + if (!type.is_empty()) { + // If t contains any characters outside the range U+0020 to U+007E, then set t to the empty string and return from these substeps. + if (AK::any_of(type.bytes_as_string_view(), [](auto ch) { return ch < 0x20 || ch > 0x7E; })) { + // Assign an empty string + type = String {}; + // Return from these substeps. + return realm.create(realm, move(bytes), move(name), move(type), last_modified); + } + // 2. Convert every character in t to ASCII lowercase. + type = type.to_ascii_lowercase(); - if (maybe_parsed_type.has_value()) - type = maybe_parsed_type->serialized(); + } else { + // If the type member is not provided or is the empty string, set t to the empty string. + type = String {}; + } - // 3. If the lastModified member is provided, let d be set to the lastModified dictionary member. If it is not provided, set d to the current date and time represented as the number of milliseconds since the Unix Epoch (which is the equivalent of Date.now() [ECMA-262]). - // Note: Since ECMA-262 Date objects convert to long long values representing the number of milliseconds since the Unix Epoch, the lastModified member could be a Date object [ECMA-262]. - last_modified = options->last_modified.has_value() ? options->last_modified.value() : UnixDateTime::now().milliseconds_since_epoch(); + // 3. If the lastModified member is provided, let d be set to the lastModified dictionary member. + // If it is not provided, set d to the current date and time represented as the number of milliseconds since the Unix Epoch. + last_modified = options->last_modified.has_value() + ? options->last_modified.value() + : UnixDateTime::now().milliseconds_since_epoch(); } // 4. Return a new File object F such that: diff --git a/Tests/LibWeb/Text/expected/wpt-import/FileAPI/file/File-constructor.any.txt b/Tests/LibWeb/Text/expected/wpt-import/FileAPI/file/File-constructor.any.txt new file mode 100644 index 00000000000..4b275a712f6 --- /dev/null +++ b/Tests/LibWeb/Text/expected/wpt-import/FileAPI/file/File-constructor.any.txt @@ -0,0 +1,61 @@ +Summary + +Harness status: OK + +Rerun + +Found 51 tests + +51 Pass +Details +Result Test Name MessagePass File interface object exists +Pass Required arguments +Pass empty fileBits +Pass DOMString fileBits +Pass Unicode DOMString fileBits +Pass String object fileBits +Pass Empty Blob fileBits +Pass Blob fileBits +Pass Empty File fileBits +Pass File fileBits +Pass ArrayBuffer fileBits +Pass Typed array fileBits +Pass Various fileBits +Pass Number in fileBits +Pass Array in fileBits +Pass Object in fileBits +Pass HTMLBodyElement in fileBits +Pass Object with toString in fileBits +Pass Custom @@iterator +Pass Invalid bits argument: "hello" +Pass Invalid bits argument: 0 +Pass Invalid bits argument: null +Pass Bits argument: object that throws +Pass Using fileName +Pass No replacement when using special character in fileName +Pass Using null fileName +Pass Using number fileName +Pass Using empty string fileName +Pass Using object fileName +Pass Using type in File constructor: text/plain +Pass Using type in File constructor: text/plain;charset=UTF-8 +Pass Using type in File constructor: TEXT/PLAIN +Pass Using type in File constructor: U+dcfdU+dceeU+dd01U+dcfd/U+dd2dU+dd29U+dd1eU+dd26U+dd2b +Pass Using type in File constructor: ascii/nonprintable +Pass Using type in File constructor: ascii/nonprintable +Pass Using type in File constructor: nonasciiî +Pass Using type in File constructor: nonasciiሴ +Pass Using type in File constructor: nonparsable +Pass Using lastModified +Pass Misusing name +Pass Unknown properties are ignored +Pass Invalid property bag: 123 +Pass Invalid property bag: 123.4 +Pass Invalid property bag: true +Pass Invalid property bag: "abc" +Pass Unusual but valid property bag: null +Pass Unusual but valid property bag: undefined +Pass Unusual but valid property bag: 1,2,3 +Pass Unusual but valid property bag: /regex/ +Pass Unusual but valid property bag: function() {} +Pass Property bag propagates exceptions \ No newline at end of file diff --git a/Tests/LibWeb/Text/input/wpt-import/FileAPI/file/File-constructor.any.html b/Tests/LibWeb/Text/input/wpt-import/FileAPI/file/File-constructor.any.html new file mode 100644 index 00000000000..dd715b0ef8a --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/FileAPI/file/File-constructor.any.html @@ -0,0 +1,15 @@ + + +File constructor + + + + +
+ diff --git a/Tests/LibWeb/Text/input/wpt-import/FileAPI/file/File-constructor.any.js b/Tests/LibWeb/Text/input/wpt-import/FileAPI/file/File-constructor.any.js new file mode 100644 index 00000000000..0b0185c40bf --- /dev/null +++ b/Tests/LibWeb/Text/input/wpt-import/FileAPI/file/File-constructor.any.js @@ -0,0 +1,155 @@ +// META: title=File constructor + +const to_string_obj = { toString: () => 'a string' }; +const to_string_throws = { toString: () => { throw new Error('expected'); } }; + +test(function() { + assert_true("File" in globalThis, "globalThis should have a File property."); +}, "File interface object exists"); + +test(t => { + assert_throws_js(TypeError, () => new File(), + 'Bits argument is required'); + assert_throws_js(TypeError, () => new File([]), + 'Name argument is required'); +}, 'Required arguments'); + +function test_first_argument(arg1, expectedSize, testName) { + test(function() { + var file = new File(arg1, "dummy"); + assert_true(file instanceof File); + assert_equals(file.name, "dummy"); + assert_equals(file.size, expectedSize); + assert_equals(file.type, ""); + // assert_false(file.isClosed); XXX: File.isClosed doesn't seem to be implemented + assert_not_equals(file.lastModified, ""); + }, testName); +} + +test_first_argument([], 0, "empty fileBits"); +test_first_argument(["bits"], 4, "DOMString fileBits"); +test_first_argument(["𝓽𝓮𝔁𝓽"], 16, "Unicode DOMString fileBits"); +test_first_argument([new String('string object')], 13, "String object fileBits"); +test_first_argument([new Blob()], 0, "Empty Blob fileBits"); +test_first_argument([new Blob(["bits"])], 4, "Blob fileBits"); +test_first_argument([new File([], 'world.txt')], 0, "Empty File fileBits"); +test_first_argument([new File(["bits"], 'world.txt')], 4, "File fileBits"); +test_first_argument([new ArrayBuffer(8)], 8, "ArrayBuffer fileBits"); +test_first_argument([new Uint8Array([0x50, 0x41, 0x53, 0x53])], 4, "Typed array fileBits"); +test_first_argument(["bits", new Blob(["bits"]), new Blob(), new Uint8Array([0x50, 0x41]), + new Uint16Array([0x5353]), new Uint32Array([0x53534150])], 16, "Various fileBits"); +test_first_argument([12], 2, "Number in fileBits"); +test_first_argument([[1,2,3]], 5, "Array in fileBits"); +test_first_argument([{}], 15, "Object in fileBits"); // "[object Object]" +if (globalThis.document !== undefined) { + test_first_argument([document.body], 24, "HTMLBodyElement in fileBits"); // "[object HTMLBodyElement]" +} +test_first_argument([to_string_obj], 8, "Object with toString in fileBits"); +test_first_argument({[Symbol.iterator]() { + let i = 0; + return {next: () => [ + {done:false, value:'ab'}, + {done:false, value:'cde'}, + {done:true} + ][i++]}; +}}, 5, 'Custom @@iterator'); + +[ + 'hello', + 0, + null +].forEach(arg => { + test(t => { + assert_throws_js(TypeError, () => new File(arg, 'world.html'), + 'Constructor should throw for invalid bits argument'); + }, `Invalid bits argument: ${JSON.stringify(arg)}`); +}); + +test(t => { + assert_throws_js(Error, () => new File([to_string_throws], 'name.txt'), + 'Constructor should propagate exceptions'); +}, 'Bits argument: object that throws'); + + +function test_second_argument(arg2, expectedFileName, testName) { + test(function() { + var file = new File(["bits"], arg2); + assert_true(file instanceof File); + assert_equals(file.name, expectedFileName); + }, testName); +} + +test_second_argument("dummy", "dummy", "Using fileName"); +test_second_argument("dummy/foo", "dummy/foo", + "No replacement when using special character in fileName"); +test_second_argument(null, "null", "Using null fileName"); +test_second_argument(1, "1", "Using number fileName"); +test_second_argument('', '', "Using empty string fileName"); +if (globalThis.document !== undefined) { + test_second_argument(document.body, '[object HTMLBodyElement]', "Using object fileName"); +} + +// testing the third argument +[ + {type: 'text/plain', expected: 'text/plain'}, + {type: 'text/plain;charset=UTF-8', expected: 'text/plain;charset=utf-8'}, + {type: 'TEXT/PLAIN', expected: 'text/plain'}, + {type: '𝓽𝓮𝔁𝓽/𝔭𝔩𝔞𝔦𝔫', expected: ''}, + {type: 'ascii/nonprintable\u001F', expected: ''}, + {type: 'ascii/nonprintable\u007F', expected: ''}, + {type: 'nonascii\u00EE', expected: ''}, + {type: 'nonascii\u1234', expected: ''}, + {type: 'nonparsable', expected: 'nonparsable'} +].forEach(testCase => { + test(t => { + var file = new File(["bits"], "dummy", { type: testCase.type}); + assert_true(file instanceof File); + assert_equals(file.type, testCase.expected); + }, `Using type in File constructor: ${testCase.type}`); +}); +test(function() { + var file = new File(["bits"], "dummy", { lastModified: 42 }); + assert_true(file instanceof File); + assert_equals(file.lastModified, 42); +}, "Using lastModified"); +test(function() { + var file = new File(["bits"], "dummy", { name: "foo" }); + assert_true(file instanceof File); + assert_equals(file.name, "dummy"); +}, "Misusing name"); +test(function() { + var file = new File(["bits"], "dummy", { unknownKey: "value" }); + assert_true(file instanceof File); + assert_equals(file.name, "dummy"); +}, "Unknown properties are ignored"); + +[ + 123, + 123.4, + true, + 'abc' +].forEach(arg => { + test(t => { + assert_throws_js(TypeError, () => new File(['bits'], 'name.txt', arg), + 'Constructor should throw for invalid property bag type'); + }, `Invalid property bag: ${JSON.stringify(arg)}`); +}); + +[ + null, + undefined, + [1,2,3], + /regex/, + function() {} +].forEach(arg => { + test(t => { + assert_equals(new File(['bits'], 'name.txt', arg).size, 4, + 'Constructor should accept object-ish property bag type'); + }, `Unusual but valid property bag: ${arg}`); +}); + +test(t => { + assert_throws_js(Error, + () => new File(['bits'], 'name.txt', {type: to_string_throws}), + 'Constructor should propagate exceptions'); +}, 'Property bag propagates exceptions');