mirror of
https://github.com/lexmount/moli.git
synced 2026-10-05 00:00:55 +00:00
fix(clipboard): implement async clipboard interfaces
This commit is contained in:
@@ -28,6 +28,7 @@ use super::super::{
|
||||
},
|
||||
media_source::media_source_constructor_callback,
|
||||
message_ports::{message_channel_constructor_callback, message_port_constructor_callback},
|
||||
navigator_runtime::clipboard_item_constructor_callback,
|
||||
notification_runtime::notification_constructor_callback,
|
||||
performance_runtime::performance_observer_constructor_callback,
|
||||
range_surface::{
|
||||
@@ -108,6 +109,11 @@ pub(in crate::context_bootstrap) fn build_constructor_template<'s>(
|
||||
ConstructorKind::ClipboardEvent => {
|
||||
build_event_subclass_template(scope, EventSubclassKind::ClipboardEvent)
|
||||
}
|
||||
ConstructorKind::ClipboardItem => {
|
||||
v8::FunctionTemplate::builder(clipboard_item_constructor_callback)
|
||||
.length(1)
|
||||
.build(scope)
|
||||
}
|
||||
ConstructorKind::KeyboardEvent => {
|
||||
build_event_subclass_template(scope, EventSubclassKind::KeyboardEvent)
|
||||
}
|
||||
|
||||
@@ -80,7 +80,13 @@ pub(in crate::context_bootstrap) const WORKER_SHARED_INTERFACE_NAMES: &[&str] =
|
||||
"FormData",
|
||||
];
|
||||
|
||||
const SECURE_CONTEXT_ONLY_INTERFACE_NAMES: &[&str] = &["SubtleCrypto", "CryptoKey", "IdleDetector"];
|
||||
const SECURE_CONTEXT_ONLY_INTERFACE_NAMES: &[&str] = &[
|
||||
"SubtleCrypto",
|
||||
"CryptoKey",
|
||||
"IdleDetector",
|
||||
"Clipboard",
|
||||
"ClipboardItem",
|
||||
];
|
||||
const WORKER_ONLY_INTERFACE_NAMES: &[&str] = &["WorkerNavigator", "WorkerLocation"];
|
||||
const WINDOW_DEDICATED_AND_SHARED_WORKER_INTERFACE_NAMES: &[&str] = &[
|
||||
"Worker",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
mod clipboard;
|
||||
mod collections;
|
||||
mod geolocation;
|
||||
mod media_capabilities;
|
||||
@@ -7,6 +8,7 @@ mod screen;
|
||||
mod visual_viewport;
|
||||
mod window_state;
|
||||
|
||||
pub(in crate::context_bootstrap) use self::clipboard::clipboard_item_constructor_callback;
|
||||
pub(crate) use self::navigator::build_lightweight_popup_window_navigator_object;
|
||||
pub(super) use self::navigator::install_navigator_template_bindings;
|
||||
pub(crate) use self::navigator::install_worker_navigator_runtime_state;
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -21,6 +21,7 @@ use super::super::window_runtime::{
|
||||
service_worker_object_set_owner_scope,
|
||||
};
|
||||
use super::super::*;
|
||||
use super::clipboard::{build_clipboard_object, install_clipboard_template_bindings};
|
||||
use super::collections::{
|
||||
build_navigator_plugin_collections, install_navigator_collection_template_bindings,
|
||||
};
|
||||
@@ -106,7 +107,6 @@ const SERVICE_WORKER_CONTAINER_ONMESSAGEERROR_SLOT: &str =
|
||||
const SERVICE_WORKER_CONTAINER_ONCONTROLLERCHANGE_SLOT: &str =
|
||||
"__moliServiceWorkerContainerOncontrollerchange";
|
||||
const SERVICE_WORKER_CONTAINER_CONTROLLER_SLOT: &str = "__moliServiceWorkerContainerController";
|
||||
const CLIPBOARD_TEXT_SLOT: &str = "__moliClipboardText";
|
||||
pub(in crate::context_bootstrap) const SERVICE_WORKER_OWNER_TOKEN_SLOT: &str =
|
||||
"__moliServiceWorkerOwner";
|
||||
const USER_ACTIVATION_BRAND_SLOT: &str = "__moliUserActivationBrand";
|
||||
@@ -489,19 +489,6 @@ struct ServiceWorkerContainerDeclaration {
|
||||
oncontrollerchange: (),
|
||||
}
|
||||
|
||||
#[derive(Default, WebApiObject)]
|
||||
#[webapi(interface = "Object")]
|
||||
struct ClipboardObjectDeclaration {
|
||||
#[webapi(slot = CLIPBOARD_TEXT_SLOT, init = "")]
|
||||
text: (),
|
||||
|
||||
#[webapi(method, enumerable, length = 0, callback = clipboard_read_text_callback)]
|
||||
read_text: (),
|
||||
|
||||
#[webapi(method, enumerable, length = 1, callback = clipboard_write_text_callback)]
|
||||
write_text: (),
|
||||
}
|
||||
|
||||
#[derive(Default, WebApiObject)]
|
||||
#[webapi(interface = "UserActivation")]
|
||||
struct UserActivationObjectDeclaration {
|
||||
@@ -752,44 +739,6 @@ fn navigator_user_activation_state_getter_callback<'s>(
|
||||
rv.set(v8::Boolean::new(scope, active).into());
|
||||
}
|
||||
|
||||
fn set_resolved_promise(
|
||||
scope: &mut v8::PinScope<'_, '_>,
|
||||
rv: &mut v8::ReturnValue<'_, v8::Value>,
|
||||
value: v8::Local<'_, v8::Value>,
|
||||
) {
|
||||
let Some(resolver) = v8::PromiseResolver::new(scope) else {
|
||||
rv.set(v8::undefined(scope).into());
|
||||
return;
|
||||
};
|
||||
let promise = resolver.get_promise(scope);
|
||||
let _ = resolver.resolve(scope, value);
|
||||
rv.set(promise.into());
|
||||
}
|
||||
|
||||
fn clipboard_read_text_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let value = get_private_value(scope, args.this(), CLIPBOARD_TEXT_SLOT)
|
||||
.filter(|value| value.is_string())
|
||||
.unwrap_or_else(|| v8::String::empty(scope).into());
|
||||
set_resolved_promise(scope, &mut rv, value);
|
||||
}
|
||||
|
||||
fn clipboard_write_text_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
let text = args
|
||||
.get(0)
|
||||
.to_string(scope)
|
||||
.unwrap_or_else(|| v8::String::empty(scope));
|
||||
set_private_value(scope, args.this(), CLIPBOARD_TEXT_SLOT, text.into());
|
||||
set_resolved_promise(scope, &mut rv, v8::undefined(scope).into());
|
||||
}
|
||||
|
||||
fn navigator_connection_event_target_noop_callback<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
@@ -866,6 +815,7 @@ pub(in crate::context_bootstrap) fn install_navigator_template_bindings<'s>(
|
||||
template: v8::Local<'s, v8::FunctionTemplate>,
|
||||
interface_name: &str,
|
||||
) {
|
||||
install_clipboard_template_bindings(scope, template, interface_name);
|
||||
install_geolocation_template_bindings(scope, template, interface_name);
|
||||
install_navigator_collection_template_bindings(scope, template, interface_name);
|
||||
install_media_capabilities_template_bindings(scope, template, interface_name);
|
||||
@@ -903,6 +853,7 @@ fn filter_navigator_secure_context_exposure<'s>(
|
||||
secure_context: bool,
|
||||
) -> Result<()> {
|
||||
if !secure_context {
|
||||
delete_object_property(scope, prototype, "clipboard")?;
|
||||
delete_object_property(scope, prototype, "storage")?;
|
||||
delete_object_property(scope, prototype, "storageBuckets")?;
|
||||
delete_object_property(scope, prototype, "serviceWorker")?;
|
||||
@@ -1054,10 +1005,7 @@ pub(super) fn build_lazy_navigator_subobject_in_current_realm<'s>(
|
||||
NavigatorSubobject::ServiceWorker => {
|
||||
build_service_worker_container(scope, owner_child, owner_popup)?.into()
|
||||
}
|
||||
NavigatorSubobject::Clipboard => ClipboardObjectDeclaration::default()
|
||||
.bind(scope)
|
||||
.map_err(|error| anyhow!("failed to bind navigator.clipboard object: {error}"))?
|
||||
.into(),
|
||||
NavigatorSubobject::Clipboard => build_clipboard_object(scope)?.into(),
|
||||
NavigatorSubobject::UserActivation => build_user_activation(scope)?.into(),
|
||||
NavigatorSubobject::StorageBuckets => {
|
||||
build_storage_bucket_manager(scope, owner_child, owner_popup)?.into()
|
||||
|
||||
@@ -1791,6 +1791,8 @@ pub(crate) fn finish_context_bootstrap(
|
||||
("Permissions", "Permissions"),
|
||||
("PermissionStatus", "PermissionStatus"),
|
||||
("MediaDevices", "MediaDevices"),
|
||||
("Clipboard", "Clipboard"),
|
||||
("ClipboardItem", "ClipboardItem"),
|
||||
("MediaCapabilities", "MediaCapabilities"),
|
||||
("Screen", "Screen"),
|
||||
("ScreenOrientation", "ScreenOrientation"),
|
||||
|
||||
@@ -628,6 +628,16 @@ const CONSTRUCTOR_SPECS_BEFORE_STREAMS: &[ConstructorSpec] = &[
|
||||
parent: Some("EventTarget"),
|
||||
kind: ConstructorKind::Illegal,
|
||||
},
|
||||
ConstructorSpec {
|
||||
name: "Clipboard",
|
||||
parent: Some("EventTarget"),
|
||||
kind: ConstructorKind::Illegal,
|
||||
},
|
||||
ConstructorSpec {
|
||||
name: "ClipboardItem",
|
||||
parent: None,
|
||||
kind: ConstructorKind::ClipboardItem,
|
||||
},
|
||||
ConstructorSpec {
|
||||
name: "MediaCapabilities",
|
||||
parent: None,
|
||||
|
||||
@@ -11,6 +11,7 @@ pub(in crate::context_bootstrap) enum ConstructorKind {
|
||||
CapturedMouseEvent,
|
||||
DragEvent,
|
||||
ClipboardEvent,
|
||||
ClipboardItem,
|
||||
KeyboardEvent,
|
||||
InputEvent,
|
||||
WheelEvent,
|
||||
|
||||
@@ -4180,7 +4180,7 @@ fn web_platform_surface_stubs_are_present_and_brand_correctly() {
|
||||
"CompressionStream","DecompressionStream",
|
||||
"ReadableStreamBYOBReader","ReadableStreamBYOBRequest","ReadableByteStreamController",
|
||||
"Geolocation","GeolocationPosition","GeolocationCoordinates","GeolocationPositionError",
|
||||
"MediaCapabilities",
|
||||
"MediaCapabilities","Clipboard","ClipboardItem",
|
||||
];
|
||||
const out = [];
|
||||
for (const name of names) {
|
||||
@@ -4191,6 +4191,7 @@ fn web_platform_surface_stubs_are_present_and_brand_correctly() {
|
||||
out.push(`HashChangeEvent<Event:${HashChangeEvent.prototype instanceof Event}`);
|
||||
out.push(`DOMRect<DOMRectReadOnly:${DOMRect.prototype instanceof DOMRectReadOnly}`);
|
||||
out.push(`DOMPoint<DOMPointReadOnly:${DOMPoint.prototype instanceof DOMPointReadOnly}`);
|
||||
out.push(`Clipboard<EventTarget:${Clipboard.prototype instanceof EventTarget}`);
|
||||
return out.join("|");
|
||||
})()
|
||||
"#,
|
||||
@@ -4226,10 +4227,13 @@ fn web_platform_surface_stubs_are_present_and_brand_correctly() {
|
||||
"GeolocationCoordinates:function:true",
|
||||
"GeolocationPositionError:function:true",
|
||||
"MediaCapabilities:function:true",
|
||||
"Clipboard:function:true",
|
||||
"ClipboardItem:function:true",
|
||||
"ToggleEvent<Event:true",
|
||||
"HashChangeEvent<Event:true",
|
||||
"DOMRect<DOMRectReadOnly:true",
|
||||
"DOMPoint<DOMPointReadOnly:true",
|
||||
"Clipboard<EventTarget:true",
|
||||
];
|
||||
assert_eq!(result, expected_parts.join("|"));
|
||||
}
|
||||
@@ -8096,16 +8100,22 @@ fn navigator_runtime_subobjects_keep_declared_brand_and_methods() {
|
||||
summarizeOwnMethod(mediaDevices, "getUserMedia")
|
||||
].join("|"),
|
||||
clipboard: [
|
||||
clipboard instanceof Clipboard,
|
||||
clipboard instanceof EventTarget,
|
||||
Object.prototype.toString.call(clipboard),
|
||||
Object.hasOwn(clipboard, "readText"),
|
||||
Object.hasOwn(clipboard, "writeText"),
|
||||
Object.keys(clipboard).join(","),
|
||||
Object.hasOwn(Clipboard.prototype, "read"),
|
||||
Object.hasOwn(Clipboard.prototype, "readText"),
|
||||
Object.hasOwn(Clipboard.prototype, "write"),
|
||||
Object.hasOwn(Clipboard.prototype, "writeText"),
|
||||
clipboard.readText && clipboard.readText.name,
|
||||
clipboard.readText && clipboard.readText.length,
|
||||
clipboard.writeText && clipboard.writeText.name,
|
||||
clipboard.writeText && clipboard.writeText.length,
|
||||
summarizeOwnMethod(clipboard, "readText"),
|
||||
summarizeOwnMethod(clipboard, "writeText"),
|
||||
summarizeOwnMethod(Clipboard.prototype, "readText"),
|
||||
summarizeOwnMethod(Clipboard.prototype, "writeText"),
|
||||
Object.prototype.toString.call(clipboard.readText()),
|
||||
Object.prototype.toString.call(clipboard.writeText("clip-text"))
|
||||
].join("|"),
|
||||
@@ -8128,7 +8138,7 @@ fn navigator_runtime_subobjects_keep_declared_brand_and_methods() {
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"{"permissions":"true|[object Permissions]|false|function|1","storage":"true|[object StorageManager]|false|false|false|false||true|true|true|true|persisted|0|persist|0|estimate|0|false:undefined:::::|[object Promise]|[object Promise]","connection":"[object Object]|string|unknown|Infinity|string|4g|10|50|false|true|type,downlinkMax,effectiveType,downlink,rtt,saveData,onchange,addEventListener,removeEventListener||true:function:addEventListener:2:true:true:true|true:function:removeEventListener:2:true:true:true|undefined|undefined|throw:TypeError|throw:TypeError","mediaDevices":"true|[object MediaDevices]|true|true|enumerateDevices,getUserMedia|enumerateDevices|0|getUserMedia|1|true:function:enumerateDevices:0:true:true:true|true:function:getUserMedia:1:true:true:true","clipboard":"[object Object]|true|true|readText,writeText|readText|0|writeText|1|true:function:readText:0:true:true:true|true:function:writeText:1:true:true:true|[object Promise]|[object Promise]","userActivation":"undefined|[object Object]|boolean|false|false|isActive,hasBeenActive|true|throw:TypeError|throw:TypeError"}"#
|
||||
r#"{"permissions":"true|[object Permissions]|false|function|1","storage":"true|[object StorageManager]|false|false|false|false||true|true|true|true|persisted|0|persist|0|estimate|0|false:undefined:::::|[object Promise]|[object Promise]","connection":"[object Object]|string|unknown|Infinity|string|4g|10|50|false|true|type,downlinkMax,effectiveType,downlink,rtt,saveData,onchange,addEventListener,removeEventListener||true:function:addEventListener:2:true:true:true|true:function:removeEventListener:2:true:true:true|undefined|undefined|throw:TypeError|throw:TypeError","mediaDevices":"true|[object MediaDevices]|true|true|enumerateDevices,getUserMedia|enumerateDevices|0|getUserMedia|1|true:function:enumerateDevices:0:true:true:true|true:function:getUserMedia:1:true:true:true","clipboard":"true|true|[object Clipboard]|false|false||true|true|true|true|readText|0|writeText|1|true:function:readText:0:true:true:true|true:function:writeText:1:true:true:true|[object Promise]|[object Promise]","userActivation":"undefined|[object Object]|boolean|false|false|isActive,hasBeenActive|true|throw:TypeError|throw:TypeError"}"#
|
||||
);
|
||||
let receiver_errors = vm
|
||||
.eval(
|
||||
@@ -8150,6 +8160,198 @@ fn navigator_runtime_subobjects_keep_declared_brand_and_methods() {
|
||||
assert_eq!(clipboard_text, "clip-text");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn async_clipboard_interfaces_are_branded_and_round_trip_text_data() {
|
||||
let mut vm = new_storage_test_vm("https://async-clipboard.test/");
|
||||
|
||||
let shape = vm
|
||||
.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const clipboard = navigator.clipboard;
|
||||
const item = new ClipboardItem(
|
||||
{ "text/plain": "hello", "not a/real type": "opaque" },
|
||||
{ presentationStyle: "inline" }
|
||||
);
|
||||
const writableItem = new ClipboardItem({ "text/plain": "hello" });
|
||||
const invalidCustomItem = new ClipboardItem({
|
||||
"application/x-private": new Blob(["x"], {
|
||||
type: "application/x-private"
|
||||
})
|
||||
});
|
||||
const mismatchedCustomItem = new ClipboardItem({
|
||||
"web text/plain": new Blob(["x"], { type: "text/custom" })
|
||||
});
|
||||
const stringPngItem = new ClipboardItem({ "image/png": "not an image" });
|
||||
const method = (object, name) => {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(object, name);
|
||||
return [
|
||||
typeof descriptor?.value,
|
||||
descriptor?.value?.name,
|
||||
descriptor?.value?.length,
|
||||
descriptor?.enumerable,
|
||||
descriptor?.writable,
|
||||
descriptor?.configurable
|
||||
].join(":");
|
||||
};
|
||||
const accessor = (object, name) => {
|
||||
const descriptor = Object.getOwnPropertyDescriptor(object, name);
|
||||
return [
|
||||
typeof descriptor?.get,
|
||||
descriptor?.get?.name,
|
||||
descriptor?.get?.length,
|
||||
typeof descriptor?.set,
|
||||
descriptor?.enumerable,
|
||||
descriptor?.configurable
|
||||
].join(":");
|
||||
};
|
||||
const errorName = callback => {
|
||||
try {
|
||||
callback();
|
||||
return "none";
|
||||
} catch (error) {
|
||||
return error && error.name;
|
||||
}
|
||||
};
|
||||
const presentationStyleGetter = Object.getOwnPropertyDescriptor(
|
||||
ClipboardItem.prototype,
|
||||
"presentationStyle"
|
||||
).get;
|
||||
const typesGetter = Object.getOwnPropertyDescriptor(
|
||||
ClipboardItem.prototype,
|
||||
"types"
|
||||
).get;
|
||||
|
||||
globalThis.__asyncClipboardProbe = { state: "pending" };
|
||||
const rejectionName = promise => Promise.resolve(promise).then(
|
||||
() => "resolved",
|
||||
error => error && error.name
|
||||
);
|
||||
(async () => {
|
||||
const initialBlob = await item.getType("text/plain");
|
||||
const initialText = await initialBlob.text();
|
||||
const opaqueBlob = await item.getType("not a/real type");
|
||||
const opaqueText = await opaqueBlob.text();
|
||||
await clipboard.write([writableItem]);
|
||||
const writtenText = await clipboard.readText();
|
||||
const readItems = await clipboard.read({ unsanitized: ["text/html"] });
|
||||
await clipboard.writeText("next");
|
||||
const textItems = await clipboard.read();
|
||||
const textBlob = await textItems[0].getType("text/plain");
|
||||
const roundTripText = await textBlob.text();
|
||||
const rejections = await Promise.all([
|
||||
rejectionName(Clipboard.prototype.read.call({})),
|
||||
rejectionName(Clipboard.prototype.write.call({}, [item])),
|
||||
rejectionName(clipboard.writeText()),
|
||||
rejectionName(clipboard.write([])),
|
||||
rejectionName(clipboard.read({ unsanitized: ["text/html", "text/plain"] })),
|
||||
rejectionName(item.getType()),
|
||||
rejectionName(item.getType("missing/type")),
|
||||
rejectionName(ClipboardItem.prototype.getType.call({}, "text/plain")),
|
||||
rejectionName(clipboard.write([invalidCustomItem])),
|
||||
rejectionName(clipboard.write([mismatchedCustomItem])),
|
||||
rejectionName(clipboard.write([stringPngItem]))
|
||||
]);
|
||||
globalThis.__asyncClipboardProbe = {
|
||||
state: "done",
|
||||
initialBlob: [initialBlob instanceof Blob, initialBlob.type, initialText],
|
||||
writtenText,
|
||||
read: [
|
||||
readItems.length,
|
||||
readItems[0] === writableItem,
|
||||
opaqueText,
|
||||
Object.isFrozen(readItems)
|
||||
],
|
||||
textWrite: [
|
||||
textItems.length,
|
||||
textItems[0] instanceof ClipboardItem,
|
||||
textItems[0].types.join(","),
|
||||
roundTripText
|
||||
],
|
||||
rejections
|
||||
};
|
||||
})().catch(error => {
|
||||
globalThis.__asyncClipboardProbe = {
|
||||
state: "failed",
|
||||
error: `${error && error.name}:${error && error.message}`
|
||||
};
|
||||
});
|
||||
|
||||
return JSON.stringify({
|
||||
constructors: [
|
||||
typeof Clipboard,
|
||||
Clipboard.name,
|
||||
Clipboard.length,
|
||||
typeof ClipboardItem,
|
||||
ClipboardItem.name,
|
||||
ClipboardItem.length
|
||||
],
|
||||
clipboard: [
|
||||
navigator.clipboard === navigator.clipboard,
|
||||
clipboard instanceof Clipboard,
|
||||
clipboard instanceof EventTarget,
|
||||
Object.getPrototypeOf(clipboard) === Clipboard.prototype,
|
||||
Object.prototype.toString.call(clipboard),
|
||||
Object.keys(clipboard).join(","),
|
||||
Object.keys(Clipboard.prototype).join(",")
|
||||
],
|
||||
methods: [
|
||||
method(Clipboard.prototype, "read"),
|
||||
method(Clipboard.prototype, "readText"),
|
||||
method(Clipboard.prototype, "write"),
|
||||
method(Clipboard.prototype, "writeText")
|
||||
],
|
||||
item: [
|
||||
item instanceof ClipboardItem,
|
||||
Object.getPrototypeOf(item) === ClipboardItem.prototype,
|
||||
Object.prototype.toString.call(item),
|
||||
Object.keys(item).join(","),
|
||||
item.presentationStyle,
|
||||
item.types.join(","),
|
||||
Object.isFrozen(item.types),
|
||||
Object.keys(ClipboardItem.prototype).join(",")
|
||||
],
|
||||
itemMembers: [
|
||||
accessor(ClipboardItem.prototype, "presentationStyle"),
|
||||
accessor(ClipboardItem.prototype, "types"),
|
||||
method(ClipboardItem.prototype, "getType"),
|
||||
method(ClipboardItem, "supports")
|
||||
],
|
||||
supports: [
|
||||
ClipboardItem.supports("text/plain"),
|
||||
ClipboardItem.supports("web foo/bar"),
|
||||
ClipboardItem.supports("foo/bar")
|
||||
],
|
||||
syncErrors: [
|
||||
errorName(() => new Clipboard()),
|
||||
errorName(() => Clipboard()),
|
||||
errorName(() => ClipboardItem({ "text/plain": "x" })),
|
||||
errorName(() => new ClipboardItem()),
|
||||
errorName(() => new ClipboardItem(null)),
|
||||
errorName(() => new ClipboardItem({})),
|
||||
errorName(() => presentationStyleGetter.call({})),
|
||||
errorName(() => typesGetter.call({}))
|
||||
]
|
||||
});
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("Async Clipboard interface shape should evaluate");
|
||||
|
||||
assert_eq!(
|
||||
shape,
|
||||
r#"{"constructors":["function","Clipboard",0,"function","ClipboardItem",1],"clipboard":[true,true,true,true,"[object Clipboard]","","read,readText,write,writeText"],"methods":["function:read:0:true:true:true","function:readText:0:true:true:true","function:write:1:true:true:true","function:writeText:1:true:true:true"],"item":[true,true,"[object ClipboardItem]","","inline","text/plain,not a/real type",true,"presentationStyle,types,getType"],"itemMembers":["function:get presentationStyle:0:undefined:true:true","function:get types:0:undefined:true:true","function:getType:1:true:true:true","function:supports:1:true:true:true"],"supports":[true,true,false],"syncErrors":["TypeError","TypeError","TypeError","TypeError","TypeError","TypeError","TypeError","TypeError"]}"#
|
||||
);
|
||||
|
||||
vm.eval("0")
|
||||
.expect("Async Clipboard promise operations should drain");
|
||||
assert_eq!(
|
||||
vm.eval("JSON.stringify(globalThis.__asyncClipboardProbe)")
|
||||
.expect("Async Clipboard promise result should evaluate"),
|
||||
r#"{"state":"done","initialBlob":[true,"text/plain","hello"],"writtenText":"hello","read":[1,true,"opaque",false],"textWrite":[1,true,"text/plain","next"],"rejections":["TypeError","TypeError","TypeError","NotAllowedError","NotAllowedError","TypeError","NotFoundError","TypeError","NotAllowedError","NotAllowedError","TypeError"]}"#
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn navigator_storage_apis_are_secure_context_only() {
|
||||
let mut vm = new_storage_test_vm("http://insecure-storage-surface.test/");
|
||||
@@ -8164,16 +8366,21 @@ fn navigator_storage_apis_are_secure_context_only() {
|
||||
const childProto = Object.getPrototypeOf(frame.contentWindow.navigator);
|
||||
return JSON.stringify({
|
||||
secure: globalThis.isSecureContext === true,
|
||||
clipboardInNavigator: "clipboard" in navigator,
|
||||
storageInNavigator: "storage" in navigator,
|
||||
storageBucketsInNavigator: "storageBuckets" in navigator,
|
||||
userAgentDataInNavigator: "userAgentData" in navigator,
|
||||
clipboardInProto: Object.prototype.hasOwnProperty.call(proto, "clipboard"),
|
||||
storageInProto: Object.prototype.hasOwnProperty.call(proto, "storage"),
|
||||
storageBucketsInProto: Object.prototype.hasOwnProperty.call(proto, "storageBuckets"),
|
||||
userAgentDataInProto:
|
||||
Object.prototype.hasOwnProperty.call(proto, "userAgentData"),
|
||||
clipboardValueType: typeof navigator.clipboard,
|
||||
storageValueType: typeof navigator.storage,
|
||||
storageBucketsValueType: typeof navigator.storageBuckets,
|
||||
userAgentDataValueType: typeof navigator.userAgentData,
|
||||
clipboardGlobal: "Clipboard" in globalThis,
|
||||
clipboardItemGlobal: "ClipboardItem" in globalThis,
|
||||
storageManagerGlobal: "StorageManager" in globalThis,
|
||||
storageEstimateGlobal: "StorageEstimate" in globalThis,
|
||||
storageBucketManagerGlobal: "StorageBucketManager" in globalThis,
|
||||
@@ -8185,14 +8392,19 @@ fn navigator_storage_apis_are_secure_context_only() {
|
||||
"FileSystemWritableFileStream" in globalThis,
|
||||
fileSystemSyncAccessHandleGlobal:
|
||||
"FileSystemSyncAccessHandle" in globalThis,
|
||||
childClipboardInNavigator: "clipboard" in frame.contentWindow.navigator,
|
||||
childStorageInNavigator: "storage" in frame.contentWindow.navigator,
|
||||
childStorageBucketsInNavigator: "storageBuckets" in frame.contentWindow.navigator,
|
||||
childUserAgentDataInNavigator:
|
||||
"userAgentData" in frame.contentWindow.navigator,
|
||||
childClipboardInProto:
|
||||
Object.prototype.hasOwnProperty.call(childProto, "clipboard"),
|
||||
childStorageInProto: Object.prototype.hasOwnProperty.call(childProto, "storage"),
|
||||
childStorageBucketsInProto: Object.prototype.hasOwnProperty.call(childProto, "storageBuckets"),
|
||||
childUserAgentDataInProto:
|
||||
Object.prototype.hasOwnProperty.call(childProto, "userAgentData"),
|
||||
childClipboardGlobal: "Clipboard" in frame.contentWindow,
|
||||
childClipboardItemGlobal: "ClipboardItem" in frame.contentWindow,
|
||||
childFileSystemHandleGlobal:
|
||||
"FileSystemHandle" in frame.contentWindow,
|
||||
childFileSystemFileHandleGlobal:
|
||||
@@ -8211,7 +8423,7 @@ fn navigator_storage_apis_are_secure_context_only() {
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"{"secure":false,"storageInNavigator":false,"storageBucketsInNavigator":false,"userAgentDataInNavigator":false,"storageInProto":false,"storageBucketsInProto":false,"userAgentDataInProto":false,"storageValueType":"undefined","storageBucketsValueType":"undefined","userAgentDataValueType":"undefined","storageManagerGlobal":false,"storageEstimateGlobal":false,"storageBucketManagerGlobal":false,"storageBucketGlobal":false,"fileSystemHandleGlobal":false,"fileSystemFileHandleGlobal":false,"fileSystemDirectoryHandleGlobal":false,"fileSystemWritableFileStreamGlobal":false,"fileSystemSyncAccessHandleGlobal":false,"childStorageInNavigator":false,"childStorageBucketsInNavigator":false,"childUserAgentDataInNavigator":false,"childStorageInProto":false,"childStorageBucketsInProto":false,"childUserAgentDataInProto":false,"childFileSystemHandleGlobal":false,"childFileSystemFileHandleGlobal":false,"childFileSystemDirectoryHandleGlobal":false,"childFileSystemWritableFileStreamGlobal":false,"childFileSystemSyncAccessHandleGlobal":false}"#
|
||||
r#"{"secure":false,"clipboardInNavigator":false,"storageInNavigator":false,"storageBucketsInNavigator":false,"userAgentDataInNavigator":false,"clipboardInProto":false,"storageInProto":false,"storageBucketsInProto":false,"userAgentDataInProto":false,"clipboardValueType":"undefined","storageValueType":"undefined","storageBucketsValueType":"undefined","userAgentDataValueType":"undefined","clipboardGlobal":false,"clipboardItemGlobal":false,"storageManagerGlobal":false,"storageEstimateGlobal":false,"storageBucketManagerGlobal":false,"storageBucketGlobal":false,"fileSystemHandleGlobal":false,"fileSystemFileHandleGlobal":false,"fileSystemDirectoryHandleGlobal":false,"fileSystemWritableFileStreamGlobal":false,"fileSystemSyncAccessHandleGlobal":false,"childClipboardInNavigator":false,"childStorageInNavigator":false,"childStorageBucketsInNavigator":false,"childUserAgentDataInNavigator":false,"childClipboardInProto":false,"childStorageInProto":false,"childStorageBucketsInProto":false,"childUserAgentDataInProto":false,"childClipboardGlobal":false,"childClipboardItemGlobal":false,"childFileSystemHandleGlobal":false,"childFileSystemFileHandleGlobal":false,"childFileSystemDirectoryHandleGlobal":false,"childFileSystemWritableFileStreamGlobal":false,"childFileSystemSyncAccessHandleGlobal":false}"#
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -620,6 +620,8 @@ fn navigator_scalar_access_does_not_materialize_unused_same_object_children() {
|
||||
"Geolocation",
|
||||
"GeolocationPositionError",
|
||||
"MediaCapabilities",
|
||||
"Clipboard",
|
||||
"ClipboardItem",
|
||||
] {
|
||||
assert_eq!(
|
||||
constructor_materialization_count(&mut vm, name),
|
||||
@@ -674,6 +676,9 @@ fn navigator_scalar_access_does_not_materialize_unused_same_object_children() {
|
||||
storageRealm:
|
||||
Object.getPrototypeOf(navigator.storage) ===
|
||||
StorageManager.prototype,
|
||||
clipboardRealm:
|
||||
Object.getPrototypeOf(navigator.clipboard) ===
|
||||
Clipboard.prototype,
|
||||
geolocationRealm:
|
||||
Object.getPrototypeOf(navigator.geolocation) ===
|
||||
Geolocation.prototype,
|
||||
@@ -685,7 +690,7 @@ fn navigator_scalar_access_does_not_materialize_unused_same_object_children() {
|
||||
"#,
|
||||
)
|
||||
.expect("Navigator lazy subobjects should materialize"),
|
||||
r#"{"same":true,"languagesFrozen":false,"permissionsRealm":true,"mediaDevicesRealm":true,"storageRealm":true,"geolocationRealm":true,"mediaCapabilitiesRealm":true}"#
|
||||
r#"{"same":true,"languagesFrozen":false,"permissionsRealm":true,"mediaDevicesRealm":true,"storageRealm":true,"clipboardRealm":true,"geolocationRealm":true,"mediaCapabilitiesRealm":true}"#
|
||||
);
|
||||
assert_eq!(
|
||||
default_navigator_subobjects(&mut vm),
|
||||
@@ -709,6 +714,12 @@ fn navigator_scalar_access_does_not_materialize_unused_same_object_children() {
|
||||
]
|
||||
);
|
||||
assert_eq!(constructor_materialization_count(&mut vm, "Geolocation"), 1);
|
||||
assert_eq!(constructor_materialization_count(&mut vm, "Clipboard"), 1);
|
||||
assert_eq!(
|
||||
constructor_materialization_count(&mut vm, "ClipboardItem"),
|
||||
0,
|
||||
"materializing navigator.clipboard must not eagerly build ClipboardItem"
|
||||
);
|
||||
assert_eq!(
|
||||
constructor_materialization_count(&mut vm, "MediaCapabilities"),
|
||||
1
|
||||
|
||||
Reference in New Issue
Block a user