mirror of
https://github.com/lexmount/moli.git
synced 2026-10-25 08:01:49 +00:00
refactor(clipboard): derive collections and option dictionaries
Replace ClipboardItemReference and collection/options adapters with interface sequences, dictionary fields and a raw record field. Retain record projection, storage snapshots and Promise rejection behavior. Cover iterator failure identity and conversion stopping without modifying clipboard data. Validation on the combined tree: cargo fmt --all; cargo clippy --workspace --all-targets --all-features -- -D warnings; cargo nextest run --no-fail-fast (19639 passed, 16 skipped; CARGO_BUILD_JOBS=1).
This commit is contained in:
@@ -88,52 +88,31 @@ struct ClipboardItemOptions {
|
||||
#[derive(Default, webidl::WebIdlDictionary)]
|
||||
#[webidl(prefix = "ClipboardUnsanitizedFormats")]
|
||||
struct ClipboardUnsanitizedFormats {
|
||||
#[webidl(with = clipboard_unsanitized_formats_member)]
|
||||
#[webidl(sequence)]
|
||||
unsanitized: Option<Vec<String>>,
|
||||
}
|
||||
|
||||
#[derive(webidl::WebIdlArgs)]
|
||||
#[webidl(prefix = "ClipboardItem")]
|
||||
struct ClipboardItemConstructorArgs<'scope> {
|
||||
#[webidl(required, with = clipboard_item_record_arg)]
|
||||
items: Vec<(String, v8::Local<'scope, v8::Promise>)>,
|
||||
#[webidl(required, converter = "raw")]
|
||||
items: webidl::Record<webidl::DomString, v8::Local<'scope, v8::Promise>>,
|
||||
|
||||
#[webidl(with = clipboard_item_options_arg)]
|
||||
#[webidl(dictionary)]
|
||||
options: ClipboardItemOptions,
|
||||
}
|
||||
|
||||
struct ClipboardItemReference<'scope>(v8::Local<'scope, v8::Object>);
|
||||
|
||||
impl<'scope> webidl::WebIdlConverter<'scope> for ClipboardItemReference<'scope> {
|
||||
type Options = ();
|
||||
|
||||
fn convert(
|
||||
scope: &mut v8::PinScope<'scope, '_>,
|
||||
value: v8::Local<'scope, v8::Value>,
|
||||
context: webidl::Context,
|
||||
_options: &Self::Options,
|
||||
) -> std::result::Result<Self, webidl::WebIdlError> {
|
||||
let object = webidl::convert::<v8::Local<'scope, v8::Object>>(scope, value, context)?;
|
||||
if !clipboard_item_receiver_branded(scope, object) {
|
||||
return Err(webidl::WebIdlError::custom_message(
|
||||
"Clipboard.write data must contain ClipboardItem objects.",
|
||||
));
|
||||
}
|
||||
Ok(Self(object))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(webidl::WebIdlArgs)]
|
||||
#[webidl(prefix = "Clipboard.read")]
|
||||
struct ClipboardReadArgs {
|
||||
#[webidl(with = clipboard_read_formats_arg)]
|
||||
#[webidl(dictionary)]
|
||||
formats: ClipboardUnsanitizedFormats,
|
||||
}
|
||||
|
||||
#[derive(webidl::WebIdlArgs)]
|
||||
#[webidl(prefix = "Clipboard.write")]
|
||||
struct ClipboardWriteArgs<'scope> {
|
||||
#[webidl(required, with = clipboard_write_data_arg)]
|
||||
#[webidl(required, sequence, interface = web_api_interfaces::ClipboardItem)]
|
||||
data: Vec<v8::Local<'scope, v8::Object>>,
|
||||
}
|
||||
|
||||
@@ -202,7 +181,7 @@ pub(in crate::context_bootstrap) fn clipboard_item_constructor_callback<'s>(
|
||||
let Some(parsed) = webidl::parse_args::<ClipboardItemConstructorArgs<'s>>(scope, &args) else {
|
||||
return;
|
||||
};
|
||||
if parsed.items.is_empty() {
|
||||
if parsed.items.0.is_empty() {
|
||||
throw_type_error(
|
||||
scope,
|
||||
"Failed to construct 'ClipboardItem': The items record must not be empty.",
|
||||
@@ -212,7 +191,12 @@ pub(in crate::context_bootstrap) fn clipboard_item_constructor_callback<'s>(
|
||||
if !initialize_clipboard_item(
|
||||
scope,
|
||||
args.this(),
|
||||
parsed.items,
|
||||
parsed
|
||||
.items
|
||||
.0
|
||||
.into_iter()
|
||||
.map(|(mime_type, value)| (mime_type.0, value))
|
||||
.collect(),
|
||||
parsed.options.presentation_style,
|
||||
) {
|
||||
throw_type_error(scope, "Failed to initialize ClipboardItem data.");
|
||||
@@ -221,82 +205,6 @@ pub(in crate::context_bootstrap) fn clipboard_item_constructor_callback<'s>(
|
||||
rv.set(args.this().into());
|
||||
}
|
||||
|
||||
fn clipboard_item_options_arg<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: &v8::FunctionCallbackArguments<'s>,
|
||||
index: i32,
|
||||
) -> std::result::Result<ClipboardItemOptions, webidl::WebIdlError> {
|
||||
let context = webidl::Context::argument("ClipboardItem", (index + 1) as usize);
|
||||
webidl::dictionary_arg(args, index, context)?
|
||||
.map(|object| webidl::parse_dictionary_object(scope, object))
|
||||
.transpose()
|
||||
.map(|options| options.unwrap_or_default())
|
||||
}
|
||||
|
||||
fn clipboard_item_record_arg<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: &v8::FunctionCallbackArguments<'s>,
|
||||
index: i32,
|
||||
) -> std::result::Result<Vec<(String, v8::Local<'s, v8::Promise>)>, webidl::WebIdlError> {
|
||||
let context = webidl::Context::argument("ClipboardItem", (index + 1) as usize);
|
||||
if args.length() <= index {
|
||||
return Err(webidl::WebIdlError::missing_required(context));
|
||||
}
|
||||
webidl::convert::<webidl::Record<webidl::DomString, v8::Local<'s, v8::Promise>>>(
|
||||
scope,
|
||||
args.get(index),
|
||||
context,
|
||||
)
|
||||
.map(|record| {
|
||||
record
|
||||
.0
|
||||
.into_iter()
|
||||
.map(|(mime_type, value)| (mime_type.0, value))
|
||||
.collect()
|
||||
})
|
||||
}
|
||||
|
||||
fn clipboard_unsanitized_formats_member<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
object: v8::Local<'s, v8::Object>,
|
||||
name: &'static str,
|
||||
) -> std::result::Result<Option<Vec<String>>, webidl::WebIdlError> {
|
||||
let context = webidl::Context::member("ClipboardUnsanitizedFormats", name);
|
||||
let Some(value) = webidl::property_result(scope, object, name, context)? else {
|
||||
return Ok(None);
|
||||
};
|
||||
if value.is_undefined() {
|
||||
return Ok(None);
|
||||
}
|
||||
webidl::convert::<webidl::Sequence<webidl::DomString>>(scope, value, context)
|
||||
.map(|sequence| Some(sequence.0.into_iter().map(|format| format.0).collect()))
|
||||
}
|
||||
|
||||
fn clipboard_write_data_arg<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: &v8::FunctionCallbackArguments<'s>,
|
||||
index: i32,
|
||||
) -> std::result::Result<Vec<v8::Local<'s, v8::Object>>, webidl::WebIdlError> {
|
||||
let context = webidl::Context::argument("Clipboard.write", (index + 1) as usize);
|
||||
if args.length() <= index {
|
||||
return Err(webidl::WebIdlError::missing_required(context));
|
||||
}
|
||||
webidl::convert::<webidl::Sequence<ClipboardItemReference<'s>>>(scope, args.get(index), context)
|
||||
.map(|sequence| sequence.0.into_iter().map(|item| item.0).collect())
|
||||
}
|
||||
|
||||
fn clipboard_read_formats_arg<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: &v8::FunctionCallbackArguments<'s>,
|
||||
index: i32,
|
||||
) -> std::result::Result<ClipboardUnsanitizedFormats, webidl::WebIdlError> {
|
||||
let context = webidl::Context::argument("Clipboard.read", (index + 1) as usize);
|
||||
webidl::dictionary_arg(args, index, context)?
|
||||
.map(|object| webidl::parse_dictionary_object(scope, object))
|
||||
.transpose()
|
||||
.map(|formats| formats.unwrap_or_default())
|
||||
}
|
||||
|
||||
fn initialize_clipboard_item<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
object: v8::Local<'s, v8::Object>,
|
||||
|
||||
@@ -39,6 +39,40 @@ fn assert_clipboard_probe(vm: &mut ScriptVm, script: &str) {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clipboard_write_interface_sequence_preserves_iteration_and_promise_failures() {
|
||||
let mut vm = new_storage_test_vm("https://clipboard-sequence.test/");
|
||||
assert_clipboard_probe(
|
||||
&mut vm,
|
||||
r#"
|
||||
(async () => {
|
||||
const clipboard = navigator.clipboard;
|
||||
const item = new ClipboardItem({'text/plain': 'kept'}, null);
|
||||
Object.setPrototypeOf(item, null);
|
||||
await clipboard.write(new Set([item]));
|
||||
if (await clipboard.readText() !== 'kept') return 'native item after prototype mutation';
|
||||
let nextCalls = 0, closeCalls = 0;
|
||||
const invalid = {[Symbol.iterator]() {
|
||||
return {
|
||||
next() {nextCalls++; return {done: false, value: {}};},
|
||||
return() {closeCalls++; return {done: true};}
|
||||
};
|
||||
}};
|
||||
const promise = clipboard.write(invalid);
|
||||
if (!(promise instanceof Promise)) return 'conversion failure did not return a Promise';
|
||||
let error;
|
||||
try {await promise;} catch (caught) {error = caught;}
|
||||
if (!(error instanceof TypeError) || nextCalls !== 1 || closeCalls !== 0) return 'failed item iteration';
|
||||
const marker = {};
|
||||
try {await clipboard.write({get [Symbol.iterator]() {throw marker;}});} catch (caught) {error = caught;}
|
||||
if (error !== marker) return 'iterator exception identity';
|
||||
if (await clipboard.readText() !== 'kept') return 'failed conversion changed stored data';
|
||||
return 'ok';
|
||||
})()
|
||||
"#,
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn clipboard_storage_reads_new_items_and_blobs_from_byte_snapshots() {
|
||||
let mut vm = new_storage_test_vm("https://clipboard-storage.test/");
|
||||
|
||||
Reference in New Issue
Block a user