LibWeb: Update FileAPI File constructor to follow the latest spec steps
This fixes http://wpt.live/FileAPI/file/File-constructor.any.html
This commit is contained in:
parent
8965698ce7
commit
60d0212f0b
4 changed files with 252 additions and 10 deletions
|
@ -57,19 +57,30 @@ WebIDL::ExceptionOr<GC::Ref<File>> File::create(JS::Realm& realm, Vector<BlobPar
|
|||
i64 last_modified = 0;
|
||||
// 3. Process FilePropertyBag dictionary argument by running the following substeps:
|
||||
if (options.has_value()) {
|
||||
// FIXME: 1. If the type member is provided and is not the empty string, let t be set to the type dictionary member.
|
||||
// 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.
|
||||
// FIXME: 2. Convert every character in t to ASCII lowercase.
|
||||
// 1. If the type member is provided and is not the empty string, let t be set to the type dictionary member.
|
||||
type = options->type;
|
||||
|
||||
// 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<File>(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:
|
||||
|
|
|
@ -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: <20><><EFBFBD>U+dcfd<66><64><EFBFBD>U+dcee<65><65><EFBFBD>U+dd01<30><31><EFBFBD>U+dcfd/<2F><><EFBFBD>U+dd2d<32><64><EFBFBD>U+dd29<32><39><EFBFBD>U+dd1e<31><65><EFBFBD>U+dd26<32><36><EFBFBD>U+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
|
|
@ -0,0 +1,15 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>File constructor</title>
|
||||
<script>
|
||||
self.GLOBAL = {
|
||||
isWindow: function() { return true; },
|
||||
isWorker: function() { return false; },
|
||||
isShadowRealm: function() { return false; },
|
||||
};
|
||||
</script>
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
|
||||
<div id=log></div>
|
||||
<script src="../../FileAPI/file/File-constructor.any.js"></script>
|
|
@ -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');
|
Loading…
Add table
Reference in a new issue