mirror of
https://github.com/lexmount/moli.git
synced 2026-09-28 08:01:37 +00:00
fix(file-api): enforce WebIDL receiver contracts
This commit is contained in:
+3
-1
@@ -35,7 +35,9 @@ document.body.setAttribute("data-file-type", file.type);
|
||||
document.body.setAttribute("data-file-last-modified", String(file.lastModified));
|
||||
document.body.setAttribute("data-file-size", String(file.size));
|
||||
|
||||
const list = new FileList([file]);
|
||||
const transfer = new DataTransfer();
|
||||
transfer.items.add(file);
|
||||
const list = transfer.files;
|
||||
document.body.setAttribute("data-file-list-tag", Object.prototype.toString.call(list));
|
||||
document.body.setAttribute("data-file-list-length", String(list.length));
|
||||
document.body.setAttribute("data-file-list-item-same", String(list.item(0) === file));
|
||||
|
||||
@@ -110,6 +110,10 @@ fn blob_size_attribute_getter_callback<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if !is_blob_object(scope, args.this()) {
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return;
|
||||
}
|
||||
let size = blob_bytes_from_object(scope, args.this())
|
||||
.map(|bytes| bytes.len() as f64)
|
||||
.unwrap_or(0.0);
|
||||
@@ -121,6 +125,10 @@ fn blob_type_attribute_getter_callback<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if !is_blob_object(scope, args.this()) {
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return;
|
||||
}
|
||||
let mime_type = blob_mime_type_from_object(scope, args.this()).unwrap_or_default();
|
||||
if let Some(value) = v8_string(scope, &mime_type) {
|
||||
rv.set(value.into());
|
||||
@@ -134,6 +142,10 @@ pub(super) fn blob_text_callback<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if !is_blob_object(scope, args.this()) {
|
||||
set_rejected_type_error_promise(scope, &mut rv, "Illegal invocation");
|
||||
return;
|
||||
}
|
||||
let text = blob_bytes_from_object(scope, args.this())
|
||||
.map(|bytes| String::from_utf8_lossy(&bytes).into_owned())
|
||||
.unwrap_or_default();
|
||||
@@ -149,6 +161,10 @@ pub(super) fn blob_array_buffer_callback<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if !is_blob_object(scope, args.this()) {
|
||||
set_rejected_type_error_promise(scope, &mut rv, "Illegal invocation");
|
||||
return;
|
||||
}
|
||||
let bytes = blob_bytes_from_object(scope, args.this()).unwrap_or_default();
|
||||
let value = array_buffer_from_bytes(scope, bytes)
|
||||
.map(|buffer| buffer.into())
|
||||
@@ -161,6 +177,10 @@ pub(super) fn blob_bytes_callback<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if !is_blob_object(scope, args.this()) {
|
||||
set_rejected_type_error_promise(scope, &mut rv, "Illegal invocation");
|
||||
return;
|
||||
}
|
||||
let bytes = blob_bytes_from_object(scope, args.this()).unwrap_or_default();
|
||||
let value = new_uint8_array_from_bytes(scope, bytes)
|
||||
.map(|array| array.into())
|
||||
@@ -173,6 +193,10 @@ pub(super) fn blob_stream_callback<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if !is_blob_object(scope, args.this()) {
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return;
|
||||
}
|
||||
let Some(bytes) = blob_bytes_from_object(scope, args.this()) else {
|
||||
rv.set(v8::undefined(scope).into());
|
||||
return;
|
||||
@@ -196,6 +220,10 @@ pub(super) fn blob_slice_callback<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if !is_blob_object(scope, args.this()) {
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return;
|
||||
}
|
||||
let bytes = blob_bytes_from_object(scope, args.this()).unwrap_or_default();
|
||||
let size = bytes.len();
|
||||
let start = clamped_long_long_arg(scope, &args, 0).unwrap_or(0);
|
||||
@@ -940,3 +968,19 @@ fn set_resolved_promise(
|
||||
let _ = resolver.resolve(scope, value);
|
||||
rv.set(promise.into());
|
||||
}
|
||||
|
||||
fn set_rejected_type_error_promise(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
rv: &mut v8::ReturnValue<'_, v8::Value>,
|
||||
message: &str,
|
||||
) {
|
||||
let Some(resolver) = v8::PromiseResolver::new(scope) else {
|
||||
rv.set(v8::undefined(scope).into());
|
||||
return;
|
||||
};
|
||||
let promise = resolver.get_promise(scope);
|
||||
let message = v8_string(scope, message).unwrap_or_else(|| v8::String::empty(scope));
|
||||
let reason = v8::Exception::type_error(scope, message);
|
||||
let _ = resolver.reject(scope, reason);
|
||||
rv.set(promise.into());
|
||||
}
|
||||
|
||||
@@ -16,8 +16,7 @@ use super::super::{
|
||||
exposed_interfaces::{TemplateBuildProfile, install_interface_template_metadata},
|
||||
file_api::{
|
||||
data_transfer_constructor_callback, file_constructor_callback,
|
||||
file_list_constructor_callback, file_reader_constructor_callback,
|
||||
file_reader_sync_constructor_callback,
|
||||
file_reader_constructor_callback, file_reader_sync_constructor_callback,
|
||||
},
|
||||
form_data_runtime::build_form_data_constructor_template,
|
||||
geometry_runtime::{
|
||||
@@ -463,14 +462,6 @@ pub(in crate::context_bootstrap) fn build_constructor_template_for_profile<'s>(
|
||||
.length(2)
|
||||
.build(scope)
|
||||
}
|
||||
ConstructorKind::FileList => {
|
||||
v8::FunctionTemplate::builder(moli_webapi_declare::web_api_constructor!(
|
||||
web_api_interfaces::FileList,
|
||||
file_list_constructor_callback
|
||||
))
|
||||
.length(0)
|
||||
.build(scope)
|
||||
}
|
||||
ConstructorKind::FileReader => {
|
||||
v8::FunctionTemplate::builder(moli_webapi_declare::web_api_constructor!(
|
||||
web_api_interfaces::FileReader,
|
||||
|
||||
@@ -37,11 +37,11 @@ pub(crate) use file_entry_file::{
|
||||
FileEntryFileCallbackTask, FileEntryFileCallbackTaskEffect,
|
||||
file_system_file_entry_file_callback,
|
||||
};
|
||||
pub(super) use file_list::file_list_item_callback;
|
||||
pub(crate) use file_list::{
|
||||
build_file_list_object, file_list_files_from_object, is_file_list_object,
|
||||
sync_file_list_contents,
|
||||
};
|
||||
pub(super) use file_list::{file_list_constructor_callback, file_list_item_callback};
|
||||
pub(crate) use file_reader::flush_one_pending_file_reader;
|
||||
pub(super) use file_reader::{
|
||||
file_reader_abort_callback, file_reader_add_event_listener_callback,
|
||||
|
||||
@@ -86,6 +86,10 @@ fn file_name_attribute_getter_callback<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if !file_receiver_branded(scope, args.this()) {
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return;
|
||||
}
|
||||
let name = file_name_from_object(scope, args.this()).unwrap_or_default();
|
||||
if let Some(value) = v8_string(scope, &name) {
|
||||
rv.set(value.into());
|
||||
@@ -106,12 +110,23 @@ fn file_last_modified_attribute_getter_callback<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if !file_receiver_branded(scope, args.this()) {
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return;
|
||||
}
|
||||
let last_modified = get_private_value(scope, args.this(), FILE_LAST_MODIFIED_SLOT)
|
||||
.and_then(|value| value.number_value(scope))
|
||||
.unwrap_or_else(unix_epoch_millis);
|
||||
rv.set(v8::Number::new(scope, last_modified).into());
|
||||
}
|
||||
|
||||
fn file_receiver_branded<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
) -> bool {
|
||||
get_private_value(scope, receiver, FILE_NAME_SLOT).is_some()
|
||||
}
|
||||
|
||||
fn private_string_value<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
object: v8::Local<'s, v8::Object>,
|
||||
|
||||
@@ -6,10 +6,14 @@ use moli_webapi_declare::{WebApiFunctionTemplate, WebApiObject};
|
||||
|
||||
const FILE_LIST_LENGTH_SLOT: &str = "__lmFileListLength";
|
||||
const FILE_LIST_FILES_SLOT: &str = "__lmFileListFiles";
|
||||
const FILE_LIST_BRAND_SLOT: &str = "__lmFileListBrand";
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(interface = web_api_interfaces::FileList, require_prototype)]
|
||||
struct FileListObjectDeclaration {
|
||||
#[webapi(slot = FILE_LIST_BRAND_SLOT, init = true)]
|
||||
brand: (),
|
||||
|
||||
#[webapi(slot = FILE_LIST_LENGTH_SLOT)]
|
||||
length: f64,
|
||||
}
|
||||
@@ -42,22 +46,6 @@ pub(super) fn install_file_list_template_bindings<'s>(
|
||||
);
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap) fn file_list_constructor_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if !args.is_construct_call() {
|
||||
throw_type_error(
|
||||
scope,
|
||||
"Failed to construct 'FileList': Please use the 'new' operator.",
|
||||
);
|
||||
return;
|
||||
}
|
||||
initialize_file_list_object(scope, args.this(), args.get(0));
|
||||
rv.set(args.this().into());
|
||||
}
|
||||
|
||||
pub(crate) fn build_file_list_object<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
files: &[v8::Local<'s, v8::Object>],
|
||||
@@ -153,6 +141,10 @@ pub(in crate::context_bootstrap) fn file_list_item_callback<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if !file_list_receiver_branded(scope, args.this()) {
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return;
|
||||
}
|
||||
let Some(parsed) = webidl::parse_args::<FileListItemArgs>(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
@@ -172,6 +164,10 @@ fn file_list_length_getter_callback<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if !file_list_receiver_branded(scope, args.this()) {
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return;
|
||||
}
|
||||
let length = file_list_length_from_object(scope, args.this())
|
||||
.filter(|value| value.is_finite() && *value >= 0.0)
|
||||
.unwrap_or(0.0);
|
||||
@@ -186,33 +182,10 @@ fn file_list_length_from_object<'s>(
|
||||
.and_then(|value| value.number_value(scope))
|
||||
}
|
||||
|
||||
fn initialize_file_list_object<'s>(
|
||||
fn file_list_receiver_branded<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
object: v8::Local<'s, v8::Object>,
|
||||
files_value: v8::Local<'s, v8::Value>,
|
||||
) {
|
||||
let files = if files_value.is_null_or_undefined() {
|
||||
None
|
||||
} else {
|
||||
files_value.to_object(scope)
|
||||
};
|
||||
let length = files
|
||||
.and_then(|value| value.get(scope, v8str(scope, "length").into()))
|
||||
.and_then(|value| value.number_value(scope))
|
||||
.filter(|value| value.is_finite() && *value >= 0.0)
|
||||
.map(|value| value as u32)
|
||||
.unwrap_or(0);
|
||||
let mut contents = Vec::new();
|
||||
for index in 0..length {
|
||||
let file = files.and_then(|files| files.get_index(scope, index));
|
||||
contents.push(file.unwrap_or_else(|| v8::undefined(scope).into()));
|
||||
if let Some(file) = file {
|
||||
let _ = object.set_index(scope, index, file);
|
||||
}
|
||||
}
|
||||
let contents = v8::Array::new_with_elements(scope, &contents);
|
||||
set_private_value(scope, object, FILE_LIST_FILES_SLOT, contents.into());
|
||||
FileListObjectDeclaration::new(length as f64)
|
||||
.initialize(scope, object)
|
||||
.expect("FileList declaration should initialize object");
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
) -> bool {
|
||||
get_private_value(scope, receiver, FILE_LIST_BRAND_SLOT)
|
||||
.is_some_and(|value| value.boolean_value(scope))
|
||||
}
|
||||
|
||||
@@ -21,10 +21,11 @@ pub(in crate::context_bootstrap::file_api) use state::install_file_reader_templa
|
||||
pub(in crate::context_bootstrap::file_api::file_reader) use events::dispatch_file_reader_event;
|
||||
pub(in crate::context_bootstrap::file_api::file_reader) use state::{
|
||||
file_reader_pending_result, file_reader_pending_total, file_reader_read_id,
|
||||
file_reader_ready_state, file_reader_scheduled, file_reader_task_phase,
|
||||
initialize_file_reader_object, set_file_reader_error, set_file_reader_pending_result,
|
||||
set_file_reader_pending_total, set_file_reader_read_id, set_file_reader_ready_state,
|
||||
set_file_reader_result, set_file_reader_scheduled, set_file_reader_task_phase,
|
||||
file_reader_ready_state, file_reader_receiver_branded, file_reader_scheduled,
|
||||
file_reader_task_phase, initialize_file_reader_object, set_file_reader_error,
|
||||
set_file_reader_pending_result, set_file_reader_pending_total, set_file_reader_read_id,
|
||||
set_file_reader_ready_state, set_file_reader_result, set_file_reader_scheduled,
|
||||
set_file_reader_task_phase,
|
||||
};
|
||||
pub(in crate::context_bootstrap::file_api::file_reader) use task::file_reader_flush_callback;
|
||||
pub(crate) use task::flush_one_pending_file_reader;
|
||||
|
||||
@@ -24,6 +24,10 @@ pub(in crate::context_bootstrap) fn file_reader_add_event_listener_callback<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if !file_reader_receiver_branded(scope, args.this()) {
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return;
|
||||
}
|
||||
simple_object_event_target_add_listener(scope, &args, FILE_READER_LISTENERS_SLOT);
|
||||
rv.set_undefined();
|
||||
}
|
||||
@@ -33,6 +37,10 @@ pub(in crate::context_bootstrap) fn file_reader_remove_event_listener_callback<'
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if !file_reader_receiver_branded(scope, args.this()) {
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return;
|
||||
}
|
||||
simple_object_event_target_remove_listener(scope, &args, FILE_READER_LISTENERS_SLOT);
|
||||
rv.set_undefined();
|
||||
}
|
||||
|
||||
@@ -126,6 +126,10 @@ pub(in crate::context_bootstrap) fn file_reader_abort_callback<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if !file_reader_receiver_branded(scope, args.this()) {
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return;
|
||||
}
|
||||
if file_reader_ready_state(scope, args.this()) != 1.0 {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
@@ -177,6 +181,10 @@ fn ensure_file_reader_can_start_read<'s>(
|
||||
reader: v8::Local<'s, v8::Object>,
|
||||
rv: &mut v8::ReturnValue<'_, v8::Value>,
|
||||
) -> bool {
|
||||
if !file_reader_receiver_branded(scope, reader) {
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return false;
|
||||
}
|
||||
if file_reader_ready_state(scope, reader) != 1.0 {
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1,18 +1,31 @@
|
||||
use super::*;
|
||||
use crate::util::{get_private_value, set_private_value};
|
||||
use crate::util::{callback_data_item, get_private_value, set_private_value};
|
||||
use crate::web_api_interfaces;
|
||||
use moli_webapi_declare::{WebApiFunctionTemplate, WebApiObject};
|
||||
|
||||
const FILE_READER_READY_STATE_SLOT: &str = "__lmFileReaderReadyState";
|
||||
const FILE_READER_RESULT_SLOT: &str = "__lmFileReaderResult";
|
||||
const FILE_READER_ERROR_SLOT: &str = "__lmFileReaderError";
|
||||
const FILE_READER_BRAND_SLOT: &str = "__lmFileReaderBrand";
|
||||
const FILE_READER_ONLOADSTART_SLOT: &str = "__lmFileReaderOnloadstart";
|
||||
const FILE_READER_ONPROGRESS_SLOT: &str = "__lmFileReaderOnprogress";
|
||||
const FILE_READER_ONLOAD_SLOT: &str = "__lmFileReaderOnload";
|
||||
const FILE_READER_ONABORT_SLOT: &str = "__lmFileReaderOnabort";
|
||||
const FILE_READER_ONERROR_SLOT: &str = "__lmFileReaderOnerror";
|
||||
const FILE_READER_ONLOADEND_SLOT: &str = "__lmFileReaderOnloadend";
|
||||
|
||||
#[derive(Default, WebApiObject)]
|
||||
#[webapi(interface = web_api_interfaces::FileReader)]
|
||||
struct FileReaderObjectDeclaration {
|
||||
#[webapi(slot = FILE_READER_BRAND_SLOT, init = true)]
|
||||
brand: (),
|
||||
|
||||
#[webapi(slot = SIMPLE_EVENT_TARGET_SLOT, value = FILE_READER_LISTENERS_SLOT)]
|
||||
event_target_slot: (),
|
||||
|
||||
#[webapi(slot = SIMPLE_EVENT_TARGET_ORDERED_HANDLERS_SLOT, init = true)]
|
||||
ordered_handlers: (),
|
||||
|
||||
#[webapi(slot = FILE_READER_READY_STATE_SLOT, init = 0)]
|
||||
ready_state: (),
|
||||
|
||||
@@ -22,23 +35,23 @@ struct FileReaderObjectDeclaration {
|
||||
#[webapi(slot = FILE_READER_ERROR_SLOT, init = "null")]
|
||||
error: (),
|
||||
|
||||
#[webapi(data_property = "onloadstart", init = "null")]
|
||||
#[webapi(slot = FILE_READER_ONLOADSTART_SLOT, init = "null")]
|
||||
onloadstart: (),
|
||||
|
||||
#[webapi(data_property = "onprogress", init = "null")]
|
||||
#[webapi(slot = FILE_READER_ONPROGRESS_SLOT, init = "null")]
|
||||
onprogress: (),
|
||||
|
||||
#[webapi(data_property = "onload", init = "null")]
|
||||
#[webapi(slot = FILE_READER_ONLOAD_SLOT, init = "null")]
|
||||
onload: (),
|
||||
|
||||
#[webapi(data_property = "onloadend", init = "null")]
|
||||
onloadend: (),
|
||||
#[webapi(slot = FILE_READER_ONABORT_SLOT, init = "null")]
|
||||
onabort: (),
|
||||
|
||||
#[webapi(data_property = "onerror", init = "null")]
|
||||
#[webapi(slot = FILE_READER_ONERROR_SLOT, init = "null")]
|
||||
onerror: (),
|
||||
|
||||
#[webapi(data_property = "onabort", init = "null")]
|
||||
onabort: (),
|
||||
#[webapi(slot = FILE_READER_ONLOADEND_SLOT, init = "null")]
|
||||
onloadend: (),
|
||||
|
||||
#[webapi(slot = FILE_READER_LISTENERS_SLOT, init = "null_object")]
|
||||
listeners: (),
|
||||
@@ -74,8 +87,95 @@ struct FileReaderPrototypeAccessorsDeclaration {
|
||||
|
||||
#[webapi(accessor_property, getter = file_reader_error_getter_callback, enumerable)]
|
||||
error: (),
|
||||
|
||||
#[webapi(
|
||||
accessor_property,
|
||||
getter = file_reader_event_handler_getter_callback,
|
||||
setter = file_reader_event_handler_setter_callback,
|
||||
data = crate::util::callback_data_index_value(scope, 0),
|
||||
enumerable
|
||||
)]
|
||||
onloadstart: (),
|
||||
|
||||
#[webapi(
|
||||
accessor_property,
|
||||
getter = file_reader_event_handler_getter_callback,
|
||||
setter = file_reader_event_handler_setter_callback,
|
||||
data = crate::util::callback_data_index_value(scope, 1),
|
||||
enumerable
|
||||
)]
|
||||
onprogress: (),
|
||||
|
||||
#[webapi(
|
||||
accessor_property,
|
||||
getter = file_reader_event_handler_getter_callback,
|
||||
setter = file_reader_event_handler_setter_callback,
|
||||
data = crate::util::callback_data_index_value(scope, 2),
|
||||
enumerable
|
||||
)]
|
||||
onload: (),
|
||||
|
||||
#[webapi(
|
||||
accessor_property,
|
||||
getter = file_reader_event_handler_getter_callback,
|
||||
setter = file_reader_event_handler_setter_callback,
|
||||
data = crate::util::callback_data_index_value(scope, 3),
|
||||
enumerable
|
||||
)]
|
||||
onabort: (),
|
||||
|
||||
#[webapi(
|
||||
accessor_property,
|
||||
getter = file_reader_event_handler_getter_callback,
|
||||
setter = file_reader_event_handler_setter_callback,
|
||||
data = crate::util::callback_data_index_value(scope, 4),
|
||||
enumerable
|
||||
)]
|
||||
onerror: (),
|
||||
|
||||
#[webapi(
|
||||
accessor_property,
|
||||
getter = file_reader_event_handler_getter_callback,
|
||||
setter = file_reader_event_handler_setter_callback,
|
||||
data = crate::util::callback_data_index_value(scope, 5),
|
||||
enumerable
|
||||
)]
|
||||
onloadend: (),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct FileReaderEventHandler {
|
||||
event_type: &'static str,
|
||||
slot_name: &'static str,
|
||||
}
|
||||
|
||||
const FILE_READER_EVENT_HANDLERS: &[FileReaderEventHandler] = &[
|
||||
FileReaderEventHandler {
|
||||
event_type: "loadstart",
|
||||
slot_name: FILE_READER_ONLOADSTART_SLOT,
|
||||
},
|
||||
FileReaderEventHandler {
|
||||
event_type: "progress",
|
||||
slot_name: FILE_READER_ONPROGRESS_SLOT,
|
||||
},
|
||||
FileReaderEventHandler {
|
||||
event_type: "load",
|
||||
slot_name: FILE_READER_ONLOAD_SLOT,
|
||||
},
|
||||
FileReaderEventHandler {
|
||||
event_type: "abort",
|
||||
slot_name: FILE_READER_ONABORT_SLOT,
|
||||
},
|
||||
FileReaderEventHandler {
|
||||
event_type: "error",
|
||||
slot_name: FILE_READER_ONERROR_SLOT,
|
||||
},
|
||||
FileReaderEventHandler {
|
||||
event_type: "loadend",
|
||||
slot_name: FILE_READER_ONLOADEND_SLOT,
|
||||
},
|
||||
];
|
||||
|
||||
pub(in crate::context_bootstrap::file_api) fn install_file_reader_template_bindings<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_, ()>,
|
||||
template: v8::Local<'s, v8::FunctionTemplate>,
|
||||
@@ -95,6 +195,10 @@ fn file_reader_ready_state_getter_callback<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if !file_reader_receiver_branded(scope, args.this()) {
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return;
|
||||
}
|
||||
let ready_state = file_reader_ready_state(scope, args.this());
|
||||
rv.set(v8::Number::new(scope, ready_state).into());
|
||||
}
|
||||
@@ -104,6 +208,10 @@ fn file_reader_result_getter_callback<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if !file_reader_receiver_branded(scope, args.this()) {
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return;
|
||||
}
|
||||
let result = args.this();
|
||||
let result = file_reader_slot_value(scope, result, FILE_READER_RESULT_SLOT)
|
||||
.unwrap_or_else(|| v8::null(scope).into());
|
||||
@@ -115,12 +223,77 @@ fn file_reader_error_getter_callback<'s>(
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if !file_reader_receiver_branded(scope, args.this()) {
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return;
|
||||
}
|
||||
let error = args.this();
|
||||
let error = file_reader_slot_value(scope, error, FILE_READER_ERROR_SLOT)
|
||||
.unwrap_or_else(|| v8::null(scope).into());
|
||||
rv.set(error);
|
||||
}
|
||||
|
||||
fn file_reader_event_handler_getter_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if !file_reader_receiver_branded(scope, args.this()) {
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return;
|
||||
}
|
||||
let Some(handler) = callback_data_item(
|
||||
scope,
|
||||
&args,
|
||||
FILE_READER_EVENT_HANDLERS,
|
||||
"FileReader event handlers",
|
||||
) else {
|
||||
rv.set_null();
|
||||
return;
|
||||
};
|
||||
let value = get_private_value(scope, args.this(), handler.slot_name)
|
||||
.unwrap_or_else(|| v8::null(scope).into());
|
||||
if value.is_null_or_undefined() {
|
||||
rv.set_null();
|
||||
} else {
|
||||
rv.set(value);
|
||||
}
|
||||
}
|
||||
|
||||
fn file_reader_event_handler_setter_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
_rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if !file_reader_receiver_branded(scope, args.this()) {
|
||||
throw_type_error(scope, "Illegal invocation");
|
||||
return;
|
||||
}
|
||||
let Some(handler) = callback_data_item(
|
||||
scope,
|
||||
&args,
|
||||
FILE_READER_EVENT_HANDLERS,
|
||||
"FileReader event handlers",
|
||||
) else {
|
||||
return;
|
||||
};
|
||||
let value = args.get(0);
|
||||
let stored = if value.is_function() {
|
||||
value
|
||||
} else {
|
||||
v8::null(scope).into()
|
||||
};
|
||||
set_private_value(scope, args.this(), handler.slot_name, stored);
|
||||
simple_object_event_set_ordered_handler(
|
||||
scope,
|
||||
args.this(),
|
||||
FILE_READER_LISTENERS_SLOT,
|
||||
handler.event_type,
|
||||
handler.slot_name,
|
||||
stored.is_function(),
|
||||
);
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap::file_api::file_reader) fn initialize_file_reader_object<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
reader: v8::Local<'s, v8::Object>,
|
||||
@@ -130,6 +303,14 @@ pub(in crate::context_bootstrap::file_api::file_reader) fn initialize_file_reade
|
||||
.expect("FileReader declaration should initialize");
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap::file_api::file_reader) fn file_reader_receiver_branded<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
receiver: v8::Local<'s, v8::Object>,
|
||||
) -> bool {
|
||||
get_private_value(scope, receiver, FILE_READER_BRAND_SLOT)
|
||||
.is_some_and(|value| value.boolean_value(scope))
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap::file_api::file_reader) fn file_reader_ready_state<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
reader: v8::Local<'s, v8::Object>,
|
||||
|
||||
@@ -961,7 +961,7 @@ const CONSTRUCTOR_SPECS_AFTER_STREAMS: &[ConstructorSpec] = &[
|
||||
},
|
||||
ConstructorSpec {
|
||||
interface: web_api_interfaces::FileList::DESCRIPTOR,
|
||||
kind: ConstructorKind::FileList,
|
||||
kind: ConstructorKind::Illegal,
|
||||
},
|
||||
ConstructorSpec {
|
||||
interface: web_api_interfaces::FileSystem::DESCRIPTOR,
|
||||
|
||||
@@ -71,7 +71,6 @@ pub(in crate::context_bootstrap) enum ConstructorKind {
|
||||
WebGlDebugRendererInfo,
|
||||
WebGlLoseContext,
|
||||
File,
|
||||
FileList,
|
||||
FileReader,
|
||||
FileReaderSync,
|
||||
XmlSerializer,
|
||||
|
||||
+88
-5
@@ -62,6 +62,9 @@ fn file_reader_backing_state_is_not_own_property_surface() {
|
||||
].join(":");
|
||||
};
|
||||
const reader = new FileReader();
|
||||
const eventHandlerNames = [
|
||||
"onloadstart", "onprogress", "onload", "onabort", "onerror", "onloadend"
|
||||
];
|
||||
const leaked = () => Object.getOwnPropertyNames(reader)
|
||||
.filter(name => name.startsWith("__lmFileReader") ||
|
||||
name.startsWith("__moliFileReader"))
|
||||
@@ -74,6 +77,19 @@ fn file_reader_backing_state_is_not_own_property_surface() {
|
||||
__lmFileReaderResult: "spoofed",
|
||||
__lmFileReaderError: "spoofed"
|
||||
};
|
||||
const throwsTypeError = callback => {
|
||||
try {
|
||||
callback();
|
||||
return false;
|
||||
} catch (error) {
|
||||
return error instanceof TypeError;
|
||||
}
|
||||
};
|
||||
const handler = () => {};
|
||||
reader.onload = handler;
|
||||
const handlerRoundTrips = reader.onload === handler;
|
||||
reader.onload = {};
|
||||
const nonCallableHandlerBecomesNull = reader.onload === null;
|
||||
const before = leaked();
|
||||
reader.addEventListener("load", () => {});
|
||||
reader.readAsText(new Blob(["abc"]));
|
||||
@@ -82,7 +98,8 @@ fn file_reader_backing_state_is_not_own_property_surface() {
|
||||
descriptors: [
|
||||
descriptorReport("readyState"),
|
||||
descriptorReport("result"),
|
||||
descriptorReport("error")
|
||||
descriptorReport("error"),
|
||||
...eventHandlerNames.map(descriptorReport)
|
||||
],
|
||||
constructorConstants: ["EMPTY", "LOADING", "DONE"].map(name =>
|
||||
constantDescriptor(FileReader, name)
|
||||
@@ -93,12 +110,18 @@ fn file_reader_backing_state_is_not_own_property_surface() {
|
||||
readerOwnConstants: ["EMPTY", "LOADING", "DONE"].filter(name =>
|
||||
Object.prototype.hasOwnProperty.call(reader, name)
|
||||
),
|
||||
readerOwnHandlers: eventHandlerNames.filter(name =>
|
||||
Object.prototype.hasOwnProperty.call(reader, name)
|
||||
),
|
||||
before,
|
||||
during,
|
||||
resultIsNull: reader.result === null,
|
||||
fakeReadyState: readyStateDescriptor.get.call(fake),
|
||||
fakeResult: String(resultDescriptor.get.call(fake)),
|
||||
fakeError: String(errorDescriptor.get.call(fake))
|
||||
handlerRoundTrips,
|
||||
nonCallableHandlerBecomesNull,
|
||||
fakeReadyStateThrows: throwsTypeError(() => readyStateDescriptor.get.call(fake)),
|
||||
fakeResultThrows: throwsTypeError(() => resultDescriptor.get.call(fake)),
|
||||
fakeErrorThrows: throwsTypeError(() => errorDescriptor.get.call(fake)),
|
||||
fakeAbortThrows: throwsTypeError(() => FileReader.prototype.abort.call(fake))
|
||||
});
|
||||
})()
|
||||
"#,
|
||||
@@ -107,7 +130,7 @@ fn file_reader_backing_state_is_not_own_property_surface() {
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"{"descriptors":["readyState:function:get readyState:0:undefined:true:true","result:function:get result:0:undefined:true:true","error:function:get error:0:undefined:true:true"],"constructorConstants":["EMPTY:0:true:false:false","LOADING:1:true:false:false","DONE:2:true:false:false"],"prototypeConstants":["EMPTY:0:true:false:false","LOADING:1:true:false:false","DONE:2:true:false:false"],"readerOwnConstants":[],"before":[],"during":[],"resultIsNull":true,"fakeReadyState":0,"fakeResult":"null","fakeError":"null"}"#
|
||||
r#"{"descriptors":["readyState:function:get readyState:0:undefined:true:true","result:function:get result:0:undefined:true:true","error:function:get error:0:undefined:true:true","onloadstart:function:get onloadstart:0:function:true:true","onprogress:function:get onprogress:0:function:true:true","onload:function:get onload:0:function:true:true","onabort:function:get onabort:0:function:true:true","onerror:function:get onerror:0:function:true:true","onloadend:function:get onloadend:0:function:true:true"],"constructorConstants":["EMPTY:0:true:false:false","LOADING:1:true:false:false","DONE:2:true:false:false"],"prototypeConstants":["EMPTY:0:true:false:false","LOADING:1:true:false:false","DONE:2:true:false:false"],"readerOwnConstants":[],"readerOwnHandlers":[],"before":[],"during":[],"resultIsNull":true,"handlerRoundTrips":true,"nonCallableHandlerBecomesNull":true,"fakeReadyStateThrows":true,"fakeResultThrows":true,"fakeErrorThrows":true,"fakeAbortThrows":true}"#
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
@@ -1096,3 +1119,63 @@ fn removing_child_frame_revokes_only_its_blob_object_urls() {
|
||||
"removing a child frame must revoke object URLs created by its realm"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn blob_members_enforce_webidl_receiver_contracts() {
|
||||
let mut vm = new_storage_test_vm("https://blob-receiver-brand.test/");
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const throwsTypeError = callback => {
|
||||
try {
|
||||
callback();
|
||||
return false;
|
||||
} catch (error) {
|
||||
return error instanceof TypeError;
|
||||
}
|
||||
};
|
||||
const rejectionName = callback => {
|
||||
try {
|
||||
return Promise.resolve(callback()).then(
|
||||
() => "resolved",
|
||||
error => error && error.name,
|
||||
);
|
||||
} catch (error) {
|
||||
return Promise.resolve("threw:" + (error && error.name));
|
||||
}
|
||||
};
|
||||
const sizeGetter = Object.getOwnPropertyDescriptor(Blob.prototype, "size").get;
|
||||
const typeGetter = Object.getOwnPropertyDescriptor(Blob.prototype, "type").get;
|
||||
globalThis.__blobReceiverProbe = {
|
||||
syncThrows: [
|
||||
throwsTypeError(() => sizeGetter.call({})),
|
||||
throwsTypeError(() => typeGetter.call({})),
|
||||
throwsTypeError(() => Blob.prototype.slice.call(null)),
|
||||
throwsTypeError(() => Blob.prototype.stream.call(null)),
|
||||
],
|
||||
};
|
||||
Promise.all([
|
||||
rejectionName(() => Blob.prototype.text.call(null)),
|
||||
rejectionName(() => Blob.prototype.arrayBuffer.call(null)),
|
||||
rejectionName(() => Blob.prototype.bytes.call(null)),
|
||||
]).then(names => {
|
||||
__blobReceiverProbe.promiseRejections = names;
|
||||
});
|
||||
return "scheduled";
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("Blob receiver contract probe should evaluate");
|
||||
|
||||
assert_eq!(result, "scheduled");
|
||||
vm.eval("0")
|
||||
.expect("Blob receiver rejection microtasks should drain");
|
||||
|
||||
assert_eq!(
|
||||
vm.eval("JSON.stringify(globalThis.__blobReceiverProbe)")
|
||||
.expect("Blob receiver contract result should evaluate"),
|
||||
r#"{"syncThrows":[true,true,true,true],"promiseRejections":["TypeError","TypeError","TypeError"]}"#
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4583,6 +4583,14 @@ fn file_constructor_preserves_declared_metadata_slots() {
|
||||
__lmFileLastModified: 99
|
||||
};
|
||||
const fakeFileList = { __lmFileListLength: 99 };
|
||||
const throwsTypeError = callback => {
|
||||
try {
|
||||
callback();
|
||||
return false;
|
||||
} catch (error) {
|
||||
return error instanceof TypeError;
|
||||
}
|
||||
};
|
||||
return JSON.stringify({
|
||||
fileDescriptors: [
|
||||
descriptorReport(File.prototype, "name"),
|
||||
@@ -4604,9 +4612,11 @@ fn file_constructor_preserves_declared_metadata_slots() {
|
||||
tag: Object.prototype.toString.call(explicitFile),
|
||||
ctor: explicitFile instanceof File,
|
||||
weirdLastModifiedFinite: Number.isFinite(weirdFile.lastModified),
|
||||
fakeName: nameDescriptor.get.call(fakeFile),
|
||||
fakeLastModifiedFinite: Number.isFinite(lastModifiedDescriptor.get.call(fakeFile)),
|
||||
fakeLength: lengthDescriptor.get.call(fakeFileList)
|
||||
constructFileListThrows: throwsTypeError(() => new FileList()),
|
||||
fakeNameThrows: throwsTypeError(() => nameDescriptor.get.call(fakeFile)),
|
||||
fakeLastModifiedThrows: throwsTypeError(() => lastModifiedDescriptor.get.call(fakeFile)),
|
||||
fakeLengthThrows: throwsTypeError(() => lengthDescriptor.get.call(fakeFileList)),
|
||||
fakeItemThrows: throwsTypeError(() => FileList.prototype.item.call(fakeFileList, 0))
|
||||
});
|
||||
})()
|
||||
"#,
|
||||
@@ -4615,7 +4625,7 @@ fn file_constructor_preserves_declared_metadata_slots() {
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"{"fileDescriptors":["name:function:get name:0:undefined:true:true","lastModified:function:get lastModified:0:undefined:true:true"],"fileListDescriptors":["length:function:get length:0:undefined:true:true"],"fileInternalNames":[],"fileListInternalNames":[],"defaultName":"default.txt","defaultLastModifiedFinite":true,"explicitName":"note.txt","explicitLastModified":7,"explicitType":"text/plain","roundTripName":"note.txt","roundTripLastModified":7,"roundTripType":"text/plain","tag":"[object File]","ctor":true,"weirdLastModifiedFinite":true,"fakeName":"","fakeLastModifiedFinite":true,"fakeLength":0}"#
|
||||
r#"{"fileDescriptors":["name:function:get name:0:undefined:true:true","lastModified:function:get lastModified:0:undefined:true:true"],"fileListDescriptors":["length:function:get length:0:undefined:true:true"],"fileInternalNames":[],"fileListInternalNames":[],"defaultName":"default.txt","defaultLastModifiedFinite":true,"explicitName":"note.txt","explicitLastModified":7,"explicitType":"text/plain","roundTripName":"note.txt","roundTripLastModified":7,"roundTripType":"text/plain","tag":"[object File]","ctor":true,"weirdLastModifiedFinite":true,"constructFileListThrows":true,"fakeNameThrows":true,"fakeLastModifiedThrows":true,"fakeLengthThrows":true,"fakeItemThrows":true}"#
|
||||
);
|
||||
}
|
||||
#[test]
|
||||
|
||||
@@ -2603,19 +2603,23 @@ async fn worker_filelist_interface_object_is_available() {
|
||||
ensure_v8();
|
||||
let mut handle = spawn_worker(
|
||||
r#"
|
||||
const file = new File(["hello"], "note.txt", { type: "text/plain" });
|
||||
const list = new FileList([file]);
|
||||
let constructError = null;
|
||||
try {
|
||||
new FileList();
|
||||
} catch (error) {
|
||||
constructError = error && error.name;
|
||||
}
|
||||
postMessage({
|
||||
ctorOwn: Object.prototype.hasOwnProperty.call(self, "FileList"),
|
||||
ctorType: typeof FileList,
|
||||
ctorName: list.constructor && list.constructor.name,
|
||||
tag: Object.prototype.toString.call(list),
|
||||
instanceofFileList: list instanceof FileList,
|
||||
length: list.length,
|
||||
firstName: list.item(0) && list.item(0).name,
|
||||
indexName: list[0] && list[0].name,
|
||||
iterType: typeof list[Symbol.iterator],
|
||||
iterName: Array.from(list).map(file => file.name).join(","),
|
||||
ctorName: FileList.name,
|
||||
constructError,
|
||||
itemType: typeof FileList.prototype.item,
|
||||
lengthGetterType: typeof Object.getOwnPropertyDescriptor(
|
||||
FileList.prototype,
|
||||
"length",
|
||||
).get,
|
||||
iterType: typeof FileList.prototype[Symbol.iterator],
|
||||
});
|
||||
close();
|
||||
"#
|
||||
@@ -2629,7 +2633,7 @@ async fn worker_filelist_interface_object_is_available() {
|
||||
.expect("channel closed");
|
||||
assert_eq!(
|
||||
expect_post_json(msg),
|
||||
r#"{"ctorOwn":true,"ctorType":"function","ctorName":"FileList","tag":"[object FileList]","instanceofFileList":true,"length":1,"firstName":"note.txt","indexName":"note.txt","iterType":"function","iterName":"note.txt"}"#
|
||||
r#"{"ctorOwn":true,"ctorType":"function","ctorName":"FileList","constructError":"TypeError","itemType":"function","lengthGetterType":"function","iterType":"function"}"#
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,14 @@ function assert_throws_name(expectedName, callback, description) {
|
||||
throw new Error(description || "expected exception");
|
||||
}
|
||||
|
||||
function file_list(files) {
|
||||
const transfer = new DataTransfer();
|
||||
for (const file of files) {
|
||||
transfer.items.add(file);
|
||||
}
|
||||
return transfer.files;
|
||||
}
|
||||
|
||||
promise_test(async function () {
|
||||
const file = new File(["hello ", new Uint8Array([0xe2, 0x99, 0xa5])], "note.txt", {
|
||||
type: "TEXT/PLAIN",
|
||||
@@ -33,7 +41,8 @@ test(function () {
|
||||
const file = new File(["hello"], "note.txt");
|
||||
const second = new File(["second"], "second.txt");
|
||||
const third = new File(["third"], "third.txt");
|
||||
const list = new FileList([file, second, third]);
|
||||
assert_throws_name("TypeError", () => new FileList(), "FileList is not constructible");
|
||||
const list = file_list([file, second, third]);
|
||||
|
||||
assert_true(list instanceof FileList, "FileList instance");
|
||||
assert_equals(list.length, 3, "FileList length");
|
||||
|
||||
+12
-4
@@ -54,6 +54,14 @@ function append_target_frame(name) {
|
||||
return frame;
|
||||
}
|
||||
|
||||
function file_list(files) {
|
||||
const transfer = new DataTransfer();
|
||||
for (const file of files) {
|
||||
transfer.items.add(file);
|
||||
}
|
||||
return transfer.files;
|
||||
}
|
||||
|
||||
test(function () {
|
||||
const text = document.createElement("input");
|
||||
assert_equals(text.files, null, "files should not apply to non-file inputs");
|
||||
@@ -66,7 +74,7 @@ test(function () {
|
||||
assert_equals(input.files, empty, "files getter should return the same object until replaced");
|
||||
|
||||
const file = new File(["file-body"], "note.txt", { type: "text/plain" });
|
||||
const files = new FileList([file]);
|
||||
const files = file_list([file]);
|
||||
input.files = files;
|
||||
assert_equals(input.files, files, "files setter should install the selected FileList");
|
||||
}, "HTMLInputElement.files exposes a replaceable FileList for file inputs");
|
||||
@@ -80,7 +88,7 @@ test(function () {
|
||||
upload.type = "file";
|
||||
upload.name = "upload";
|
||||
upload.multiple = true;
|
||||
upload.files = new FileList([
|
||||
upload.files = file_list([
|
||||
new File(["alpha-body"], "alpha.txt", { type: "text/plain" }),
|
||||
new File(["beta-body"], "beta.bin"),
|
||||
]);
|
||||
@@ -115,7 +123,7 @@ promise_test(async function () {
|
||||
const upload = document.createElement("input");
|
||||
upload.type = "file";
|
||||
upload.name = "upload";
|
||||
upload.files = new FileList([
|
||||
upload.files = file_list([
|
||||
new File(["file-body"], 'report"name.txt', { type: "text/custom" }),
|
||||
]);
|
||||
const emptyInput = document.createElement("input");
|
||||
@@ -180,7 +188,7 @@ promise_test(async function () {
|
||||
externalUpload.type = "file";
|
||||
externalUpload.name = "upload";
|
||||
externalUpload.multiple = true;
|
||||
externalUpload.files = new FileList([
|
||||
externalUpload.files = file_list([
|
||||
new File(["external-alpha-body"], "external-alpha.txt", { type: "text/external-alpha" }),
|
||||
new File(["external-beta-body"], "external-beta.bin"),
|
||||
]);
|
||||
|
||||
@@ -2,17 +2,23 @@ self.onmessage = async function (event) {
|
||||
const mode = event.data && event.data.mode;
|
||||
|
||||
if (mode === "filelist") {
|
||||
const file = new File(["hello"], "note.txt", { type: "text/plain" });
|
||||
const list = new FileList([file]);
|
||||
let constructError = null;
|
||||
try {
|
||||
new FileList();
|
||||
} catch (error) {
|
||||
constructError = error && error.name;
|
||||
}
|
||||
postMessage({
|
||||
ctorOwn: Object.prototype.hasOwnProperty.call(self, "FileList"),
|
||||
ctorType: typeof FileList,
|
||||
ctorName: list.constructor && list.constructor.name,
|
||||
tag: Object.prototype.toString.call(list),
|
||||
instanceofFileList: list instanceof FileList,
|
||||
length: list.length,
|
||||
firstName: list.item(0) && list.item(0).name,
|
||||
indexName: list[0] && list[0].name,
|
||||
ctorName: FileList.name,
|
||||
constructError: constructError,
|
||||
itemType: typeof FileList.prototype.item,
|
||||
lengthGetterType: typeof Object.getOwnPropertyDescriptor(
|
||||
FileList.prototype,
|
||||
"length",
|
||||
).get,
|
||||
iterType: typeof FileList.prototype[Symbol.iterator],
|
||||
});
|
||||
close();
|
||||
return;
|
||||
|
||||
@@ -42,11 +42,10 @@ promise_test(async function () {
|
||||
assert_equals(message.data.ctorOwn, true, "worker should expose FileList");
|
||||
assert_equals(message.data.ctorType, "function", "FileList constructor type");
|
||||
assert_equals(message.data.ctorName, "FileList", "FileList constructor name");
|
||||
assert_equals(message.data.tag, "[object FileList]", "FileList object brand");
|
||||
assert_equals(message.data.instanceofFileList, true, "FileList instanceof");
|
||||
assert_equals(message.data.length, 1, "FileList length");
|
||||
assert_equals(message.data.firstName, "note.txt", "FileList.item(0)");
|
||||
assert_equals(message.data.indexName, "note.txt", "FileList index getter");
|
||||
assert_equals(message.data.constructError, "TypeError", "FileList is not constructible");
|
||||
assert_equals(message.data.itemType, "function", "FileList.item is exposed");
|
||||
assert_equals(message.data.lengthGetterType, "function", "FileList.length is exposed");
|
||||
assert_equals(message.data.iterType, "function", "FileList iterator is exposed");
|
||||
} finally {
|
||||
worker.terminate();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user