mirror of
https://github.com/lexmount/moli.git
synced 2026-10-04 08:00:45 +00:00
fix(events): construct trusted history event subclasses
This commit is contained in:
@@ -186,6 +186,9 @@ pub(in crate::context_bootstrap) fn build_constructor_template_for_profile<'s>(
|
||||
ConstructorKind::PopStateEvent => {
|
||||
build_event_subclass_template(scope, EventSubclassKind::PopStateEvent)
|
||||
}
|
||||
ConstructorKind::HashChangeEvent => {
|
||||
build_event_subclass_template(scope, EventSubclassKind::HashChangeEvent)
|
||||
}
|
||||
ConstructorKind::PageTransitionEvent => {
|
||||
build_event_subclass_template(scope, EventSubclassKind::PageTransitionEvent)
|
||||
}
|
||||
|
||||
@@ -31,6 +31,7 @@ pub(in crate::context_bootstrap) enum EventSubclassKind {
|
||||
ClipboardEvent = 29,
|
||||
CapturedMouseEvent = 30,
|
||||
FontFaceSetLoadEvent = 31,
|
||||
HashChangeEvent = 32,
|
||||
}
|
||||
|
||||
impl EventSubclassKind {
|
||||
@@ -67,6 +68,7 @@ impl EventSubclassKind {
|
||||
29 => Some(Self::ClipboardEvent),
|
||||
30 => Some(Self::CapturedMouseEvent),
|
||||
31 => Some(Self::FontFaceSetLoadEvent),
|
||||
32 => Some(Self::HashChangeEvent),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -104,6 +106,7 @@ impl EventSubclassKind {
|
||||
Self::ClipboardEvent => "ClipboardEvent",
|
||||
Self::CapturedMouseEvent => "CapturedMouseEvent",
|
||||
Self::FontFaceSetLoadEvent => "FontFaceSetLoadEvent",
|
||||
Self::HashChangeEvent => "HashChangeEvent",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -163,6 +163,11 @@ fn event_subclass_constructor_callback<'s>(
|
||||
return;
|
||||
}
|
||||
}
|
||||
EventSubclassKind::HashChangeEvent => {
|
||||
if !data::initialize_hash_change_event(scope, event, init) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
EventSubclassKind::PageTransitionEvent => {
|
||||
data::initialize_page_transition_event(scope, event, init);
|
||||
}
|
||||
|
||||
@@ -195,6 +195,25 @@ struct PopStateEventInitMembers<'s> {
|
||||
state: Option<v8::Local<'s, v8::Value>>,
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(interface = "Object", data_properties, enumerable)]
|
||||
struct HashChangeEventInitDeclaration {
|
||||
#[webapi(data_property = "oldURL")]
|
||||
old_url: String,
|
||||
#[webapi(data_property = "newURL")]
|
||||
new_url: String,
|
||||
}
|
||||
|
||||
/// Members are declared in Web IDL lexicographic order.
|
||||
#[derive(Default, webidl::WebIdlDictionary)]
|
||||
#[webidl(prefix = "HashChangeEventInit")]
|
||||
struct HashChangeEventInitMembers {
|
||||
#[webidl(name = "newURL", default = "", converter = "usv_string")]
|
||||
new_url: String,
|
||||
#[webidl(name = "oldURL", default = "", converter = "usv_string")]
|
||||
old_url: String,
|
||||
}
|
||||
|
||||
#[derive(WebApiObject)]
|
||||
#[webapi(interface = "Object", data_properties, enumerable)]
|
||||
struct PageTransitionEventOwnInitDeclaration {
|
||||
@@ -535,6 +554,29 @@ pub(in crate::context_bootstrap::events::subclasses) fn initialize_pop_state_eve
|
||||
true
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap::events::subclasses) fn initialize_hash_change_event<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
event: v8::Local<'s, v8::Object>,
|
||||
init: Option<v8::Local<'s, v8::Object>>,
|
||||
) -> bool {
|
||||
let parsed = match init {
|
||||
Some(init) => {
|
||||
match webidl::parse_dictionary_object::<HashChangeEventInitMembers>(scope, init) {
|
||||
Ok(parsed) => parsed,
|
||||
Err(error) => {
|
||||
webidl::throw_error(scope, &error);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
None => HashChangeEventInitMembers::default(),
|
||||
};
|
||||
HashChangeEventInitDeclaration::new(parsed.old_url, parsed.new_url)
|
||||
.initialize(scope, event)
|
||||
.expect("HashChangeEvent init declaration should initialize");
|
||||
true
|
||||
}
|
||||
|
||||
pub(in crate::context_bootstrap::events::subclasses) fn initialize_page_transition_event<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
event: v8::Local<'s, v8::Object>,
|
||||
|
||||
@@ -573,17 +573,20 @@ pub(super) fn dispatch_popstate_event<'s>(
|
||||
child_handle: Option<crate::document_runtime::DomHandle>,
|
||||
state: v8::Local<'s, v8::Value>,
|
||||
) {
|
||||
let global = scope.get_current_context().global(scope);
|
||||
let Some(event_ctor) = global
|
||||
.get(scope, v8str(scope, "Event").into())
|
||||
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())
|
||||
let Ok(event_ctor) =
|
||||
super::exposed_interfaces::ensure_intrinsic_interface_constructor(scope, "PopStateEvent")
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let Some(event) = event_ctor.new_instance(scope, &[v8str(scope, "popstate").into()]) else {
|
||||
let init = PopStateEventStateDeclaration::new(state)
|
||||
.bind(scope)
|
||||
.expect("PopStateEvent init declaration should bind");
|
||||
let Some(event) =
|
||||
event_ctor.new_instance(scope, &[v8str(scope, "popstate").into(), init.into()])
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let _ = PopStateEventStateDeclaration::new(state).initialize(scope, event);
|
||||
mark_event_trusted(scope, event);
|
||||
let runtime = unsafe { &mut *host_ptr };
|
||||
if let Some(child_handle) = child_handle {
|
||||
runtime.dispatch_child_window_event(scope, child_handle, "popstate", event);
|
||||
@@ -603,13 +606,15 @@ pub(crate) fn construct_original_hash_change_event<'s>(
|
||||
old_url: &str,
|
||||
new_url: &str,
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let global = scope.get_current_context().global(scope);
|
||||
let event_ctor = global
|
||||
.get(scope, v8str(scope, "Event").into())
|
||||
.and_then(|value| v8::Local::<v8::Function>::try_from(value).ok())?;
|
||||
let event = event_ctor.new_instance(scope, &[v8str(scope, "hashchange").into()])?;
|
||||
let _ = HashChangeEventStateDeclaration::new(old_url.to_owned(), new_url.to_owned())
|
||||
.initialize(scope, event);
|
||||
let event_ctor =
|
||||
super::exposed_interfaces::ensure_intrinsic_interface_constructor(scope, "HashChangeEvent")
|
||||
.ok()?;
|
||||
let init = HashChangeEventStateDeclaration::new(old_url.to_owned(), new_url.to_owned())
|
||||
.bind(scope)
|
||||
.expect("HashChangeEvent init declaration should bind");
|
||||
let event =
|
||||
event_ctor.new_instance(scope, &[v8str(scope, "hashchange").into(), init.into()])?;
|
||||
mark_event_trusted(scope, event);
|
||||
Some(event)
|
||||
}
|
||||
|
||||
|
||||
@@ -421,7 +421,7 @@ const CONSTRUCTOR_SPECS_BEFORE_STREAMS: &[ConstructorSpec] = &[
|
||||
ConstructorSpec {
|
||||
name: "HashChangeEvent",
|
||||
parent: Some("Event"),
|
||||
kind: ConstructorKind::Illegal,
|
||||
kind: ConstructorKind::HashChangeEvent,
|
||||
},
|
||||
ConstructorSpec {
|
||||
name: "MediaQueryListEvent",
|
||||
|
||||
@@ -31,6 +31,7 @@ pub(in crate::context_bootstrap) enum ConstructorKind {
|
||||
ToggleEvent,
|
||||
InterestEvent,
|
||||
PopStateEvent,
|
||||
HashChangeEvent,
|
||||
PageTransitionEvent,
|
||||
DomException,
|
||||
DomError,
|
||||
|
||||
@@ -15,6 +15,52 @@ fn take_next_hash_change_task_for_authorization_test(
|
||||
Some(task)
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn user_agent_hashchange_is_trusted_but_author_constructed_event_is_not() {
|
||||
run_page_vm_async_test(async move {
|
||||
let loader =
|
||||
crate::network::ResourceRequestClient::new(&FetchConfig::default()).expect("loader");
|
||||
let document_url = Url::parse("https://example.com/hashchange-event-trust").unwrap();
|
||||
let (mut page_vm, _resource_source, _owner_wake_rx) =
|
||||
page_vm_with_bound_task_sources_and_owner_wake(&loader, document_url);
|
||||
page_vm.vm_mut().eval(
|
||||
r##"
|
||||
globalThis.__authorHashChange = new HashChangeEvent("author", {
|
||||
oldURL: "https://old.example/",
|
||||
newURL: "https://new.example/"
|
||||
});
|
||||
globalThis.__hashChangeTrust = null;
|
||||
addEventListener("hashchange", event => {
|
||||
__hashChangeTrust = [event.isTrusted, event instanceof HashChangeEvent];
|
||||
}, { once: true });
|
||||
location.hash = "#trusted";
|
||||
"queued"
|
||||
"##,
|
||||
)?;
|
||||
|
||||
assert!(
|
||||
page_vm
|
||||
.run_exact_selected_page_task_for_test(
|
||||
PageSelectedTaskTestSelector::DomManipulation(
|
||||
PageDomManipulationTestFamily::HashChange
|
||||
),
|
||||
&loader,
|
||||
)
|
||||
.await?,
|
||||
"one exact hashchange task should dispatch"
|
||||
);
|
||||
assert_eq!(
|
||||
page_vm
|
||||
.vm_mut()
|
||||
.eval("JSON.stringify([...__hashChangeTrust, __authorHashChange instanceof HashChangeEvent, __authorHashChange.isTrusted, __authorHashChange.oldURL, __authorHashChange.newURL])")?,
|
||||
r#"[true,true,true,false,"https://old.example/","https://new.example/"]"#
|
||||
);
|
||||
Ok::<_, anyhow::Error>(())
|
||||
})
|
||||
.await
|
||||
.expect("hashchange trust test should run");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn hashchange_body_leaves_reactions_for_selected_callback_completion() {
|
||||
run_page_vm_async_test(async move {
|
||||
|
||||
@@ -5,6 +5,49 @@ use crate::{
|
||||
runtime::{IntoPageTaskCompletion, PageTaskCompletion},
|
||||
};
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn user_agent_popstate_is_trusted_but_author_constructed_event_is_not() {
|
||||
run_page_vm_async_test(async move {
|
||||
let loader =
|
||||
crate::network::ResourceRequestClient::new(&FetchConfig::default()).expect("loader");
|
||||
let document_url = Url::parse("https://example.com/popstate-event-trust").unwrap();
|
||||
let (mut page_vm, _resource_source, _owner_wake_rx) =
|
||||
page_vm_with_bound_task_sources_and_owner_wake(&loader, document_url);
|
||||
page_vm.vm_mut().eval(
|
||||
r##"
|
||||
history.pushState(null, "", "#one");
|
||||
globalThis.__popStateTrust = null;
|
||||
addEventListener("popstate", event => {
|
||||
__popStateTrust = JSON.stringify([
|
||||
event.isTrusted,
|
||||
event instanceof PopStateEvent,
|
||||
new PopStateEvent("popstate").isTrusted
|
||||
]);
|
||||
}, { once: true });
|
||||
history.back();
|
||||
"queued"
|
||||
"##,
|
||||
)?;
|
||||
|
||||
assert!(
|
||||
page_vm
|
||||
.run_exact_selected_page_task_for_test(
|
||||
PageSelectedTaskTestSelector::HistoryTraversal,
|
||||
&loader
|
||||
)
|
||||
.await?,
|
||||
"one exact history traversal should dispatch popstate"
|
||||
);
|
||||
assert_eq!(
|
||||
page_vm.vm_mut().eval("__popStateTrust")?,
|
||||
"[true,true,false]"
|
||||
);
|
||||
Ok::<_, anyhow::Error>(())
|
||||
})
|
||||
.await
|
||||
.expect("popstate trust test should run");
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
async fn history_traversal_body_leaves_reaction_for_selected_completion() {
|
||||
run_page_vm_async_test(async move {
|
||||
|
||||
Reference in New Issue
Block a user