fix(clipboard): implement async clipboard interfaces

This commit is contained in:
ldm0
2026-09-07 17:51:09 +08:00
parent b381236125
commit 746a8f637a
10 changed files with 1336 additions and 63 deletions
@@ -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