diff --git a/moli-benchmark/wpt-cross-current/failed-cases.txt b/moli-benchmark/wpt-cross-current/failed-cases.txt index ec9014ba07..c5d404f833 100644 --- a/moli-benchmark/wpt-cross-current/failed-cases.txt +++ b/moli-benchmark/wpt-cross-current/failed-cases.txt @@ -2733,6 +2733,7 @@ html/browsers/history/the-location-interface/assign_before_load.html html/browsers/history/the-location-interface/location_hash.html html/browsers/history/the-location-interface/reload_document_write_onload.html html/browsers/history/the-location-interface/same-hash.html +html/browsers/history/the-location-interface/scripted_click_assign_during_load.html html/browsers/origin/cross-origin-objects/window-location-and-location-href-cross-realm-set.html html/browsers/origin/relaxing-the-same-origin-restriction/document_domain_setter.html html/browsers/the-window-object/accessing-other-browsing-contexts/indexed-browsing-contexts-02.html diff --git a/moli-benchmark/wpt-cross-current/passed-cases.txt b/moli-benchmark/wpt-cross-current/passed-cases.txt index c29d5db96f..cd8e0ab346 100644 --- a/moli-benchmark/wpt-cross-current/passed-cases.txt +++ b/moli-benchmark/wpt-cross-current/passed-cases.txt @@ -6441,6 +6441,7 @@ html/browsers/history/the-location-interface/location_search.html html/browsers/history/the-location-interface/no-browsing-context.window.js?moli-wpt-script=window html/browsers/history/the-location-interface/reload_document_write.html html/browsers/history/the-location-interface/replace-with-nested-iframe.html +html/browsers/history/the-location-interface/scripted_click_location_assign_during_load.html html/browsers/history/the-location-interface/security_location_0.htm html/browsers/origin/cross-origin-objects/cross-origin-due-to-document-domain-only.html html/browsers/origin/cross-origin-objects/cross-origin-objects-function-caching.html @@ -8216,6 +8217,7 @@ html/webappapis/scripting/event-loops/update-the-rendering-resize-autofocus.html html/webappapis/scripting/events/body-onload.html html/webappapis/scripting/events/compile-event-handler-lexical-scopes-form-owner.html html/webappapis/scripting/events/compile-event-handler-lexical-scopes.html +html/webappapis/scripting/events/compile-event-handler-settings-objects.html html/webappapis/scripting/events/compile-event-handler-symbol-unscopables.html html/webappapis/scripting/events/event-handler-all-global-events.html html/webappapis/scripting/events/event-handler-attributes-body-alt.html diff --git a/moli-benchmark/wpt-cross-current/timeout-cases.txt b/moli-benchmark/wpt-cross-current/timeout-cases.txt index 6f9518d9c0..fe46ee1e07 100644 --- a/moli-benchmark/wpt-cross-current/timeout-cases.txt +++ b/moli-benchmark/wpt-cross-current/timeout-cases.txt @@ -91,8 +91,6 @@ html/browsers/history/the-history-interface/traverse_the_history_write_onload_1. html/browsers/history/the-history-interface/traverse_the_history_write_onload_2.html html/browsers/history/the-location-interface/location_replace_session_history.html html/browsers/history/the-location-interface/reload_document_open_write.html -html/browsers/history/the-location-interface/scripted_click_assign_during_load.html -html/browsers/history/the-location-interface/scripted_click_location_assign_during_load.html html/browsers/history/the-session-history-of-browsing-contexts/navigation-in-onload.html html/browsers/sandboxing/sandbox-document-open.html html/browsers/sandboxing/sandbox-window-open-srcdoc.html @@ -153,7 +151,6 @@ html/semantics/scripting-1/the-script-element/execution-timing/102.html html/semantics/scripting-1/the-script-element/microtasks/checkpoint-after-workerglobalscope-onerror-module.html html/semantics/text-level-semantics/the-a-element/a-type.historical.html html/webappapis/scripting/event-loops/new-scroll-event-dispatched-at-next-updating-rendering-time.html -html/webappapis/scripting/events/compile-event-handler-settings-objects.html html/webappapis/scripting/events/onerroreventhandler.html html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/promise-rejection-events.serviceworker.https.html import-maps/data-driven/resolving.html?data-url-prefix.json diff --git a/moli-renderer-v8/src/context_bootstrap/window_events/error.rs b/moli-renderer-v8/src/context_bootstrap/window_events/error.rs index 5cb86fa947..9801d6d9ba 100644 --- a/moli-renderer-v8/src/context_bootstrap/window_events/error.rs +++ b/moli-renderer-v8/src/context_bootstrap/window_events/error.rs @@ -136,7 +136,10 @@ pub(crate) fn dispatch_window_error_event_with_details<'s>( }; let global = scope.get_current_context().global(scope); - ensure_window_reflecting_body_onerror_handler(scope); + let popup_id = crate::native_bridge::active_lightweight_popup_id(scope); + if popup_id.is_none() { + ensure_window_reflecting_body_onerror_handler(scope); + } let error_value = error_value.unwrap_or_else(|| v8::null(scope).into()); let message = v8_string(scope, message) @@ -173,6 +176,10 @@ pub(crate) fn dispatch_window_error_event_with_details<'s>( mark_event_trusted(scope, event); let runtime = unsafe { &mut *host_ptr }; + if let Some(popup_id) = popup_id { + runtime.dispatch_lightweight_popup_window_event(scope, popup_id, "error", event); + return Ok(()); + } if let Some(child_handle) = crate::context_bootstrap::child_browsing_context_handle_for_current_realm_scope(scope) { diff --git a/moli-renderer-v8/src/native_bridge/element/event_handlers/generic.rs b/moli-renderer-v8/src/native_bridge/element/event_handlers/generic.rs index 3a4600a064..ecf33838c2 100644 --- a/moli-renderer-v8/src/native_bridge/element/event_handlers/generic.rs +++ b/moli-renderer-v8/src/native_bridge/element/event_handlers/generic.rs @@ -1,6 +1,7 @@ use crate::{ context_bootstrap::WINDOW_EVENT_HANDLER_PROPERTIES, document_runtime::EventTargetHandle, + native_bridge::OwnerDispatchScope, util::{ context_host_ptr_from_global_bridge, node_wrapper_from_handle, throw_type_error, v8_string, v8str, @@ -409,7 +410,9 @@ fn node_event_handler_getter_function<'s>( rv.set(v8::null(scope).into()); return; }; - let Some(target_context) = node_event_handler_target_context(scope, runtime_ptr, handle) else { + let Some((target_context, dispatch_scope)) = + node_event_handler_target_context(scope, runtime_ptr, handle) + else { rv.set(v8::null(scope).into()); return; }; @@ -427,29 +430,23 @@ fn node_event_handler_getter_function<'s>( return; } - let handler = if target_context == scope.get_current_context() { - compile_node_event_attribute_handler( - scope, - runtime_ptr, - handle, - object, - &handler_name, - &source, - ) - .map(|handler| v8::Global::new(scope, handler)) - } else { + let handler = { let object = v8::Global::new(scope, object); let target_scope = &mut v8::ContextScope::new(scope, target_context); + let previous = dispatch_scope.enter(target_scope); let object = v8::Local::new(target_scope, &object); - compile_node_event_attribute_handler( + let handler = compile_node_event_attribute_handler( target_scope, runtime_ptr, + dispatch_scope, handle, object, &handler_name, &source, ) - .map(|handler| v8::Global::new(target_scope, handler)) + .map(|handler| v8::Global::new(target_scope, handler)); + dispatch_scope.restore(target_scope, previous); + handler }; match handler { Some(handler) => rv.set(v8::Local::new(scope, &handler).into()), @@ -461,25 +458,24 @@ fn node_event_handler_target_context<'s>( scope: &mut v8::PinScope<'s, '_>, runtime_ptr: *mut crate::native_bridge::JsContextHost, handle: crate::document_runtime::DomHandle, -) -> Option> { - let runtime = unsafe { &*runtime_ptr }; - let owner_document = runtime.dom_host().owner_document_handle(handle)?; - let dispatch_scope = if owner_document == runtime.dom_host().document_handle() { - crate::native_bridge::OwnerDispatchScope::Top - } else { - crate::native_bridge::OwnerDispatchScope::Child( - runtime.child_browsing_context_host_for_document_handle(owner_document)?, - ) - }; +) -> Option<(v8::Local<'s, v8::Context>, OwnerDispatchScope)> { + let runtime = unsafe { &mut *runtime_ptr }; + let dispatch_scope = runtime.owner_dispatch_scope_for_node(handle)?; + if let OwnerDispatchScope::LightweightPopup(popup_id) = dispatch_scope + && !runtime.ensure_lightweight_popup_execution_context(scope, popup_id) + { + return None; + } let owner = runtime.current_window_execution_context_owner(dispatch_scope)?; runtime .window_execution_context(scope, owner, dispatch_scope) - .map(|(_, context)| context) + .map(|(_, context)| (context, dispatch_scope)) } fn compile_node_event_attribute_handler<'s>( scope: &mut v8::PinScope<'s, '_>, runtime_ptr: *mut crate::native_bridge::JsContextHost, + dispatch_scope: OwnerDispatchScope, handle: crate::document_runtime::DomHandle, object: v8::Local<'s, v8::Object>, handler_name: &str, @@ -498,9 +494,20 @@ fn compile_node_event_attribute_handler<'s>( target_context, ); let event_argument = v8_string(scope, "event")?; - let global = scope.get_current_context().global(scope); - let mut context_extensions = Vec::with_capacity(3); - if let Some(document) = global + let mut context_extensions = Vec::with_capacity(4); + let window = match dispatch_scope { + OwnerDispatchScope::LightweightPopup(popup_id) => { + let window = unsafe { &*runtime_ptr }.lightweight_popup_window(scope, popup_id)?; + // Popup Windows currently share a concrete V8 realm with their + // opener. Their own global bindings must precede that realm's globals. + context_extensions.push(window); + window + } + OwnerDispatchScope::Top | OwnerDispatchScope::Child(_) => { + scope.get_current_context().global(scope) + } + }; + if let Some(document) = window .get(scope, v8str(scope, "document").into()) .and_then(|value| v8::Local::::try_from(value).ok()) { diff --git a/moli-renderer-v8/src/script_vm/tests/browser_api/event_handlers.rs b/moli-renderer-v8/src/script_vm/tests/browser_api/event_handlers.rs index fc72e7e67b..821819c9cd 100644 --- a/moli-renderer-v8/src/script_vm/tests/browser_api/event_handlers.rs +++ b/moli-renderer-v8/src/script_vm/tests/browser_api/event_handlers.rs @@ -3,6 +3,7 @@ use super::*; mod child_window; mod compilation; mod object_values; +mod popup; #[test] fn event_attribute_handlers_use_html_scope_chain_and_report_compile_errors() { diff --git a/moli-renderer-v8/src/script_vm/tests/browser_api/event_handlers/popup.rs b/moli-renderer-v8/src/script_vm/tests/browser_api/event_handlers/popup.rs new file mode 100644 index 0000000000..3527709283 --- /dev/null +++ b/moli-renderer-v8/src/script_vm/tests/browser_api/event_handlers/popup.rs @@ -0,0 +1,149 @@ +use super::*; + +#[test] +fn popup_content_handlers_use_their_window_document_form_and_element_scopes() { + let mut vm = new_storage_test_vm("https://popup-content-handler.test/"); + let result = vm + .eval( + r#" +(() => { + const popup = open(); + try { + const d = popup.document; + d.body.innerHTML = '
'; + const button = d.getElementById('button'); + const form = button.form; + const trace = popup.__handlerTrace = []; + popup.scopeToken = 'window'; + d.scopeToken = 'document'; + form.scopeToken = 'form'; + button.scopeToken = 'element'; + button.setAttribute('onclick', ` + globalThis.__handlerTrace.push([globalThis === window, window !== opener, + document === ownerDocument, form === document.forms[0], + this === document.getElementById('button'), scopeToken, + event.type, arguments.length]); + location.hash = 'handled'; + return false; + `); + const handler = button.onclick; + const canceled = []; + for (const object of [button, form, d, popup]) { + canceled.push(!button.dispatchEvent(new Event('click', {cancelable:true}))); + delete object.scopeToken; + } + return JSON.stringify({type:typeof handler, cached:handler === button.onclick, + trace, canceled, popupHash:popup.location.hash, openerHash:location.hash, + openerUntouched:!Object.hasOwn(window, '__handlerTrace')}); + } finally { + popup.close(); + } +})() +"#, + ) + .expect("popup content handler scope probe should evaluate"); + assert_eq!( + serde_json::from_str::(&result).unwrap(), + serde_json::json!({ + "type": "function", + "cached": true, + "trace": [ + [true, true, true, true, true, "element", "click", 1], + [true, true, true, true, true, "form", "click", 1], + [true, true, true, true, true, "document", "click", 1], + [true, true, true, true, true, "window", "click", 1] + ], + "canceled": [true, true, true, true], + "popupHash": "#handled", + "openerHash": "", + "openerUntouched": true + }) + ); +} + +#[test] +fn popup_content_handler_compile_errors_report_to_the_popup_and_preserve_reentry() { + let mut vm = new_storage_test_vm("https://popup-content-handler-error.test/"); + let result = vm + .eval( + r#" +(() => { + const popup = open(); + try { + const button = popup.document.createElement('button'); + popup.document.body.appendChild(button); + const trace = []; + let openerErrors = 0; + window.addEventListener('error', () => ++openerErrors); + const replacement = () => trace.push('replacement'); + popup.addEventListener('error', event => { + trace.push([event.target === popup, event.error.name === 'SyntaxError', + button.onclick === null]); + button.onclick = replacement; + event.preventDefault(); + }); + button.setAttribute('onclick', '}'); + const before = trace.length; + const firstNull = button.onclick === null; + const replacementPreserved = button.onclick === replacement; + button.click(); + return JSON.stringify({before, firstNull, replacementPreserved, trace, openerErrors}); + } finally { + popup.close(); + } +})() +"#, + ) + .expect("popup content handler syntax error probe should evaluate"); + assert_eq!( + serde_json::from_str::(&result).unwrap(), + serde_json::json!({ + "before": 0, + "firstNull": true, + "replacementPreserved": true, + "trace": [[true, true, true], "replacement"], + "openerErrors": 0 + }) + ); +} + +#[tokio::test] +async fn popup_content_handler_timers_retire_with_the_popup() { + let loader = ResourceRequestClient::new(&moli_fetch::FetchConfig::default()).expect("loader"); + let mut vm = new_page_task_executor_test_vm_with_loader( + "https://popup-content-handler-timer.test/", + &loader, + ); + assert_eq!( + vm.eval( + r#" +(() => { + globalThis.__popupContentTimerCalls = 0; + globalThis.__openerContentTimerDone = false; + const popup = open(); + const button = popup.document.createElement('button'); + popup.document.body.appendChild(button); + button.setAttribute('onclick', ` + setTimeout(() => ++opener.__popupContentTimerCalls, 50); + `); + const compiled = typeof button.onclick; + button.click(); + popup.close(); + setTimeout(() => { __openerContentTimerDone = true; }, 100); + return compiled; +})() +"#, + ) + .expect("popup content handler timer probe should evaluate"), + "function" + ); + advance_page_task_executor_until_eval_equals( + &mut vm, + &loader, + "String(__openerContentTimerDone)", + "true", + "opener timer after popup close", + ) + .await; + assert_eq!(vm.eval("String(__popupContentTimerCalls)").unwrap(), "0"); +}