mirror of
https://github.com/lexmount/moli.git
synced 2026-10-05 00:00:55 +00:00
2207 lines
76 KiB
Rust
2207 lines
76 KiB
Rust
use super::date_locale_runtime::install_date_locale_runtime_state;
|
|
use super::file_api::initialize_file_api_runtime_queues;
|
|
use super::shared::*;
|
|
use super::webassembly_runtime::install_webassembly_runtime_extensions;
|
|
use super::window_events::*;
|
|
use super::{
|
|
crypto::install_window_crypto_runtime_state,
|
|
css_runtime::install_css_runtime_state,
|
|
exposed_interfaces::is_lazy_exposed_interface,
|
|
indexed_db::ensure_indexed_db_runtime_state,
|
|
location_runtime::{
|
|
ensure_location_constructor_runtime_state, location_href_slot,
|
|
sync_document_location_runtime_state_from_window,
|
|
},
|
|
navigation_bootstrap::install_window_location_history_navigation_runtime_state,
|
|
navigator_runtime::{bind_window_navigator_identity_seed, install_navigator_runtime_state},
|
|
performance_runtime::install_default_window_performance_seed,
|
|
range_surface::reset_range_runtime_state,
|
|
trusted_types::install_trusted_types_runtime_state,
|
|
web_storage::{
|
|
install_storage_runtime_state, window_local_storage_getter, window_session_storage_getter,
|
|
},
|
|
window_runtime::{build_legacy_storage_info_object, window_noop_callback},
|
|
window_template::install_window_named_properties_object,
|
|
};
|
|
use crate::{
|
|
document_runtime::{DocumentRuntime, DomHandle},
|
|
native_bridge::{
|
|
self, JsContextHost,
|
|
document::{
|
|
document_all_value_for_receiver, document_cookie_for_receiver,
|
|
set_document_cookie_for_receiver,
|
|
},
|
|
},
|
|
network_host,
|
|
util::{
|
|
callback_data_index_value, callback_data_item, context_host_ptr_from_global_bridge,
|
|
context_host_ptr_from_window_object, create_script_origin_with_base_url, get_private_value,
|
|
script_base_url_continuation_data, set_private_value, throw_type_error, v8_string, v8str,
|
|
},
|
|
webidl,
|
|
webidl_iterator::install_webidl_collection_iterator_intrinsics,
|
|
window_host,
|
|
};
|
|
use anyhow::{Result, anyhow, bail};
|
|
use moli_script::html_script_element_supports_type;
|
|
use moli_webapi_declare::WebApiObject;
|
|
|
|
#[cfg(test)]
|
|
mod tests;
|
|
|
|
pub(crate) const ORIGINAL_WEBASSEMBLY_INSTANCE_CONSTRUCTOR_SLOT: &str =
|
|
"__moliOriginalWebAssemblyInstanceConstructor";
|
|
pub(crate) const ORIGINAL_WEBASSEMBLY_COMPILE_ERROR_CONSTRUCTOR_SLOT: &str =
|
|
"__moliOriginalWebAssemblyCompileErrorConstructor";
|
|
pub(crate) const ORIGINAL_WEBASSEMBLY_LINK_ERROR_CONSTRUCTOR_SLOT: &str =
|
|
"__moliOriginalWebAssemblyLinkErrorConstructor";
|
|
pub(crate) const ORIGINAL_WEBASSEMBLY_GLOBAL_VALUE_GETTER_SLOT: &str =
|
|
"__moliOriginalWebAssemblyGlobalValueGetter";
|
|
const WINDOW_INDEXED_DB_SURFACE_SLOT: &str = "moli.Window.indexedDB";
|
|
const WINDOW_ORIGIN_RUNTIME_SLOT: &str = "__moliWindowOriginRuntime";
|
|
const WINDOW_INTRINSIC_EVAL_SLOT: &str = "__moliWindowIntrinsicEval";
|
|
pub(in crate::context_bootstrap) const WINDOW_SECURE_CONTEXT_AVAILABLE_SLOT: &str =
|
|
"__moliWindowSecureContextAvailable";
|
|
|
|
#[derive(webidl::WebIdlArgs)]
|
|
#[webidl(prefix = "HTMLScriptElement.supports")]
|
|
struct HtmlScriptElementSupportsArgs {
|
|
#[webidl(required, name = "type")]
|
|
script_type: String,
|
|
}
|
|
|
|
#[derive(Default, WebApiObject)]
|
|
#[webapi(interface = "HTMLScriptElement", enumerable)]
|
|
struct HtmlScriptElementStaticMethodsDeclaration {
|
|
#[webapi(method, length = 1, callback = html_script_element_supports_callback)]
|
|
supports: (),
|
|
}
|
|
|
|
#[derive(Default, WebApiObject)]
|
|
#[webapi(interface = "Document", enumerable)]
|
|
struct DocumentStaticMethodsDeclaration {
|
|
#[webapi(
|
|
method = "parseHTMLUnsafe",
|
|
length = 1,
|
|
callback = document_parse_html_unsafe_callback
|
|
)]
|
|
parse_html_unsafe: (),
|
|
}
|
|
|
|
#[derive(Default, WebApiObject)]
|
|
#[webapi(interface = "Document", enumerable)]
|
|
struct DocumentPrototypeRuntimeDeclaration {
|
|
#[webapi(
|
|
accessor_property = "designMode",
|
|
getter = native_bridge::document::node_document_design_mode_getter_function,
|
|
setter = native_bridge::document::node_document_design_mode_setter_function
|
|
)]
|
|
design_mode: (),
|
|
#[webapi(accessor_property, getter = document_all_runtime_getter)]
|
|
all: (),
|
|
#[webapi(
|
|
accessor_property,
|
|
getter = document_cookie_runtime_getter,
|
|
setter = document_cookie_runtime_setter
|
|
)]
|
|
cookie: (),
|
|
#[webapi(
|
|
accessor_property,
|
|
getter = native_bridge::document::node_document_style_sheets_getter_function
|
|
)]
|
|
style_sheets: (),
|
|
#[webapi(
|
|
accessor_property,
|
|
getter = native_bridge::document::node_document_adopted_style_sheets_getter_function,
|
|
setter = native_bridge::document::node_document_adopted_style_sheets_setter_function
|
|
)]
|
|
adopted_style_sheets: (),
|
|
#[webapi(
|
|
accessor_property = "fullscreenEnabled",
|
|
getter = document_fullscreen_enabled_getter,
|
|
setter = document_fullscreen_enabled_lenient_setter
|
|
)]
|
|
fullscreen_enabled: (),
|
|
#[webapi(
|
|
accessor_property = "pointerLockElement",
|
|
getter = native_bridge::pointer_lock::document_pointer_lock_element_getter
|
|
)]
|
|
pointer_lock_element: (),
|
|
#[webapi(
|
|
method = "exitPointerLock",
|
|
length = 0,
|
|
callback = native_bridge::pointer_lock::document_exit_pointer_lock_callback
|
|
)]
|
|
exit_pointer_lock: (),
|
|
}
|
|
|
|
#[derive(Default, WebApiObject)]
|
|
#[webapi(interface = "Object")]
|
|
struct WindowComputedStyleMethodDeclaration {
|
|
#[webapi(
|
|
method,
|
|
enumerable,
|
|
length = 1,
|
|
callback = window_host::window_get_computed_style_callback
|
|
)]
|
|
get_computed_style: (),
|
|
}
|
|
|
|
#[derive(WebApiObject)]
|
|
#[webapi(interface = "Object", scope_lifetime = 'scope)]
|
|
struct WindowPublicSurfaceAccessorsDeclaration<'scope> {
|
|
history_slot: v8::Local<'scope, v8::Value>,
|
|
navigation_slot: v8::Local<'scope, v8::Value>,
|
|
navigator_slot: v8::Local<'scope, v8::Value>,
|
|
screen_slot: v8::Local<'scope, v8::Value>,
|
|
speech_synthesis_slot: v8::Local<'scope, v8::Value>,
|
|
custom_elements_slot: v8::Local<'scope, v8::Value>,
|
|
crypto_slot: v8::Local<'scope, v8::Value>,
|
|
performance_slot: v8::Local<'scope, v8::Value>,
|
|
visual_viewport_slot: v8::Local<'scope, v8::Value>,
|
|
indexed_db_slot: v8::Local<'scope, v8::Value>,
|
|
navigation_name: v8::Local<'scope, v8::Value>,
|
|
screen_name: v8::Local<'scope, v8::Value>,
|
|
performance_name: v8::Local<'scope, v8::Value>,
|
|
visual_viewport_name: v8::Local<'scope, v8::Value>,
|
|
|
|
#[webapi(
|
|
accessor_property,
|
|
enumerable,
|
|
getter = window_surface_slot_getter,
|
|
data = self.history_slot
|
|
)]
|
|
history: (),
|
|
#[webapi(
|
|
accessor_property,
|
|
enumerable,
|
|
getter = window_surface_slot_getter,
|
|
setter = window_surface_replaceable_setter,
|
|
data = self.navigation_slot,
|
|
setter_data = self.navigation_name
|
|
)]
|
|
navigation: (),
|
|
#[webapi(
|
|
accessor_property,
|
|
enumerable,
|
|
getter = window_surface_slot_getter,
|
|
data = self.navigator_slot
|
|
)]
|
|
navigator: (),
|
|
#[webapi(
|
|
accessor_property,
|
|
enumerable,
|
|
getter = window_surface_slot_getter,
|
|
setter = window_surface_replaceable_setter,
|
|
data = self.screen_slot,
|
|
setter_data = self.screen_name
|
|
)]
|
|
screen: (),
|
|
#[webapi(
|
|
accessor_property = "speechSynthesis",
|
|
enumerable,
|
|
getter = window_surface_slot_getter,
|
|
data = self.speech_synthesis_slot
|
|
)]
|
|
speech_synthesis: (),
|
|
#[webapi(
|
|
accessor_property = "customElements",
|
|
enumerable,
|
|
getter = window_surface_slot_getter,
|
|
data = self.custom_elements_slot
|
|
)]
|
|
custom_elements: (),
|
|
#[webapi(
|
|
accessor_property,
|
|
enumerable,
|
|
getter = window_surface_slot_getter,
|
|
data = self.crypto_slot
|
|
)]
|
|
crypto: (),
|
|
#[webapi(
|
|
accessor_property,
|
|
enumerable,
|
|
getter = window_surface_slot_getter,
|
|
setter = window_surface_replaceable_setter,
|
|
data = self.performance_slot,
|
|
setter_data = self.performance_name
|
|
)]
|
|
performance: (),
|
|
#[webapi(
|
|
accessor_property = "visualViewport",
|
|
enumerable,
|
|
getter = window_surface_slot_getter,
|
|
setter = window_surface_replaceable_setter,
|
|
data = self.visual_viewport_slot,
|
|
setter_data = self.visual_viewport_name
|
|
)]
|
|
visual_viewport: (),
|
|
#[webapi(
|
|
accessor_property = "localStorage",
|
|
enumerable,
|
|
getter = window_local_storage_getter
|
|
)]
|
|
local_storage: (),
|
|
#[webapi(
|
|
accessor_property = "sessionStorage",
|
|
enumerable,
|
|
getter = window_session_storage_getter
|
|
)]
|
|
session_storage: (),
|
|
#[webapi(
|
|
accessor_property = "indexedDB",
|
|
enumerable,
|
|
getter = window_surface_slot_getter,
|
|
data = self.indexed_db_slot
|
|
)]
|
|
indexed_db: (),
|
|
#[webapi(
|
|
accessor_property,
|
|
enumerable,
|
|
getter = window_name_runtime_getter,
|
|
setter = window_name_runtime_setter
|
|
)]
|
|
name: (),
|
|
}
|
|
|
|
#[derive(Default, WebApiObject)]
|
|
#[webapi(interface = "Object", enumerable)]
|
|
struct WindowLegacyAliasAccessorsDeclaration {
|
|
#[webapi(
|
|
accessor_property,
|
|
getter = legacy_unforgeable_self_getter,
|
|
setter = replaceable_self_setter
|
|
)]
|
|
self_: (),
|
|
#[webapi(
|
|
accessor_property,
|
|
getter = legacy_unforgeable_parent_getter,
|
|
setter = replaceable_parent_setter
|
|
)]
|
|
parent: (),
|
|
#[webapi(accessor_property, dont_delete, getter = legacy_unforgeable_top_getter)]
|
|
top: (),
|
|
#[webapi(
|
|
accessor_property,
|
|
getter = legacy_unforgeable_frames_getter,
|
|
setter = replaceable_frames_setter
|
|
)]
|
|
frames: (),
|
|
}
|
|
|
|
#[derive(WebApiObject)]
|
|
#[webapi(interface = "Window", enumerable)]
|
|
struct WindowAdditionalReplaceableAccessorsDeclaration<'scope> {
|
|
origin_name: v8::Local<'scope, v8::Value>,
|
|
inner_width_name: v8::Local<'scope, v8::Value>,
|
|
length_name: v8::Local<'scope, v8::Value>,
|
|
event_name: v8::Local<'scope, v8::Value>,
|
|
outer_height_name: v8::Local<'scope, v8::Value>,
|
|
scroll_x_name: v8::Local<'scope, v8::Value>,
|
|
screen_left_name: v8::Local<'scope, v8::Value>,
|
|
screen_top_name: v8::Local<'scope, v8::Value>,
|
|
screen_x_name: v8::Local<'scope, v8::Value>,
|
|
screen_y_name: v8::Local<'scope, v8::Value>,
|
|
#[webapi(
|
|
accessor_property,
|
|
getter = window_origin_replaceable_getter,
|
|
setter = window_surface_replaceable_setter,
|
|
setter_data = self.origin_name
|
|
)]
|
|
origin: (),
|
|
#[webapi(
|
|
accessor_property = "innerWidth",
|
|
getter = window_inner_width_replaceable_getter,
|
|
setter = window_surface_replaceable_setter,
|
|
setter_data = self.inner_width_name
|
|
)]
|
|
inner_width: (),
|
|
#[webapi(
|
|
accessor_property,
|
|
getter = window_length_replaceable_getter,
|
|
setter = window_surface_replaceable_setter,
|
|
setter_data = self.length_name
|
|
)]
|
|
length: (),
|
|
#[webapi(
|
|
accessor_property,
|
|
getter = window_event_replaceable_getter,
|
|
setter = window_surface_replaceable_setter,
|
|
setter_data = self.event_name
|
|
)]
|
|
event: (),
|
|
#[webapi(
|
|
accessor_property = "outerHeight",
|
|
getter = window_outer_height_replaceable_getter,
|
|
setter = window_surface_replaceable_setter,
|
|
setter_data = self.outer_height_name
|
|
)]
|
|
outer_height: (),
|
|
#[webapi(
|
|
accessor_property = "scrollX",
|
|
getter = window_scroll_x_replaceable_getter,
|
|
setter = window_surface_replaceable_setter,
|
|
setter_data = self.scroll_x_name
|
|
)]
|
|
scroll_x: (),
|
|
#[webapi(
|
|
accessor_property = "screenLeft",
|
|
getter = window_zero_replaceable_getter,
|
|
setter = window_surface_replaceable_setter,
|
|
setter_data = self.screen_left_name
|
|
)]
|
|
screen_left: (),
|
|
#[webapi(
|
|
accessor_property = "screenTop",
|
|
getter = window_zero_replaceable_getter,
|
|
setter = window_surface_replaceable_setter,
|
|
setter_data = self.screen_top_name
|
|
)]
|
|
screen_top: (),
|
|
#[webapi(
|
|
accessor_property = "screenX",
|
|
getter = window_zero_replaceable_getter,
|
|
setter = window_surface_replaceable_setter,
|
|
setter_data = self.screen_x_name
|
|
)]
|
|
screen_x: (),
|
|
#[webapi(
|
|
accessor_property = "screenY",
|
|
getter = window_zero_replaceable_getter,
|
|
setter = window_surface_replaceable_setter,
|
|
setter_data = self.screen_y_name
|
|
)]
|
|
screen_y: (),
|
|
}
|
|
|
|
#[derive(Default, WebApiObject)]
|
|
#[webapi(interface = "Object")]
|
|
struct ConsoleObjectDeclaration {
|
|
#[webapi(method, callback = console_log_callback)]
|
|
log: (),
|
|
#[webapi(method, callback = console_info_callback)]
|
|
info: (),
|
|
#[webapi(method, callback = console_warn_callback)]
|
|
warn: (),
|
|
#[webapi(method, callback = console_error_callback)]
|
|
error: (),
|
|
#[webapi(method, callback = console_debug_callback)]
|
|
debug: (),
|
|
#[webapi(method, callback = console_trace_callback)]
|
|
trace: (),
|
|
#[webapi(method, callback = console_noop_callback)]
|
|
clear: (),
|
|
#[webapi(method, callback = console_assert_callback)]
|
|
assert: (),
|
|
#[webapi(method, callback = console_error_callback)]
|
|
exception: (),
|
|
#[webapi(method, callback = console_table_callback)]
|
|
table: (),
|
|
#[webapi(method, callback = console_noop_callback)]
|
|
time: (),
|
|
#[webapi(method, callback = console_noop_callback)]
|
|
time_log: (),
|
|
#[webapi(method, callback = console_noop_callback)]
|
|
time_end: (),
|
|
#[webapi(method, callback = console_noop_callback)]
|
|
count: (),
|
|
#[webapi(method, callback = console_noop_callback)]
|
|
count_reset: (),
|
|
#[webapi(method, callback = console_group_callback)]
|
|
group: (),
|
|
#[webapi(method, callback = console_group_collapsed_callback)]
|
|
group_collapsed: (),
|
|
#[webapi(method, callback = console_noop_callback)]
|
|
group_end: (),
|
|
#[webapi(method, callback = console_profile_callback)]
|
|
profile: (),
|
|
#[webapi(method, callback = console_profile_end_callback)]
|
|
profile_end: (),
|
|
}
|
|
|
|
#[derive(WebApiObject)]
|
|
#[webapi(interface = "Object", scope_lifetime = 'scope)]
|
|
struct WindowBootstrapGlobalSlotsDeclaration<'scope> {
|
|
#[webapi(slot = WINDOW_CONSOLE_SLOT)]
|
|
console: v8::Local<'scope, v8::Object>,
|
|
#[webapi(data_property = WINDOW_EVENT_SLOT, init = "undefined")]
|
|
event: (),
|
|
#[webapi(data_property = WINDOW_ONERROR_SLOT, init = "null")]
|
|
on_error: (),
|
|
#[webapi(data_property = WINDOW_ONUNHANDLEDREJECTION_SLOT, init = "null")]
|
|
on_unhandled_rejection: (),
|
|
#[webapi(data_property = WINDOW_ONREJECTIONHANDLED_SLOT, init = "null")]
|
|
on_rejection_handled: (),
|
|
#[webapi(data_property = WINDOW_SELF_SLOT)]
|
|
self_value: v8::Local<'scope, v8::Object>,
|
|
#[webapi(data_property = WINDOW_PARENT_SLOT)]
|
|
parent: v8::Local<'scope, v8::Object>,
|
|
#[webapi(data_property = WINDOW_TOP_SLOT)]
|
|
top: v8::Local<'scope, v8::Object>,
|
|
#[webapi(data_property = WINDOW_FRAMES_SLOT)]
|
|
frames: v8::Local<'scope, v8::Object>,
|
|
#[webapi(data_property = "isSecureContext", readonly)]
|
|
is_secure_context: bool,
|
|
#[webapi(data_property = "TEMPORARY")]
|
|
temporary: u32,
|
|
#[webapi(data_property = "PERSISTENT")]
|
|
persistent: u32,
|
|
#[webapi(data_property = "webkitStorageInfo")]
|
|
webkit_storage_info: v8::Local<'scope, v8::Object>,
|
|
#[webapi(data_property = WINDOW_NAME_SLOT)]
|
|
window_name: &'static str,
|
|
#[webapi(data_property = "console")]
|
|
public_console: v8::Local<'scope, v8::Object>,
|
|
}
|
|
|
|
#[derive(WebApiObject)]
|
|
#[webapi(interface = "Object")]
|
|
struct WindowEvalGlobalDeclaration<'scope> {
|
|
#[webapi(data_property = "eval")]
|
|
intrinsic_eval: v8::Local<'scope, v8::Value>,
|
|
}
|
|
|
|
#[derive(Default, WebApiObject)]
|
|
#[webapi(interface = "Object")]
|
|
struct WebAssemblyNamespaceDeclaration {
|
|
#[webapi(method, length = 1, callback = webassembly_namespace_instance_callback)]
|
|
namespace_instance: (),
|
|
}
|
|
|
|
#[cfg(feature = "wpt-extensions")]
|
|
#[derive(WebApiObject)]
|
|
#[webapi(interface = "Object", prototype = "WebDriver", require_prototype)]
|
|
struct WebDriverObjectDeclaration {}
|
|
|
|
#[cfg(feature = "wpt-extensions")]
|
|
#[derive(WebApiObject)]
|
|
#[webapi(interface = "Object")]
|
|
struct WebDriverPrototypeDeclaration {
|
|
#[webapi(method, length = 0, callback = webdriver_delete_all_cookies_callback)]
|
|
delete_all_cookies: (),
|
|
}
|
|
|
|
#[cfg(feature = "wpt-extensions")]
|
|
#[derive(WebApiObject)]
|
|
#[webapi(interface = "Object")]
|
|
struct WebDriverPrototypeMetadataDeclaration<'scope> {
|
|
#[webapi(data_property = "constructor")]
|
|
constructor: v8::Local<'scope, v8::Function>,
|
|
|
|
#[webapi(to_string_tag, readonly, init = string("WebDriver"))]
|
|
to_string_tag: (),
|
|
}
|
|
|
|
fn legacy_unforgeable_window_getter<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
mut rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
rv.set(legacy_unforgeable_window_slot_value(
|
|
scope,
|
|
args.this(),
|
|
WINDOW_SELF_SLOT,
|
|
));
|
|
}
|
|
|
|
fn legacy_unforgeable_window_slot_value<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
receiver: v8::Local<'s, v8::Object>,
|
|
slot: &'static str,
|
|
) -> v8::Local<'s, v8::Value> {
|
|
object_hidden_value(scope, receiver, slot)
|
|
.unwrap_or_else(|| scope.get_current_context().global(scope).into())
|
|
}
|
|
|
|
fn document_fullscreen_enabled_getter<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
mut rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
let valid_receiver =
|
|
native_bridge::node_runtime_and_handle_from_object_or_detached(scope, args.this())
|
|
.ok()
|
|
.is_some_and(|(runtime_ptr, handle)| {
|
|
unsafe { &*runtime_ptr }
|
|
.dom_host()
|
|
.node(handle)
|
|
.is_some_and(crate::dom::native::Node::is_document)
|
|
});
|
|
if !valid_receiver {
|
|
throw_type_error(
|
|
scope,
|
|
"Document.fullscreenEnabled getter called on incompatible receiver.",
|
|
);
|
|
return;
|
|
}
|
|
rv.set_bool(false);
|
|
}
|
|
|
|
fn document_fullscreen_enabled_lenient_setter<'s>(
|
|
_scope: &mut v8::PinScope<'s, '_>,
|
|
_args: v8::FunctionCallbackArguments<'s>,
|
|
_rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
}
|
|
|
|
fn define_replaceable_window_property(
|
|
scope: &mut v8::PinScope<'_, '_>,
|
|
receiver: v8::Local<'_, v8::Object>,
|
|
name: &'static str,
|
|
value: v8::Local<'_, v8::Value>,
|
|
) {
|
|
match receiver.define_own_property(
|
|
scope,
|
|
v8str(scope, name).into(),
|
|
value,
|
|
v8::PropertyAttribute::NONE,
|
|
) {
|
|
Some(true) => {}
|
|
Some(false) => throw_type_error(
|
|
scope,
|
|
&format!("Failed to replace non-configurable Window.{name} property."),
|
|
),
|
|
None => {}
|
|
}
|
|
}
|
|
|
|
fn window_origin_replaceable_getter<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
mut rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
let value = get_private_value(scope, args.this(), WINDOW_ORIGIN_RUNTIME_SLOT)
|
|
.or_else(|| {
|
|
let global = scope.get_current_context().global(scope);
|
|
get_private_value(scope, global, WINDOW_ORIGIN_RUNTIME_SLOT)
|
|
})
|
|
.unwrap_or_else(|| v8str(scope, "null").into());
|
|
rv.set(value);
|
|
}
|
|
|
|
fn window_inner_width_replaceable_getter<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
mut rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
let width = super::window_accessors::window_inner_surface_width(scope, args.this());
|
|
rv.set(v8::Number::new(scope, width).into());
|
|
}
|
|
|
|
fn window_outer_height_replaceable_getter<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
_args: v8::FunctionCallbackArguments<'s>,
|
|
mut rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
rv.set(
|
|
v8::Number::new(
|
|
scope,
|
|
moli_browser_profile::DEFAULT_WINDOW_SURFACE_PROFILE.inner_height,
|
|
)
|
|
.into(),
|
|
);
|
|
}
|
|
|
|
fn window_length_replaceable_getter<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
mut rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
let Some(host_ptr) = context_host_ptr_from_window_object(scope, args.this())
|
|
.or_else(|| context_host_ptr_from_global_bridge(scope))
|
|
else {
|
|
rv.set(v8::Integer::new(scope, 0).into());
|
|
return;
|
|
};
|
|
let host = unsafe { &mut *host_ptr };
|
|
let count = child_context_handle_from_owner(scope, args.this())
|
|
.map(|handle| host.child_browsing_context_child_frame_count(handle))
|
|
.unwrap_or_else(|| host.child_browsing_context_count());
|
|
rv.set(v8::Number::new(scope, count as f64).into());
|
|
}
|
|
|
|
fn window_event_replaceable_getter<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
_args: v8::FunctionCallbackArguments<'s>,
|
|
mut rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
rv.set(
|
|
global_hidden_value(scope, WINDOW_EVENT_SLOT)
|
|
.unwrap_or_else(|| v8::undefined(scope).into()),
|
|
);
|
|
}
|
|
|
|
fn window_scroll_x_replaceable_getter<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
mut rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
let value = get_private_value(scope, args.this(), WINDOW_SCROLL_X_SLOT)
|
|
.or_else(|| {
|
|
let global = scope.get_current_context().global(scope);
|
|
get_private_value(scope, global, WINDOW_SCROLL_X_SLOT)
|
|
})
|
|
.and_then(|value| value.number_value(scope))
|
|
.unwrap_or(0.0);
|
|
rv.set(v8::Number::new(scope, value).into());
|
|
}
|
|
|
|
fn window_zero_replaceable_getter<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
_args: v8::FunctionCallbackArguments<'s>,
|
|
mut rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
rv.set(v8::Integer::new(scope, 0).into());
|
|
}
|
|
|
|
fn define_legacy_unforgeable_window_property<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
object: v8::Local<'s, v8::Object>,
|
|
) -> Result<()> {
|
|
let getter = v8::Function::builder(legacy_unforgeable_window_getter)
|
|
.build(scope)
|
|
.ok_or_else(|| anyhow!("failed to build window getter"))?;
|
|
define_get_set_property(
|
|
scope,
|
|
object,
|
|
v8str(scope, "window").into(),
|
|
getter.into(),
|
|
v8::undefined(scope).into(),
|
|
v8::PropertyAttribute::DONT_DELETE,
|
|
"window",
|
|
)
|
|
}
|
|
|
|
fn legacy_unforgeable_self_getter<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
mut rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
rv.set(legacy_unforgeable_window_slot_value(
|
|
scope,
|
|
args.this(),
|
|
WINDOW_SELF_SLOT,
|
|
));
|
|
}
|
|
|
|
fn replaceable_window_alias_set<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
name: &'static str,
|
|
) {
|
|
define_replaceable_window_property(scope, args.this(), name, args.get(0));
|
|
}
|
|
|
|
fn replaceable_self_setter<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
_rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
replaceable_window_alias_set(scope, args, "self");
|
|
}
|
|
|
|
fn legacy_unforgeable_parent_getter<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
mut rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
rv.set(legacy_unforgeable_window_slot_value(
|
|
scope,
|
|
args.this(),
|
|
WINDOW_PARENT_SLOT,
|
|
));
|
|
}
|
|
|
|
fn replaceable_parent_setter<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
_rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
replaceable_window_alias_set(scope, args, "parent");
|
|
}
|
|
|
|
fn legacy_unforgeable_top_getter<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
mut rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
rv.set(legacy_unforgeable_window_slot_value(
|
|
scope,
|
|
args.this(),
|
|
WINDOW_TOP_SLOT,
|
|
));
|
|
}
|
|
|
|
fn legacy_unforgeable_frames_getter<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
mut rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
rv.set(legacy_unforgeable_window_slot_value(
|
|
scope,
|
|
args.this(),
|
|
WINDOW_FRAMES_SLOT,
|
|
));
|
|
}
|
|
|
|
fn replaceable_frames_setter<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
_rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
replaceable_window_alias_set(scope, args, "frames");
|
|
}
|
|
|
|
fn callback_this_object<'s>(
|
|
_scope: &mut v8::PinScope<'s, '_>,
|
|
args: &v8::FunctionCallbackArguments<'s>,
|
|
) -> v8::Local<'s, v8::Object> {
|
|
args.this()
|
|
}
|
|
|
|
fn hidden_slot_value_for_receiver<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
receiver: v8::Local<'s, v8::Object>,
|
|
slot: &'static str,
|
|
) -> Option<v8::Local<'s, v8::Value>> {
|
|
if let Some(value) =
|
|
get_private_value(scope, receiver, slot).filter(|value| !value.is_undefined())
|
|
{
|
|
return Some(value);
|
|
}
|
|
if window_surface_slot_uses_legacy_hidden_fallback(slot)
|
|
&& let Some(value) =
|
|
object_hidden_value(scope, receiver, slot).filter(|value| !value.is_undefined())
|
|
{
|
|
return Some(value);
|
|
}
|
|
let global = scope.get_current_context().global(scope);
|
|
if let Some(value) =
|
|
get_private_value(scope, global, slot).filter(|value| !value.is_undefined())
|
|
{
|
|
return Some(value);
|
|
}
|
|
if window_surface_slot_uses_legacy_hidden_fallback(slot) {
|
|
return global_hidden_value(scope, slot).filter(|value| !value.is_undefined());
|
|
}
|
|
None
|
|
}
|
|
|
|
fn window_surface_slot_uses_legacy_hidden_fallback(slot: &str) -> bool {
|
|
matches!(slot, WINDOW_CUSTOM_ELEMENTS_SLOT | WINDOW_PERFORMANCE_SLOT)
|
|
}
|
|
|
|
fn window_surface_slot_getter<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
mut rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
let Some((slot, detached_fallback)) = super::window_receiver::bound_callback_data_item(
|
|
scope,
|
|
&args,
|
|
WINDOW_SURFACE_SLOTS,
|
|
"Window surface slots",
|
|
) else {
|
|
rv.set_undefined();
|
|
return;
|
|
};
|
|
let receiver = callback_this_object(scope, &args);
|
|
if slot == WINDOW_INDEXED_DB_SURFACE_SLOT {
|
|
if let Some(value) = detached_fallback {
|
|
rv.set(value);
|
|
return;
|
|
}
|
|
match ensure_indexed_db_runtime_state(scope).map(Into::into) {
|
|
Some(value) => rv.set(value),
|
|
None => rv.set_undefined(),
|
|
}
|
|
return;
|
|
}
|
|
match super::window_lazy_surface::ensure_window_lazy_surface_value(scope, receiver, slot) {
|
|
Ok(Some(value)) => {
|
|
rv.set(value);
|
|
return;
|
|
}
|
|
Ok(None) => {}
|
|
Err(error) => {
|
|
throw_error(
|
|
scope,
|
|
&format!("Failed to materialize Window surface: {error}"),
|
|
);
|
|
return;
|
|
}
|
|
}
|
|
match hidden_slot_value_for_receiver(scope, receiver, slot).or(detached_fallback) {
|
|
Some(value) => rv.set(value),
|
|
None => rv.set_undefined(),
|
|
}
|
|
}
|
|
|
|
fn window_surface_replaceable_setter<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
_rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
let Some(name) = callback_data_item(
|
|
scope,
|
|
&args,
|
|
WINDOW_SURFACE_REPLACEABLE_NAMES,
|
|
"Window surface replaceable names",
|
|
) else {
|
|
return;
|
|
};
|
|
let receiver = callback_this_object(scope, &args);
|
|
define_replaceable_window_property(scope, receiver, name, args.get(0));
|
|
}
|
|
|
|
fn window_name_runtime_getter<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
mut rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
let receiver = callback_this_object(scope, &args);
|
|
let value = object_hidden_value(scope, receiver, WINDOW_NAME_SLOT)
|
|
.unwrap_or_else(|| v8::String::empty(scope).into());
|
|
rv.set(value);
|
|
}
|
|
|
|
fn window_name_runtime_setter<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
_rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
let receiver = callback_this_object(scope, &args);
|
|
let next = args
|
|
.get(0)
|
|
.to_string(scope)
|
|
.map(|value| value.to_rust_string_lossy(scope))
|
|
.unwrap_or_default();
|
|
if let Some(handle) = child_context_handle_from_owner(scope, receiver)
|
|
&& let Some(host_ptr) = context_host_ptr_from_global_bridge(scope)
|
|
{
|
|
unsafe { &mut *host_ptr }.set_child_browsing_context_name(handle, next.clone());
|
|
}
|
|
define_non_enumerable_string_property(scope, receiver, WINDOW_NAME_SLOT, &next);
|
|
}
|
|
|
|
fn install_public_window_surface_accessors<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
global: v8::Local<'s, v8::Object>,
|
|
) -> Result<()> {
|
|
let history_slot = window_surface_callback_data(scope, global, WINDOW_HISTORY_SLOT);
|
|
let navigation_slot = window_surface_callback_data(scope, global, WINDOW_NAVIGATION_SLOT);
|
|
let navigator_slot = window_surface_callback_data(scope, global, WINDOW_NAVIGATOR_SLOT);
|
|
let screen_slot = window_surface_callback_data(scope, global, WINDOW_SCREEN_SLOT);
|
|
let speech_synthesis_slot =
|
|
window_surface_callback_data(scope, global, WINDOW_SPEECH_SYNTHESIS_SLOT);
|
|
let custom_elements_slot =
|
|
window_surface_callback_data(scope, global, WINDOW_CUSTOM_ELEMENTS_SLOT);
|
|
let crypto_slot = window_surface_callback_data(scope, global, WINDOW_CRYPTO_SLOT);
|
|
let performance_slot = window_surface_callback_data(scope, global, WINDOW_PERFORMANCE_SLOT);
|
|
let visual_viewport_slot =
|
|
window_surface_callback_data(scope, global, WINDOW_VISUAL_VIEWPORT_SLOT);
|
|
let indexed_db_slot =
|
|
window_surface_callback_data(scope, global, WINDOW_INDEXED_DB_SURFACE_SLOT);
|
|
let callback_data_registry = v8::Array::new_with_elements(
|
|
scope,
|
|
&[
|
|
history_slot,
|
|
navigation_slot,
|
|
navigator_slot,
|
|
screen_slot,
|
|
speech_synthesis_slot,
|
|
custom_elements_slot,
|
|
crypto_slot,
|
|
performance_slot,
|
|
visual_viewport_slot,
|
|
indexed_db_slot,
|
|
],
|
|
);
|
|
set_private_value(
|
|
scope,
|
|
global,
|
|
WINDOW_SURFACE_CALLBACK_DATA_REGISTRY_SLOT,
|
|
callback_data_registry.into(),
|
|
);
|
|
let navigation_name = callback_data_index_value(
|
|
scope,
|
|
window_surface_replaceable_name_index("navigation").unwrap(),
|
|
);
|
|
let screen_name = callback_data_index_value(
|
|
scope,
|
|
window_surface_replaceable_name_index("screen").unwrap(),
|
|
);
|
|
let performance_name = callback_data_index_value(
|
|
scope,
|
|
window_surface_replaceable_name_index("performance").unwrap(),
|
|
);
|
|
let visual_viewport_name = callback_data_index_value(
|
|
scope,
|
|
window_surface_replaceable_name_index("visualViewport").unwrap(),
|
|
);
|
|
let origin_name = callback_data_index_value(
|
|
scope,
|
|
window_surface_replaceable_name_index("origin").unwrap(),
|
|
);
|
|
let inner_width_name = callback_data_index_value(
|
|
scope,
|
|
window_surface_replaceable_name_index("innerWidth").unwrap(),
|
|
);
|
|
let length_name = callback_data_index_value(
|
|
scope,
|
|
window_surface_replaceable_name_index("length").unwrap(),
|
|
);
|
|
let event_name = callback_data_index_value(
|
|
scope,
|
|
window_surface_replaceable_name_index("event").unwrap(),
|
|
);
|
|
let outer_height_name = callback_data_index_value(
|
|
scope,
|
|
window_surface_replaceable_name_index("outerHeight").unwrap(),
|
|
);
|
|
let scroll_x_name = callback_data_index_value(
|
|
scope,
|
|
window_surface_replaceable_name_index("scrollX").unwrap(),
|
|
);
|
|
let screen_left_name = callback_data_index_value(
|
|
scope,
|
|
window_surface_replaceable_name_index("screenLeft").unwrap(),
|
|
);
|
|
let screen_top_name = callback_data_index_value(
|
|
scope,
|
|
window_surface_replaceable_name_index("screenTop").unwrap(),
|
|
);
|
|
let screen_x_name = callback_data_index_value(
|
|
scope,
|
|
window_surface_replaceable_name_index("screenX").unwrap(),
|
|
);
|
|
let screen_y_name = callback_data_index_value(
|
|
scope,
|
|
window_surface_replaceable_name_index("screenY").unwrap(),
|
|
);
|
|
|
|
WindowPublicSurfaceAccessorsDeclaration {
|
|
history_slot,
|
|
navigation_slot,
|
|
navigator_slot,
|
|
screen_slot,
|
|
speech_synthesis_slot,
|
|
custom_elements_slot,
|
|
crypto_slot,
|
|
performance_slot,
|
|
visual_viewport_slot,
|
|
indexed_db_slot,
|
|
navigation_name,
|
|
screen_name,
|
|
performance_name,
|
|
visual_viewport_name,
|
|
history: (),
|
|
navigation: (),
|
|
navigator: (),
|
|
screen: (),
|
|
speech_synthesis: (),
|
|
custom_elements: (),
|
|
crypto: (),
|
|
performance: (),
|
|
visual_viewport: (),
|
|
local_storage: (),
|
|
session_storage: (),
|
|
indexed_db: (),
|
|
name: (),
|
|
}
|
|
.initialize(scope, global)?;
|
|
WindowAdditionalReplaceableAccessorsDeclaration {
|
|
origin_name,
|
|
inner_width_name,
|
|
length_name,
|
|
event_name,
|
|
outer_height_name,
|
|
scroll_x_name,
|
|
screen_left_name,
|
|
screen_top_name,
|
|
screen_x_name,
|
|
screen_y_name,
|
|
origin: (),
|
|
inner_width: (),
|
|
length: (),
|
|
event: (),
|
|
outer_height: (),
|
|
scroll_x: (),
|
|
screen_left: (),
|
|
screen_top: (),
|
|
screen_x: (),
|
|
screen_y: (),
|
|
}
|
|
.initialize(scope, global)?;
|
|
Ok(())
|
|
}
|
|
|
|
fn window_surface_callback_data<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
global: v8::Local<'s, v8::Object>,
|
|
slot: &'static str,
|
|
) -> v8::Local<'s, v8::Value> {
|
|
let index = window_surface_slot_index(slot).expect("Window surface slot should be registered");
|
|
let fallback = hidden_slot_value_for_receiver(scope, global, slot)
|
|
.unwrap_or_else(|| v8::undefined(scope).into());
|
|
super::window_receiver::bound_callback_data(scope, index, global, fallback)
|
|
}
|
|
|
|
pub(super) fn update_window_surface_detached_fallback<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
receiver: v8::Local<'s, v8::Object>,
|
|
slot: &'static str,
|
|
value: v8::Local<'s, v8::Value>,
|
|
) {
|
|
let index = window_surface_slot_index(slot)
|
|
.unwrap_or_else(|| panic!("Window surface slot `{slot}` is not registered"));
|
|
let data = get_private_value(scope, receiver, WINDOW_SURFACE_CALLBACK_DATA_REGISTRY_SLOT)
|
|
.and_then(|value| v8::Local::<v8::Array>::try_from(value).ok())
|
|
.and_then(|registry| registry.get_index(scope, index as u32))
|
|
.and_then(|value| v8::Local::<v8::Array>::try_from(value).ok())
|
|
.unwrap_or_else(|| panic!("missing Window callback data for `{slot}`"));
|
|
assert_eq!(
|
|
data.set_index(scope, 2, value),
|
|
Some(true),
|
|
"failed to update detached Window fallback for `{slot}`"
|
|
);
|
|
}
|
|
|
|
fn window_surface_slot_index(slot: &str) -> Option<usize> {
|
|
WINDOW_SURFACE_SLOTS
|
|
.iter()
|
|
.position(|candidate| *candidate == slot)
|
|
}
|
|
|
|
fn window_surface_replaceable_name_index(name: &str) -> Option<usize> {
|
|
WINDOW_SURFACE_REPLACEABLE_NAMES
|
|
.iter()
|
|
.position(|candidate| *candidate == name)
|
|
}
|
|
|
|
const WINDOW_SURFACE_SLOTS: &[&str] = &[
|
|
WINDOW_HISTORY_SLOT,
|
|
WINDOW_NAVIGATION_SLOT,
|
|
WINDOW_NAVIGATOR_SLOT,
|
|
WINDOW_SCREEN_SLOT,
|
|
WINDOW_SPEECH_SYNTHESIS_SLOT,
|
|
WINDOW_CUSTOM_ELEMENTS_SLOT,
|
|
WINDOW_CRYPTO_SLOT,
|
|
WINDOW_PERFORMANCE_SLOT,
|
|
WINDOW_VISUAL_VIEWPORT_SLOT,
|
|
WINDOW_INDEXED_DB_SURFACE_SLOT,
|
|
];
|
|
|
|
const WINDOW_SURFACE_CALLBACK_DATA_REGISTRY_SLOT: &str = "__moliWindowSurfaceCallbackDataRegistry";
|
|
|
|
const WINDOW_SURFACE_REPLACEABLE_NAMES: &[&str] = &[
|
|
"navigation",
|
|
"screen",
|
|
"performance",
|
|
"visualViewport",
|
|
"origin",
|
|
"innerWidth",
|
|
"length",
|
|
"event",
|
|
"outerHeight",
|
|
"scrollX",
|
|
"screenLeft",
|
|
"screenTop",
|
|
"screenX",
|
|
"screenY",
|
|
];
|
|
|
|
fn legacy_unforgeable_document_getter<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
mut rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
let receiver = args.this();
|
|
let Some(host_ptr) = context_host_ptr_from_window_object(scope, receiver)
|
|
.or_else(|| context_host_ptr_from_global_bridge(scope))
|
|
else {
|
|
rv.set_null();
|
|
return;
|
|
};
|
|
if let Some(child_handle) = child_context_handle_from_owner(scope, receiver) {
|
|
match unsafe { &mut *host_ptr }.child_browsing_context_document_wrapper(scope, child_handle)
|
|
{
|
|
Some(document) => rv.set(document.into()),
|
|
None => rv.set_null(),
|
|
}
|
|
return;
|
|
}
|
|
let handle = unsafe { &*host_ptr }.document_handle();
|
|
match unsafe { &mut *host_ptr }
|
|
.native_bridge_mut()
|
|
.wrap_handle(scope, host_ptr, handle)
|
|
{
|
|
Some(document) => rv.set(document.into()),
|
|
None => rv.set_null(),
|
|
}
|
|
}
|
|
|
|
fn child_context_handle_from_owner<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
owner: v8::Local<'s, v8::Object>,
|
|
) -> Option<DomHandle> {
|
|
private_child_context_handle_from_object(scope, owner)
|
|
}
|
|
|
|
fn private_child_context_handle_from_object<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
object: v8::Local<'s, v8::Object>,
|
|
) -> Option<DomHandle> {
|
|
let value = get_private_value(scope, object, CHILD_BROWSING_CONTEXT_HANDLE_SLOT)?;
|
|
dom_handle_from_value(scope, value)
|
|
}
|
|
|
|
fn dom_handle_from_value<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
value: v8::Local<'s, v8::Value>,
|
|
) -> Option<DomHandle> {
|
|
let n = value.number_value(scope)?;
|
|
if n.is_finite() && n >= 0.0 && n.fract() == 0.0 {
|
|
Some(DomHandle::new(n as usize))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn child_window_eval_base_url<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
window: v8::Local<'s, v8::Object>,
|
|
) -> Option<url::Url> {
|
|
// Child frames do not eagerly get their own native V8 Context in the
|
|
// normal page lifecycle yet. The child Window shell still carries the
|
|
// location state we need for eval/module resolution, including after the
|
|
// iframe has been detached and author script only holds contentWindow.
|
|
let location = object_own_hidden_value(scope, window, WINDOW_LOCATION_SLOT)
|
|
.filter(|value| !value.is_undefined())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())?;
|
|
location_href_slot(scope, location).and_then(|href| url::Url::parse(&href).ok())
|
|
}
|
|
|
|
fn run_child_window_eval_expression<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
window: v8::Local<'s, v8::Object>,
|
|
handle: DomHandle,
|
|
expression: &str,
|
|
) -> Option<v8::Local<'s, v8::Value>> {
|
|
let base_url = child_window_eval_base_url(scope, window);
|
|
// This intentionally runs the saved child Window object directly instead
|
|
// of resolving `handle` through the live child-frame registry. Blink keeps
|
|
// an initialized detached WindowProxy usable by author-held references; in
|
|
// our current single-context model, the closest owner boundary is the
|
|
// retained child Window shell.
|
|
//
|
|
// Passing the expression as an eval argument is also deliberate: V8 parses
|
|
// the author source, so dynamic import goes through the normal V8
|
|
// HostImportModuleDynamically callback. Avoid source-string prefix checks
|
|
// such as "import(" here.
|
|
let source = v8_string(
|
|
scope,
|
|
r#"(function(window, __expr) {
|
|
const document = window.document;
|
|
return (function(window, document, globalThis) {
|
|
with (window) {
|
|
return eval(__expr);
|
|
}
|
|
}).call(window, window, document, window);
|
|
})"#,
|
|
)?;
|
|
let origin = base_url.as_ref().map(|base_url| {
|
|
create_script_origin_with_base_url(scope, base_url.as_str(), 0, Some(base_url))
|
|
});
|
|
let function = v8::Script::compile(scope, source, origin.as_ref())
|
|
.and_then(|script| script.run(scope))
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())?;
|
|
let expression = v8_string(scope, expression)?;
|
|
|
|
let previous_active_child = native_bridge::enter_active_child_window_scope(scope, Some(handle));
|
|
let previous_active_child = v8::Global::new(scope, previous_active_child);
|
|
let previous_continuation_data = scope.get_continuation_preserved_embedder_data();
|
|
// The active child marker and continuation data are the bridge between the
|
|
// child Window shell and module/network machinery that still runs in the
|
|
// main native context. Restore both immediately after the eval call.
|
|
if let Some(base_url) = base_url.as_ref()
|
|
&& let Some(value) = script_base_url_continuation_data(scope, base_url)
|
|
{
|
|
scope.set_continuation_preserved_embedder_data(value);
|
|
}
|
|
let result = function.call(scope, window.into(), &[window.into(), expression.into()]);
|
|
scope.set_continuation_preserved_embedder_data(previous_continuation_data);
|
|
let previous_active_child = v8::Local::new(scope, &previous_active_child);
|
|
native_bridge::restore_active_child_window_scope(scope, previous_active_child);
|
|
|
|
result.map(|value| mark_detached_child_eval_promise_handled(scope, handle, value))
|
|
}
|
|
|
|
fn mark_detached_child_eval_promise_handled<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
handle: DomHandle,
|
|
value: v8::Local<'s, v8::Value>,
|
|
) -> v8::Local<'s, v8::Value> {
|
|
// Detached child eval can still return the promise created by V8 for
|
|
// import(). If the child browsing context is no longer live, treat that
|
|
// promise as owned by the inactive context so a later rejected dynamic
|
|
// import does not become a top-level unhandled rejection in the parent WPT
|
|
// harness. Keep this limited to real V8 promises and detached children.
|
|
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
|
return value;
|
|
};
|
|
if unsafe { &*host_ptr }.child_browsing_context_is_live(handle) {
|
|
return value;
|
|
}
|
|
let Ok(promise) = v8::Local::<v8::Promise>::try_from(value) else {
|
|
return value;
|
|
};
|
|
let promise_value: v8::Local<'s, v8::Value> = promise.into();
|
|
let Ok(promise_object) = v8::Local::<v8::Object>::try_from(promise_value) else {
|
|
return value;
|
|
};
|
|
let Some(catch) = promise_object
|
|
.get(scope, v8str(scope, "catch").into())
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
|
else {
|
|
return value;
|
|
};
|
|
let Some(noop) = v8::Function::builder(window_noop_callback).build(scope) else {
|
|
return value;
|
|
};
|
|
let _ = catch.call(scope, promise_object.into(), &[noop.into()]);
|
|
value
|
|
}
|
|
|
|
fn evaluate_child_window_expression<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
window: v8::Local<'s, v8::Object>,
|
|
handle: DomHandle,
|
|
expression: &str,
|
|
) -> Result<v8::Local<'s, v8::Value>> {
|
|
let eval_key = v8str(scope, "eval");
|
|
let own_eval = window
|
|
.get(scope, eval_key.into())
|
|
.ok_or_else(|| anyhow!("failed to capture child Window eval"))?;
|
|
let intrinsic_eval = get_private_value(scope, window, WINDOW_INTRINSIC_EVAL_SLOT)
|
|
.ok_or_else(|| anyhow!("missing child Window intrinsic eval"))?;
|
|
if !window
|
|
.define_own_property(
|
|
scope,
|
|
eval_key.into(),
|
|
intrinsic_eval,
|
|
v8::PropertyAttribute::DONT_ENUM,
|
|
)
|
|
.unwrap_or(false)
|
|
{
|
|
bail!("failed to expose child Window intrinsic eval");
|
|
}
|
|
|
|
let result = {
|
|
let try_catch = std::pin::pin!(v8::TryCatch::new(scope));
|
|
let mut scope = try_catch.init();
|
|
run_child_window_eval_expression(&mut scope, window, handle, expression)
|
|
.map(|value| v8::Global::new(&scope, value))
|
|
.ok_or_else(|| {
|
|
let message = scope
|
|
.message()
|
|
.map(|message| message.get(&scope).to_rust_string_lossy(&scope))
|
|
.or_else(|| {
|
|
scope
|
|
.exception()
|
|
.and_then(|value| value.to_string(&scope))
|
|
.map(|value| value.to_rust_string_lossy(&scope))
|
|
})
|
|
.unwrap_or_else(|| "failed to run child window eval source".to_owned());
|
|
anyhow!(message)
|
|
})
|
|
};
|
|
|
|
if !window
|
|
.define_own_property(
|
|
scope,
|
|
eval_key.into(),
|
|
own_eval,
|
|
v8::PropertyAttribute::DONT_ENUM,
|
|
)
|
|
.unwrap_or(false)
|
|
{
|
|
bail!("failed to restore child Window eval");
|
|
}
|
|
|
|
result.map(|value| v8::Local::new(scope, &value))
|
|
}
|
|
|
|
fn child_window_eval_callback<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
mut rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
if args.length() < 1 {
|
|
rv.set_undefined();
|
|
return;
|
|
}
|
|
|
|
let argument = args.get(0);
|
|
if !argument.is_string() {
|
|
rv.set(argument);
|
|
return;
|
|
}
|
|
|
|
let receiver = callback_this_object(scope, &args);
|
|
let Some(handle) = child_context_handle_from_owner(scope, receiver) else {
|
|
rv.set(argument);
|
|
return;
|
|
};
|
|
let Some(expression) = argument
|
|
.to_string(scope)
|
|
.map(|value| value.to_rust_string_lossy(scope))
|
|
else {
|
|
rv.set(argument);
|
|
return;
|
|
};
|
|
|
|
match evaluate_child_window_expression(scope, receiver, handle, &expression) {
|
|
Ok(value) => rv.set(value),
|
|
Err(error) => {
|
|
let exception = v8::Exception::error(
|
|
scope,
|
|
v8_string(scope, &error.to_string())
|
|
.unwrap_or_else(|| v8str(scope, "child window eval failed")),
|
|
);
|
|
scope.throw_exception(exception);
|
|
}
|
|
}
|
|
}
|
|
|
|
pub(crate) fn install_child_window_eval_runtime_state<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
window: v8::Local<'s, v8::Object>,
|
|
) -> Result<()> {
|
|
let inherited_or_intrinsic_eval = get_private_value(scope, window, WINDOW_INTRINSIC_EVAL_SLOT)
|
|
.filter(|value| value.is_function())
|
|
.or_else(|| {
|
|
window
|
|
.get(scope, v8str(scope, "eval").into())
|
|
.filter(|value| value.is_function())
|
|
})
|
|
.or_else(|| {
|
|
let global = scope.get_current_context().global(scope);
|
|
get_private_value(scope, global, WINDOW_INTRINSIC_EVAL_SLOT)
|
|
})
|
|
.ok_or_else(|| anyhow!("failed to resolve child Window intrinsic eval"))?;
|
|
set_private_value(
|
|
scope,
|
|
window,
|
|
WINDOW_INTRINSIC_EVAL_SLOT,
|
|
inherited_or_intrinsic_eval,
|
|
);
|
|
let eval = v8::Function::builder(child_window_eval_callback)
|
|
.build(scope)
|
|
.ok_or_else(|| anyhow!("failed to create child window eval callback"))?;
|
|
let _ = window.define_own_property(
|
|
scope,
|
|
v8str(scope, "eval").into(),
|
|
eval.into(),
|
|
v8::PropertyAttribute::DONT_ENUM,
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
fn define_legacy_unforgeable_document_property<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
object: v8::Local<'s, v8::Object>,
|
|
) -> Result<()> {
|
|
let getter = v8::Function::builder(legacy_unforgeable_document_getter)
|
|
.build(scope)
|
|
.ok_or_else(|| anyhow!("failed to build document getter"))?;
|
|
getter.set_name(v8str(scope, "get document"));
|
|
define_get_set_property(
|
|
scope,
|
|
object,
|
|
v8str(scope, "document").into(),
|
|
getter.into(),
|
|
v8::undefined(scope).into(),
|
|
v8::PropertyAttribute::DONT_DELETE,
|
|
"document",
|
|
)
|
|
}
|
|
|
|
fn document_all_runtime_getter<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
mut rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
match document_all_value_for_receiver(scope, args.this()) {
|
|
Some(value) => rv.set(value),
|
|
None => rv.set(v8::undefined(scope).into()),
|
|
}
|
|
}
|
|
|
|
fn document_cookie_runtime_getter<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
mut rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
let receiver = args.this();
|
|
let cookie = document_cookie_for_receiver(scope, receiver).unwrap_or_default();
|
|
let value = v8_string(scope, &cookie).unwrap_or_else(|| v8::String::empty(scope));
|
|
rv.set(value.into());
|
|
}
|
|
|
|
fn document_cookie_runtime_setter<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
_rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
let receiver = args.this();
|
|
let Some(value) = args.get(0).to_string(scope) else {
|
|
return;
|
|
};
|
|
let _ = set_document_cookie_for_receiver(scope, receiver, &value.to_rust_string_lossy(scope));
|
|
}
|
|
|
|
#[derive(webidl::WebIdlArgs)]
|
|
#[webidl(prefix = "Document.parseHTMLUnsafe")]
|
|
struct DocumentParseHtmlUnsafeArgs {
|
|
#[webidl(required, with = document_parse_html_unsafe_source_arg)]
|
|
html: DocumentParseHtmlUnsafeSource,
|
|
}
|
|
|
|
enum DocumentParseHtmlUnsafeSource {
|
|
TrustedHtml(String),
|
|
String(String),
|
|
}
|
|
|
|
fn document_parse_html_unsafe_source_arg<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: &v8::FunctionCallbackArguments<'s>,
|
|
index: i32,
|
|
) -> Result<DocumentParseHtmlUnsafeSource, webidl::WebIdlError> {
|
|
if args.length() <= index {
|
|
return Err(webidl::WebIdlError::custom_message(
|
|
"Failed to execute 'parseHTMLUnsafe' on 'Document': 1 argument required, but only 0 present.",
|
|
));
|
|
}
|
|
let value = args.get(index);
|
|
if let Some(value) = crate::context_bootstrap::trusted_html_value_string(scope, value) {
|
|
return Ok(DocumentParseHtmlUnsafeSource::TrustedHtml(value));
|
|
}
|
|
webidl::convert::<webidl::DomString>(
|
|
scope,
|
|
value,
|
|
webidl::Context::argument("Document.parseHTMLUnsafe", (index + 1) as usize),
|
|
)
|
|
.map(|value| DocumentParseHtmlUnsafeSource::String(value.0))
|
|
}
|
|
|
|
fn document_parse_html_unsafe_callback<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
mut rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
let Some(parsed) = webidl::parse_args::<DocumentParseHtmlUnsafeArgs>(scope, &args) else {
|
|
return;
|
|
};
|
|
let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) else {
|
|
rv.set(v8::null(scope).into());
|
|
return;
|
|
};
|
|
let runtime = unsafe { &*host_ptr };
|
|
let html = match parsed.html {
|
|
DocumentParseHtmlUnsafeSource::TrustedHtml(html) => html,
|
|
DocumentParseHtmlUnsafeSource::String(html) => {
|
|
let Some(value) = v8_string(scope, &html) else {
|
|
return;
|
|
};
|
|
let requirements = runtime.trusted_types_for_script_requirements(scope);
|
|
let Some(html) = crate::context_bootstrap::trusted_html_string_or_throw(
|
|
scope,
|
|
value.into(),
|
|
requirements,
|
|
"Document parseHTMLUnsafe",
|
|
"parseHTMLUnsafe",
|
|
) else {
|
|
return;
|
|
};
|
|
html
|
|
}
|
|
};
|
|
let Some(document) =
|
|
crate::dom_parser::parse_detached_html_document_from_source_with_declarative_shadow_roots(
|
|
scope,
|
|
runtime.document_url().clone(),
|
|
&html,
|
|
)
|
|
else {
|
|
rv.set(v8::null(scope).into());
|
|
return;
|
|
};
|
|
rv.set(document.into());
|
|
}
|
|
|
|
fn install_document_static_methods<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
_global: v8::Local<'s, v8::Object>,
|
|
) -> Result<()> {
|
|
let Some(document_constructor) = global_constructor_object(scope, "Document") else {
|
|
return Ok(());
|
|
};
|
|
DocumentStaticMethodsDeclaration::default()
|
|
.initialize(scope, document_constructor)
|
|
.map_err(|err| anyhow!("failed to install Document static methods: {err}"))?;
|
|
Ok(())
|
|
}
|
|
|
|
fn install_document_runtime_state<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
global: v8::Local<'s, v8::Object>,
|
|
) -> Result<()> {
|
|
install_document_static_methods(scope, global)?;
|
|
if let Some(prototype) = global_constructor_prototype(scope, "Document") {
|
|
DocumentPrototypeRuntimeDeclaration::default().initialize(scope, prototype)?;
|
|
}
|
|
if let Some(document) = global
|
|
.get(scope, v8str(scope, "document").into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
{
|
|
sync_document_location_runtime_state_from_window(scope, document, global);
|
|
}
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(feature = "wpt-extensions")]
|
|
fn webdriver_constructor_callback(
|
|
scope: &mut v8::PinScope<'_, '_>,
|
|
args: v8::FunctionCallbackArguments<'_>,
|
|
mut rv: v8::ReturnValue<'_, v8::Value>,
|
|
) {
|
|
if args.is_construct_call() {
|
|
rv.set(args.this().into());
|
|
return;
|
|
}
|
|
webidl::throw_type_error(scope, "WebDriver constructor requires 'new'");
|
|
}
|
|
|
|
#[cfg(feature = "wpt-extensions")]
|
|
fn webdriver_delete_all_cookies_callback(
|
|
scope: &mut v8::PinScope<'_, '_>,
|
|
_args: v8::FunctionCallbackArguments<'_>,
|
|
mut rv: v8::ReturnValue<'_, v8::Value>,
|
|
) {
|
|
if let Some(host_ptr) = context_host_ptr_from_global_bridge(scope) {
|
|
unsafe { &mut *host_ptr }.delete_all_cookies_for_wpt();
|
|
}
|
|
rv.set(v8::undefined(scope).into());
|
|
}
|
|
|
|
#[cfg(feature = "wpt-extensions")]
|
|
pub(crate) fn install_wpt_webdriver_runtime_state(
|
|
scope: &mut v8::PinScope<'_, '_>,
|
|
global: v8::Local<'_, v8::Object>,
|
|
) -> Result<()> {
|
|
if global
|
|
.get(scope, v8str(scope, "webdriver").into())
|
|
.is_some_and(|value| !value.is_undefined())
|
|
{
|
|
return Ok(());
|
|
}
|
|
|
|
let constructor_key = v8str(scope, "WebDriver");
|
|
let constructor = v8::Function::builder(webdriver_constructor_callback)
|
|
.length(0)
|
|
.build(scope)
|
|
.ok_or_else(|| anyhow!("failed to create WebDriver constructor"))?;
|
|
constructor.set_name(constructor_key);
|
|
|
|
let prototype = constructor
|
|
.get(scope, v8str(scope, "prototype").into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
.ok_or_else(|| anyhow!("failed to get WebDriver.prototype"))?;
|
|
WebDriverPrototypeDeclaration::new()
|
|
.initialize(scope, prototype)
|
|
.map_err(|error| anyhow!("failed to initialize WebDriver.prototype: {error}"))?;
|
|
WebDriverPrototypeMetadataDeclaration::new(constructor)
|
|
.initialize(scope, prototype)
|
|
.map_err(|error| anyhow!("failed to initialize WebDriver prototype metadata: {error}"))?;
|
|
define_global_value(scope, global, "WebDriver", constructor.into())?;
|
|
let webdriver = WebDriverObjectDeclaration::new()
|
|
.bind(scope)
|
|
.map_err(|error| anyhow!("failed to create WebDriver object: {error}"))?;
|
|
define_global_value(scope, global, "webdriver", webdriver.into())
|
|
}
|
|
|
|
pub(crate) fn finish_context_bootstrap(
|
|
scope: &mut v8::PinScope<'_, '_>,
|
|
document_runtime: &mut JsContextHost,
|
|
secure_context_url: &url::Url,
|
|
) -> Result<()> {
|
|
super::exposed_interfaces::initialize_realm_interface_registry(
|
|
scope,
|
|
super::exposed_interfaces::RealmKind::Window,
|
|
)?;
|
|
install_window_named_properties_object(scope)?;
|
|
let global = scope.get_current_context().global(scope);
|
|
// V8 creates its inspector-aware console as an own global property after
|
|
// applying the embedder ObjectTemplate. Preserve it for forwarding, then
|
|
// replace it with Moli's observable console object. This property
|
|
// cannot use the Window ObjectTemplate accessor path because V8's own
|
|
// console shadows that descriptor during context creation.
|
|
let console_key = v8str(scope, "console");
|
|
if let Some(original_console) = global.get(scope, console_key.into()) {
|
|
set_private_value(
|
|
scope,
|
|
global,
|
|
WINDOW_ORIGINAL_CONSOLE_SLOT,
|
|
original_console,
|
|
);
|
|
}
|
|
let _ = global.delete(scope, console_key.into());
|
|
install_window_runtime_state(scope, global, document_runtime, secure_context_url)?;
|
|
// WPT harness helper only. Normal builds keep the feature disabled so pages
|
|
// do not observe non-standard `webdriver` / `WebDriver` globals.
|
|
#[cfg(feature = "wpt-extensions")]
|
|
if document_runtime.wpt_extensions_enabled() {
|
|
install_wpt_webdriver_runtime_state(scope, global)?;
|
|
}
|
|
|
|
install_node_filter_constants(scope, global);
|
|
for (ctor_name, tag) in [
|
|
("NodeList", "NodeList"),
|
|
("HTMLCollection", "HTMLCollection"),
|
|
("PluginArray", "PluginArray"),
|
|
("MimeTypeArray", "MimeTypeArray"),
|
|
("Plugin", "Plugin"),
|
|
("MimeType", "MimeType"),
|
|
("Attr", "Attr"),
|
|
("NamedNodeMap", "NamedNodeMap"),
|
|
("HTMLAllCollection", "HTMLAllCollection"),
|
|
("DOMException", "DOMException"),
|
|
("DOMError", "DOMError"),
|
|
("DocumentType", "DocumentType"),
|
|
("DOMImplementation", "DOMImplementation"),
|
|
("DOMTokenList", "DOMTokenList"),
|
|
("DOMStringMap", "DOMStringMap"),
|
|
("CustomElementRegistry", "CustomElementRegistry"),
|
|
("HTMLFormControlsCollection", "HTMLFormControlsCollection"),
|
|
("HTMLOptionsCollection", "HTMLOptionsCollection"),
|
|
("RadioNodeList", "RadioNodeList"),
|
|
("ValidityState", "ValidityState"),
|
|
("EventTarget", "EventTarget"),
|
|
("HTMLLabelElement", "HTMLLabelElement"),
|
|
("NodeIterator", "NodeIterator"),
|
|
("TreeWalker", "TreeWalker"),
|
|
("XPathEvaluator", "XPathEvaluator"),
|
|
("XPathResult", "XPathResult"),
|
|
("SVGLength", "SVGLength"),
|
|
("SVGNumber", "SVGNumber"),
|
|
("SVGAnimatedLength", "SVGAnimatedLength"),
|
|
("SVGLengthList", "SVGLengthList"),
|
|
("SVGAnimatedLengthList", "SVGAnimatedLengthList"),
|
|
("SVGAnimatedNumber", "SVGAnimatedNumber"),
|
|
("SVGNumberList", "SVGNumberList"),
|
|
("SVGAnimatedNumberList", "SVGAnimatedNumberList"),
|
|
("SVGAnimatedEnumeration", "SVGAnimatedEnumeration"),
|
|
("SVGAnimatedTransformList", "SVGAnimatedTransformList"),
|
|
("SVGTransformList", "SVGTransformList"),
|
|
("SVGTransform", "SVGTransform"),
|
|
("SVGMatrix", "SVGMatrix"),
|
|
("StyleSheet", "StyleSheet"),
|
|
("StyleSheetList", "StyleSheetList"),
|
|
("MediaList", "MediaList"),
|
|
("CSSRuleList", "CSSRuleList"),
|
|
("CSSRule", "CSSRule"),
|
|
("CSSGroupingRule", "CSSGroupingRule"),
|
|
("CSSConditionRule", "CSSConditionRule"),
|
|
("CSSMediaRule", "CSSMediaRule"),
|
|
("CSSSupportsRule", "CSSSupportsRule"),
|
|
("CSSContainerRule", "CSSContainerRule"),
|
|
("CSSLayerBlockRule", "CSSLayerBlockRule"),
|
|
("CSSLayerStatementRule", "CSSLayerStatementRule"),
|
|
("CSSScopeRule", "CSSScopeRule"),
|
|
("CSSImportRule", "CSSImportRule"),
|
|
("CSSFontFaceRule", "CSSFontFaceRule"),
|
|
("CSSFontFeatureValuesRule", "CSSFontFeatureValuesRule"),
|
|
("CSSKeyframesRule", "CSSKeyframesRule"),
|
|
("CSSKeyframeRule", "CSSKeyframeRule"),
|
|
("CSSPageRule", "CSSPageRule"),
|
|
("CSSMarginRule", "CSSMarginRule"),
|
|
("CSSNamespaceRule", "CSSNamespaceRule"),
|
|
("CSSCounterStyleRule", "CSSCounterStyleRule"),
|
|
("CSSStyleRule", "CSSStyleRule"),
|
|
("CSSNestedDeclarations", "CSSNestedDeclarations"),
|
|
("CSSStyleDeclaration", "CSSStyleDeclaration"),
|
|
("CSSStyleProperties", "CSSStyleProperties"),
|
|
("CSSFontFaceDescriptors", "CSSFontFaceDescriptors"),
|
|
("CSSPageDescriptors", "CSSPageDescriptors"),
|
|
("CSSStyleSheet", "CSSStyleSheet"),
|
|
("FontFace", "FontFace"),
|
|
("FontFaceSet", "FontFaceSet"),
|
|
("FontFaceSetLoadEvent", "FontFaceSetLoadEvent"),
|
|
("BaseAudioContext", "BaseAudioContext"),
|
|
("OfflineAudioContext", "OfflineAudioContext"),
|
|
("AudioDestinationNode", "AudioDestinationNode"),
|
|
("OscillatorNode", "OscillatorNode"),
|
|
("DynamicsCompressorNode", "DynamicsCompressorNode"),
|
|
("AnalyserNode", "AnalyserNode"),
|
|
("AudioParam", "AudioParam"),
|
|
("AudioBuffer", "AudioBuffer"),
|
|
("AbortSignal", "AbortSignal"),
|
|
("AbortController", "AbortController"),
|
|
("TextEncoder", "TextEncoder"),
|
|
("TextDecoder", "TextDecoder"),
|
|
("ReadableStream", "ReadableStream"),
|
|
("ReadableStreamDefaultReader", "ReadableStreamDefaultReader"),
|
|
(
|
|
"ReadableStreamDefaultController",
|
|
"ReadableStreamDefaultController",
|
|
),
|
|
("WritableStream", "WritableStream"),
|
|
("WritableStreamDefaultWriter", "WritableStreamDefaultWriter"),
|
|
(
|
|
"WritableStreamDefaultController",
|
|
"WritableStreamDefaultController",
|
|
),
|
|
("TransformStream", "TransformStream"),
|
|
(
|
|
"TransformStreamDefaultController",
|
|
"TransformStreamDefaultController",
|
|
),
|
|
("TextEncoderStream", "TextEncoderStream"),
|
|
("TextDecoderStream", "TextDecoderStream"),
|
|
("Performance", "Performance"),
|
|
("PerformanceEntry", "PerformanceEntry"),
|
|
("PerformanceMark", "PerformanceMark"),
|
|
("PerformanceMeasure", "PerformanceMeasure"),
|
|
("PerformanceResourceTiming", "PerformanceResourceTiming"),
|
|
("EventCounts", "EventCounts"),
|
|
("MediaQueryList", "MediaQueryList"),
|
|
("MediaError", "MediaError"),
|
|
("TextTrack", "TextTrack"),
|
|
("TextTrackList", "TextTrackList"),
|
|
("TextTrackCue", "TextTrackCue"),
|
|
("TextTrackCueList", "TextTrackCueList"),
|
|
("TrackEvent", "TrackEvent"),
|
|
("VTTCue", "VTTCue"),
|
|
("Crypto", "Crypto"),
|
|
("SubtleCrypto", "SubtleCrypto"),
|
|
("CryptoKey", "CryptoKey"),
|
|
("VisualViewport", "VisualViewport"),
|
|
("Navigator", "Navigator"),
|
|
("Permissions", "Permissions"),
|
|
("PermissionStatus", "PermissionStatus"),
|
|
("MediaDevices", "MediaDevices"),
|
|
("MediaCapabilities", "MediaCapabilities"),
|
|
("Screen", "Screen"),
|
|
("ScreenOrientation", "ScreenOrientation"),
|
|
("Touch", "Touch"),
|
|
("TouchList", "TouchList"),
|
|
("TouchEvent", "TouchEvent"),
|
|
("BroadcastChannel", "BroadcastChannel"),
|
|
("EventSource", "EventSource"),
|
|
("MessageChannel", "MessageChannel"),
|
|
("MessagePort", "MessagePort"),
|
|
("Event", "Event"),
|
|
("CustomEvent", "CustomEvent"),
|
|
("DragEvent", "DragEvent"),
|
|
("InputEvent", "InputEvent"),
|
|
("MessageEvent", "MessageEvent"),
|
|
("StorageEvent", "StorageEvent"),
|
|
("PageTransitionEvent", "PageTransitionEvent"),
|
|
("PopStateEvent", "PopStateEvent"),
|
|
(
|
|
"NavigationCurrentEntryChangeEvent",
|
|
"NavigationCurrentEntryChangeEvent",
|
|
),
|
|
("NavigateEvent", "NavigateEvent"),
|
|
("ErrorEvent", "ErrorEvent"),
|
|
("CloseEvent", "CloseEvent"),
|
|
("SubmitEvent", "SubmitEvent"),
|
|
("FormDataEvent", "FormDataEvent"),
|
|
("Worker", "Worker"),
|
|
("SharedWorker", "SharedWorker"),
|
|
("WebSocket", "WebSocket"),
|
|
("WebSocketError", "WebSocketError"),
|
|
("WebSocketStream", "WebSocketStream"),
|
|
("RTCPeerConnection", "RTCPeerConnection"),
|
|
("RTCRtpReceiver", "RTCRtpReceiver"),
|
|
("RTCDataChannel", "RTCDataChannel"),
|
|
("Blob", "Blob"),
|
|
("File", "File"),
|
|
("DataTransfer", "DataTransfer"),
|
|
("ImageData", "ImageData"),
|
|
("ImageBitmap", "ImageBitmap"),
|
|
("CanvasGradient", "CanvasGradient"),
|
|
("CanvasPattern", "CanvasPattern"),
|
|
("TextMetrics", "TextMetrics"),
|
|
("Path2D", "Path2D"),
|
|
("OffscreenCanvas", "OffscreenCanvas"),
|
|
("CanvasRenderingContext2D", "CanvasRenderingContext2D"),
|
|
(
|
|
"OffscreenCanvasRenderingContext2D",
|
|
"OffscreenCanvasRenderingContext2D",
|
|
),
|
|
("WebGLRenderingContext", "WebGLRenderingContext"),
|
|
("WebGL2RenderingContext", "WebGL2RenderingContext"),
|
|
("WebGLObject", "WebGLObject"),
|
|
("WebGLBuffer", "WebGLBuffer"),
|
|
("WebGLFramebuffer", "WebGLFramebuffer"),
|
|
("WebGLProgram", "WebGLProgram"),
|
|
("WebGLRenderbuffer", "WebGLRenderbuffer"),
|
|
("WebGLShader", "WebGLShader"),
|
|
("WebGLUniformLocation", "WebGLUniformLocation"),
|
|
("WEBGL_debug_renderer_info", "WEBGL_debug_renderer_info"),
|
|
("WEBGL_lose_context", "WEBGL_lose_context"),
|
|
("FileList", "FileList"),
|
|
("FileReader", "FileReader"),
|
|
("DOMRect", "DOMRect"),
|
|
("DOMPoint", "DOMPoint"),
|
|
("CaretPosition", "CaretPosition"),
|
|
("DOMMatrixReadOnly", "DOMMatrixReadOnly"),
|
|
("DOMMatrix", "DOMMatrix"),
|
|
("DOMParser", "DOMParser"),
|
|
("XMLSerializer", "XMLSerializer"),
|
|
("MutationObserver", "MutationObserver"),
|
|
("MutationRecord", "MutationRecord"),
|
|
("IntersectionObserver", "IntersectionObserver"),
|
|
("IntersectionObserverEntry", "IntersectionObserverEntry"),
|
|
("ResizeObserver", "ResizeObserver"),
|
|
("PerformanceObserver", "PerformanceObserver"),
|
|
(
|
|
"PerformanceObserverEntryList",
|
|
"PerformanceObserverEntryList",
|
|
),
|
|
("PerformanceTiming", "PerformanceTiming"),
|
|
("PerformanceNavigation", "PerformanceNavigation"),
|
|
("NavigatorUAData", "NavigatorUAData"),
|
|
("StorageManager", "StorageManager"),
|
|
("StorageEstimate", "StorageEstimate"),
|
|
("StorageAccessHandle", "StorageAccessHandle"),
|
|
("StorageBucketManager", "StorageBucketManager"),
|
|
("StorageBucket", "StorageBucket"),
|
|
("FileSystemHandle", "FileSystemHandle"),
|
|
("FileSystemFileHandle", "FileSystemFileHandle"),
|
|
("FileSystemDirectoryHandle", "FileSystemDirectoryHandle"),
|
|
(
|
|
"FileSystemWritableFileStream",
|
|
"FileSystemWritableFileStream",
|
|
),
|
|
("IdleDeadline", "IdleDeadline"),
|
|
("AbstractRange", "AbstractRange"),
|
|
("Range", "Range"),
|
|
("Selection", "Selection"),
|
|
("History", "History"),
|
|
("Location", "Location"),
|
|
("Navigation", "Navigation"),
|
|
("NavigationHistoryEntry", "NavigationHistoryEntry"),
|
|
("NavigationActivation", "NavigationActivation"),
|
|
("NavigationTransition", "NavigationTransition"),
|
|
("URL", "URL"),
|
|
("URLSearchParams", "URLSearchParams"),
|
|
("FormData", "FormData"),
|
|
("Headers", "Headers"),
|
|
("Request", "Request"),
|
|
("Response", "Response"),
|
|
("XMLHttpRequest", "XMLHttpRequest"),
|
|
("IDBFactory", "IDBFactory"),
|
|
("IDBRequest", "IDBRequest"),
|
|
("IDBOpenDBRequest", "IDBOpenDBRequest"),
|
|
("IDBDatabase", "IDBDatabase"),
|
|
("IDBTransaction", "IDBTransaction"),
|
|
("IDBObjectStore", "IDBObjectStore"),
|
|
("IDBIndex", "IDBIndex"),
|
|
("IDBCursor", "IDBCursor"),
|
|
("IDBCursorWithValue", "IDBCursorWithValue"),
|
|
("IDBKeyRange", "IDBKeyRange"),
|
|
("IDBVersionChangeEvent", "IDBVersionChangeEvent"),
|
|
("Window", "Window"),
|
|
] {
|
|
if is_lazy_exposed_interface(scope, ctor_name) {
|
|
continue;
|
|
}
|
|
install_to_string_tag(scope, global, ctor_name, tag);
|
|
}
|
|
install_window_global_accessors(scope, global);
|
|
|
|
native_bridge::install_detached_bridge_methods(scope);
|
|
reset_range_runtime_state(scope);
|
|
initialize_file_api_runtime_queues(scope, global)?;
|
|
super::exposed_interfaces::capture_eager_intrinsic_interfaces(
|
|
scope,
|
|
global,
|
|
super::exposed_interfaces::RealmKind::Window,
|
|
)?;
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn set_interface_prototype_constructor<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
constructor: v8::Local<'s, v8::Function>,
|
|
public_constructor: v8::Local<'s, v8::Value>,
|
|
) {
|
|
let Some(prototype) = constructor
|
|
.get(scope, v8str(scope, "prototype").into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
else {
|
|
return;
|
|
};
|
|
let _ = prototype.define_own_property(
|
|
scope,
|
|
v8str(scope, "constructor").into(),
|
|
public_constructor,
|
|
v8::PropertyAttribute::DONT_ENUM,
|
|
);
|
|
}
|
|
|
|
fn install_window_runtime_state<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
global: v8::Local<'s, v8::Object>,
|
|
runtime: &DocumentRuntime,
|
|
secure_context_url: &url::Url,
|
|
) -> Result<()> {
|
|
let secure_context_available = moli_url::is_potentially_trustworthy_url(secure_context_url);
|
|
set_private_value(
|
|
scope,
|
|
global,
|
|
WINDOW_SECURE_CONTEXT_AVAILABLE_SLOT,
|
|
v8::Boolean::new(scope, secure_context_available).into(),
|
|
);
|
|
install_date_locale_runtime_state(scope, global)?;
|
|
ensure_location_constructor_runtime_state(scope, global)?;
|
|
install_webassembly_runtime_state(scope, global)?;
|
|
install_webidl_collection_iterator_intrinsics(scope, global)?;
|
|
|
|
install_window_location_history_navigation_runtime_state(
|
|
scope,
|
|
global,
|
|
runtime.document_url().as_str(),
|
|
)?;
|
|
let origin = moli_url::origin_ascii_serialization(runtime.document_url());
|
|
set_window_origin_runtime_state(scope, global, &origin)?;
|
|
|
|
let console = ConsoleObjectDeclaration::default()
|
|
.bind(scope)
|
|
.map_err(|error| anyhow!("failed to create console object: {error}"))?;
|
|
let webkit_storage_info = build_legacy_storage_info_object(scope)?;
|
|
WindowBootstrapGlobalSlotsDeclaration::new(
|
|
console,
|
|
global,
|
|
global,
|
|
global,
|
|
global,
|
|
secure_context_available,
|
|
0,
|
|
1,
|
|
webkit_storage_info,
|
|
"",
|
|
console,
|
|
)
|
|
.initialize(scope, global)
|
|
.map_err(|error| anyhow!("failed to initialize Window bootstrap slots: {error}"))?;
|
|
define_legacy_unforgeable_window_property(scope, global)?;
|
|
define_legacy_unforgeable_document_property(scope, global)?;
|
|
// Blink exposes `self`, `parent`, and `frames` as [Replaceable], while
|
|
// `top` stays [LegacyUnforgeable]. We mirror that split so top-level
|
|
// classic-script declarations like `var parent = ...` work, without letting
|
|
// DOM named items shadow the builtins.
|
|
WindowLegacyAliasAccessorsDeclaration::default().initialize(scope, global)?;
|
|
let intrinsic_eval = v8::Script::compile(scope, v8str(scope, "eval"), None)
|
|
.and_then(|script| script.run(scope))
|
|
.ok_or_else(|| anyhow!("failed to resolve intrinsic eval"))?;
|
|
set_private_value(scope, global, WINDOW_INTRINSIC_EVAL_SLOT, intrinsic_eval);
|
|
WindowEvalGlobalDeclaration::new(intrinsic_eval)
|
|
.initialize(scope, global)
|
|
.map_err(|error| anyhow!("failed to initialize Window intrinsic eval: {error}"))?;
|
|
// Window is a [Global] WebIDL interface. Its members are own properties of
|
|
// the global object, not duplicate properties on Window.prototype.
|
|
WindowComputedStyleMethodDeclaration::default()
|
|
.initialize(scope, global)
|
|
.map_err(|error| anyhow!(error.to_string()))?;
|
|
install_document_runtime_state(scope, global)?;
|
|
install_navigator_runtime_state(scope, global, secure_context_available)?;
|
|
let document_loader = runtime
|
|
.current_document_resource_loader()
|
|
.expect("Window bootstrap requires the committed Document resource authority");
|
|
bind_window_navigator_identity_seed(
|
|
scope,
|
|
global,
|
|
document_loader.request_client().browser_identity(),
|
|
)?;
|
|
super::exposed_interfaces::filter_window_exposed_interfaces(
|
|
scope,
|
|
global,
|
|
secure_context_available,
|
|
)?;
|
|
install_html_script_element_static_methods(scope, global)?;
|
|
install_trusted_types_runtime_state(scope, global)?;
|
|
install_window_crypto_runtime_state(scope, global, secure_context_available)?;
|
|
install_css_runtime_state(scope, global)?;
|
|
install_default_window_performance_seed(scope, global)?;
|
|
install_storage_runtime_state(scope, global)?;
|
|
network_host::initialize_fetch_realm_helpers(scope)?;
|
|
install_public_window_surface_accessors(scope, global)?;
|
|
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn set_window_origin_runtime_state(
|
|
scope: &mut v8::PinScope<'_, '_>,
|
|
window: v8::Local<'_, v8::Object>,
|
|
origin: &str,
|
|
) -> Result<()> {
|
|
let origin_value =
|
|
v8_string(scope, origin).ok_or_else(|| anyhow!("failed to allocate Window.origin"))?;
|
|
set_private_value(
|
|
scope,
|
|
window,
|
|
WINDOW_ORIGIN_RUNTIME_SLOT,
|
|
origin_value.into(),
|
|
);
|
|
Ok(())
|
|
}
|
|
|
|
pub(crate) fn install_webassembly_runtime_state<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
global: v8::Local<'s, v8::Object>,
|
|
) -> Result<()> {
|
|
let Some(webassembly) = global
|
|
.get(scope, v8str(scope, "WebAssembly").into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
else {
|
|
return Ok(());
|
|
};
|
|
for (name, slot) in [
|
|
("Instance", ORIGINAL_WEBASSEMBLY_INSTANCE_CONSTRUCTOR_SLOT),
|
|
(
|
|
"CompileError",
|
|
ORIGINAL_WEBASSEMBLY_COMPILE_ERROR_CONSTRUCTOR_SLOT,
|
|
),
|
|
(
|
|
"LinkError",
|
|
ORIGINAL_WEBASSEMBLY_LINK_ERROR_CONSTRUCTOR_SLOT,
|
|
),
|
|
] {
|
|
let Some(constructor) = webassembly
|
|
.get(scope, v8str(scope, name).into())
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
|
else {
|
|
continue;
|
|
};
|
|
set_private_value(scope, global, slot, constructor.into());
|
|
}
|
|
store_webassembly_global_value_getter(scope, global, webassembly);
|
|
install_webassembly_runtime_extensions(scope)?;
|
|
install_webassembly_namespace_instance(scope, webassembly);
|
|
super::webassembly_runtime::capture_webassembly_default_prototypes(scope, global, webassembly);
|
|
Ok(())
|
|
}
|
|
|
|
fn store_webassembly_global_value_getter<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
global: v8::Local<'s, v8::Object>,
|
|
webassembly: v8::Local<'s, v8::Object>,
|
|
) {
|
|
let Some(global_constructor) = webassembly
|
|
.get(scope, v8str(scope, "Global").into())
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
|
else {
|
|
return;
|
|
};
|
|
let Some(global_prototype) = global_constructor
|
|
.get(scope, v8str(scope, "prototype").into())
|
|
.and_then(|value| v8::Local::<v8::Object>::try_from(value).ok())
|
|
else {
|
|
return;
|
|
};
|
|
let Some(descriptor) =
|
|
global_prototype.get_own_property_descriptor(scope, v8str(scope, "value").into())
|
|
else {
|
|
return;
|
|
};
|
|
let Ok(descriptor) = v8::Local::<v8::Object>::try_from(descriptor) else {
|
|
return;
|
|
};
|
|
let Some(getter) = descriptor
|
|
.get(scope, v8str(scope, "get").into())
|
|
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
|
else {
|
|
return;
|
|
};
|
|
set_private_value(
|
|
scope,
|
|
global,
|
|
ORIGINAL_WEBASSEMBLY_GLOBAL_VALUE_GETTER_SLOT,
|
|
getter.into(),
|
|
);
|
|
}
|
|
|
|
fn install_webassembly_namespace_instance<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
webassembly: v8::Local<'s, v8::Object>,
|
|
) {
|
|
let key = v8str(scope, "namespaceInstance");
|
|
if webassembly
|
|
.get(scope, key.into())
|
|
.is_some_and(|value| value.is_function())
|
|
{
|
|
return;
|
|
}
|
|
let _ = WebAssemblyNamespaceDeclaration::default().initialize(scope, webassembly);
|
|
}
|
|
|
|
fn webassembly_namespace_instance_callback<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
mut rv: v8::ReturnValue<'s, v8::Value>,
|
|
) {
|
|
let Ok(namespace) = v8::Local::<v8::Object>::try_from(args.get(0)) else {
|
|
throw_type_error(scope, "WebAssembly namespace object expected.");
|
|
return;
|
|
};
|
|
|
|
if let Some(host_ptr) = context_host_ptr_from_global_bridge(scope)
|
|
&& let Some(instance) =
|
|
(unsafe { &*host_ptr }).native_wasm_instance_for_namespace(scope, namespace)
|
|
{
|
|
rv.set(instance.into());
|
|
return;
|
|
}
|
|
|
|
if let Some(instance) = crate::worker::worker_wasm_instance_for_namespace(scope, namespace) {
|
|
rv.set(instance.into());
|
|
return;
|
|
}
|
|
|
|
throw_type_error(scope, "WebAssembly namespace object expected.");
|
|
}
|
|
|
|
fn install_html_script_element_static_methods(
|
|
scope: &mut v8::PinScope<'_, '_>,
|
|
_global: v8::Local<'_, v8::Object>,
|
|
) -> Result<()> {
|
|
let Some(ctor) = global_constructor_object(scope, "HTMLScriptElement") else {
|
|
return Ok(());
|
|
};
|
|
HtmlScriptElementStaticMethodsDeclaration::default()
|
|
.initialize(scope, ctor)
|
|
.map_err(|err| anyhow!("failed to install HTMLScriptElement static methods: {err}"))?;
|
|
Ok(())
|
|
}
|
|
|
|
fn html_script_element_supports_callback<'s>(
|
|
scope: &mut v8::PinScope<'s, '_>,
|
|
args: v8::FunctionCallbackArguments<'s>,
|
|
mut rv: v8::ReturnValue<'_, v8::Value>,
|
|
) {
|
|
let Some(parsed) = webidl::parse_args::<HtmlScriptElementSupportsArgs>(scope, &args) else {
|
|
return;
|
|
};
|
|
rv.set(
|
|
v8::Boolean::new(
|
|
scope,
|
|
html_script_element_supports_type(&parsed.script_type),
|
|
)
|
|
.into(),
|
|
);
|
|
}
|