mirror of
https://github.com/lexmount/moli.git
synced 2026-10-08 00:01:00 +00:00
fix(window): defer lightweight popup teardown
This commit is contained in:
@@ -261,6 +261,11 @@ impl CallbackInvoker {
|
||||
invocation.relevant_context,
|
||||
invocation.incumbent_context,
|
||||
|scope| {
|
||||
let relevant_dispatch_scope = invocation
|
||||
.relevant_identity
|
||||
.map(WindowExecutionContextIdentity::dispatch_scope);
|
||||
let previous_relevant_dispatch_scope =
|
||||
relevant_dispatch_scope.map(|dispatch_scope| dispatch_scope.enter(scope));
|
||||
let relevant_context = invocation.relevant_context;
|
||||
let previous_window_event =
|
||||
invocation
|
||||
@@ -329,6 +334,11 @@ impl CallbackInvoker {
|
||||
let _ = global.set(scope, v8str(scope, WINDOW_EVENT_SLOT).into(), previous);
|
||||
}
|
||||
}
|
||||
if let (Some(dispatch_scope), Some(previous_dispatch_scope)) =
|
||||
(relevant_dispatch_scope, previous_relevant_dispatch_scope)
|
||||
{
|
||||
dispatch_scope.restore(scope, previous_dispatch_scope);
|
||||
}
|
||||
completed
|
||||
},
|
||||
)
|
||||
|
||||
+30
-3
@@ -9,6 +9,7 @@ use crate::{
|
||||
context_bootstrap::{EventHandlerType, apply_event_handler_return_value},
|
||||
exception_reporting::CallbackExceptionLogLevel,
|
||||
host::report_event_callback_exception,
|
||||
native_bridge::lightweight_popup_id_from_window,
|
||||
util::{context_host_ptr_from_global_bridge, serialize_v8_array},
|
||||
};
|
||||
|
||||
@@ -174,12 +175,34 @@ pub(crate) fn invoke_simple_event_listener<'s>(
|
||||
current_event: v8::Local<'s, v8::Object>,
|
||||
) -> Option<v8::Global<v8::Value>> {
|
||||
let invocation = listener.invocation(callback_this, arguments, Some(current_event));
|
||||
// Lightweight popup Window shells alias the opener's concrete V8 realm.
|
||||
// Retain the callback's exact registration-time Window only for popup
|
||||
// `load`: this is where a top-realm WPT callback must keep scheduling work
|
||||
// on the opener after it calls `popup.close()`. Other synthetic popup
|
||||
// events deliberately execute in their target owner scope until those
|
||||
// Window shells gain distinct V8 realms.
|
||||
let captured_relevant_identity = if event_type == "load"
|
||||
&& v8::Local::<v8::Object>::try_from(callback_this)
|
||||
.ok()
|
||||
.and_then(|target| lightweight_popup_id_from_window(scope, target))
|
||||
.is_some()
|
||||
{
|
||||
listener.relevant_identity().filter(|identity| {
|
||||
!matches!(
|
||||
identity.dispatch_scope(),
|
||||
crate::native_bridge::OwnerDispatchScope::LightweightPopup(_)
|
||||
)
|
||||
})
|
||||
} else {
|
||||
None
|
||||
};
|
||||
invoke_simple_event_callback_with_invocation(
|
||||
scope,
|
||||
event_type,
|
||||
callback_name,
|
||||
callback_this,
|
||||
listener.relevant_context(),
|
||||
captured_relevant_identity,
|
||||
invocation,
|
||||
)
|
||||
}
|
||||
@@ -213,6 +236,7 @@ fn invoke_simple_event_callback<'s>(
|
||||
callback_name,
|
||||
callback_this,
|
||||
relevant_context,
|
||||
None,
|
||||
invocation,
|
||||
)
|
||||
}
|
||||
@@ -239,12 +263,15 @@ fn invoke_simple_event_callback_with_invocation<'s>(
|
||||
callback_name: &str,
|
||||
callback_target: v8::Local<'s, v8::Value>,
|
||||
relevant_context: v8::Local<'s, v8::Context>,
|
||||
captured_relevant_identity: Option<crate::native_bridge::WindowExecutionContextIdentity>,
|
||||
mut invocation: CallbackInvocation<'s, '_>,
|
||||
) -> Option<v8::Global<v8::Value>> {
|
||||
let host_ptr = context_host_ptr_from_global_bridge(scope);
|
||||
let relevant_identity = host_ptr.and_then(|host_ptr| {
|
||||
unsafe { &*host_ptr }
|
||||
.window_execution_context_identity_for_v8_context(scope, relevant_context)
|
||||
let relevant_identity = captured_relevant_identity.or_else(|| {
|
||||
host_ptr.and_then(|host_ptr| {
|
||||
unsafe { &*host_ptr }
|
||||
.window_execution_context_identity_for_v8_context(scope, relevant_context)
|
||||
})
|
||||
});
|
||||
if let Some(host_ptr) = host_ptr {
|
||||
invocation = invocation.with_execution_context_currentness(host_ptr, relevant_identity);
|
||||
|
||||
+80
-11
@@ -1,8 +1,10 @@
|
||||
use super::*;
|
||||
use crate::abort_signal_route::{ResolvedAbortSignal, event_listener_signal_from_options_value};
|
||||
use crate::callback_invocation::CallbackInvocation;
|
||||
use crate::native_bridge::WindowExecutionContextIdentity;
|
||||
use crate::util::{
|
||||
get_private_object, get_private_value, new_null_prototype_object, set_private_value, v8_string,
|
||||
context_host_ptr_from_global_bridge, get_private_object, get_private_value,
|
||||
new_null_prototype_object, set_private_value, v8_string,
|
||||
};
|
||||
use crate::webidl;
|
||||
use moli_webapi_declare::WebApiObject;
|
||||
@@ -13,6 +15,8 @@ const SIMPLE_EVENT_TARGET_LISTENER_RELEVANT_CONTEXT_ANCHOR_SLOT: &str =
|
||||
"__moliSimpleEventTargetListenerRelevantContextAnchor";
|
||||
const SIMPLE_EVENT_TARGET_LISTENER_INCUMBENT_CONTEXT_ANCHOR_SLOT: &str =
|
||||
"__moliSimpleEventTargetListenerIncumbentContextAnchor";
|
||||
const SIMPLE_EVENT_TARGET_LISTENER_RELEVANT_IDENTITY_SLOT: &str =
|
||||
"__moliSimpleEventTargetListenerRelevantIdentity";
|
||||
const SIMPLE_EVENT_TARGET_LISTENER_CALLABLE_SLOT: &str = "__moliSimpleEventTargetListenerCallable";
|
||||
const SIMPLE_EVENT_TARGET_LISTENER_CAPTURE_SLOT: &str = "__moliSimpleEventTargetListenerCapture";
|
||||
const SIMPLE_EVENT_TARGET_LISTENER_ONCE_SLOT: &str = "__moliSimpleEventTargetListenerOnce";
|
||||
@@ -84,6 +88,7 @@ pub(crate) struct SimpleObjectEventListenerSnapshot<'s> {
|
||||
callback: v8::Local<'s, v8::Object>,
|
||||
relevant_context: v8::Local<'s, v8::Context>,
|
||||
incumbent_context: v8::Local<'s, v8::Context>,
|
||||
relevant_identity: Option<WindowExecutionContextIdentity>,
|
||||
is_callable: bool,
|
||||
pub(crate) capture: bool,
|
||||
pub(crate) once: bool,
|
||||
@@ -107,6 +112,7 @@ struct SimpleObjectResolvedEventListener<'s> {
|
||||
callback: v8::Local<'s, v8::Object>,
|
||||
relevant_context_anchor: v8::Local<'s, v8::Object>,
|
||||
incumbent_context_anchor: v8::Local<'s, v8::Object>,
|
||||
relevant_identity: Option<WindowExecutionContextIdentity>,
|
||||
is_callable: bool,
|
||||
}
|
||||
|
||||
@@ -133,6 +139,10 @@ impl<'s> SimpleObjectEventListenerSnapshot<'s> {
|
||||
self.relevant_context
|
||||
}
|
||||
|
||||
pub(crate) fn relevant_identity(&self) -> Option<WindowExecutionContextIdentity> {
|
||||
self.relevant_identity
|
||||
}
|
||||
|
||||
pub(crate) fn callable_function(&self) -> Option<v8::Local<'s, v8::Function>> {
|
||||
self.is_callable
|
||||
.then(|| unsafe { v8::Local::<v8::Function>::cast_unchecked(self.callback) })
|
||||
@@ -274,13 +284,14 @@ pub(crate) fn simple_object_event_target_register_webidl_listener<'s>(
|
||||
.expect("converted EventListener callback must remain an object");
|
||||
let relevant_context = listener.relevant_context(scope);
|
||||
let incumbent_context = listener.incumbent_context(scope);
|
||||
let (relevant_context_anchor, incumbent_context_anchor) =
|
||||
let (relevant_context_anchor, incumbent_context_anchor, relevant_identity) =
|
||||
simple_callback_context_anchors_for_contexts(scope, relevant_context, incumbent_context);
|
||||
let listener = SimpleObjectResolvedEventListener {
|
||||
original: callback.into(),
|
||||
callback,
|
||||
relevant_context_anchor,
|
||||
incumbent_context_anchor,
|
||||
relevant_identity,
|
||||
is_callable: listener.callable_at_conversion(),
|
||||
};
|
||||
simple_object_event_target_register_resolved_listener(
|
||||
@@ -319,6 +330,7 @@ fn simple_object_event_target_register_resolved_listener<'s>(
|
||||
listener.callback,
|
||||
listener.relevant_context_anchor,
|
||||
listener.incumbent_context_anchor,
|
||||
listener.relevant_identity,
|
||||
listener.is_callable,
|
||||
options.capture,
|
||||
options.once,
|
||||
@@ -622,7 +634,7 @@ pub(crate) fn simple_object_event_set_ordered_handler<'s>(
|
||||
else {
|
||||
return;
|
||||
};
|
||||
let (relevant_context_anchor, incumbent_context_anchor) =
|
||||
let (relevant_context_anchor, incumbent_context_anchor, relevant_identity) =
|
||||
simple_callback_context_anchors(scope, callback);
|
||||
for index in 0..listeners.length() {
|
||||
let Some(candidate) = listeners.get_index(scope, index) else {
|
||||
@@ -650,6 +662,11 @@ pub(crate) fn simple_object_event_set_ordered_handler<'s>(
|
||||
SIMPLE_EVENT_TARGET_LISTENER_INCUMBENT_CONTEXT_ANCHOR_SLOT,
|
||||
incumbent_context_anchor.into(),
|
||||
);
|
||||
set_simple_object_event_listener_relevant_identity(
|
||||
scope,
|
||||
entry,
|
||||
relevant_identity,
|
||||
);
|
||||
}
|
||||
ensure_simple_object_event_type_order(scope, target, slot_name, event_type);
|
||||
return;
|
||||
@@ -661,6 +678,7 @@ pub(crate) fn simple_object_event_set_ordered_handler<'s>(
|
||||
callback,
|
||||
relevant_context_anchor,
|
||||
incumbent_context_anchor,
|
||||
relevant_identity,
|
||||
);
|
||||
let _ = listeners.set_index(scope, listeners.length(), entry.into());
|
||||
ensure_simple_object_event_type_order(scope, target, slot_name, event_type);
|
||||
@@ -862,13 +880,14 @@ fn simple_object_event_listener_parts<'s>(
|
||||
return None;
|
||||
}
|
||||
let callback = v8::Local::<v8::Object>::try_from(value).ok()?;
|
||||
let (relevant_context_anchor, incumbent_context_anchor) =
|
||||
let (relevant_context_anchor, incumbent_context_anchor, relevant_identity) =
|
||||
simple_callback_context_anchors(scope, callback);
|
||||
Some(SimpleObjectResolvedEventListener {
|
||||
original: value,
|
||||
callback,
|
||||
relevant_context_anchor,
|
||||
incumbent_context_anchor,
|
||||
relevant_identity,
|
||||
is_callable: callback.is_callable(),
|
||||
})
|
||||
}
|
||||
@@ -876,7 +895,11 @@ fn simple_object_event_listener_parts<'s>(
|
||||
fn simple_callback_context_anchors<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
callback: v8::Local<'s, v8::Object>,
|
||||
) -> (v8::Local<'s, v8::Object>, v8::Local<'s, v8::Object>) {
|
||||
) -> (
|
||||
v8::Local<'s, v8::Object>,
|
||||
v8::Local<'s, v8::Object>,
|
||||
Option<WindowExecutionContextIdentity>,
|
||||
) {
|
||||
let current_context = scope.get_current_context();
|
||||
let relevant_context = callback
|
||||
.get_creation_context(scope)
|
||||
@@ -889,7 +912,15 @@ fn simple_callback_context_anchors_for_contexts<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
relevant_context: v8::Local<'s, v8::Context>,
|
||||
incumbent_context: v8::Local<'s, v8::Context>,
|
||||
) -> (v8::Local<'s, v8::Object>, v8::Local<'s, v8::Object>) {
|
||||
) -> (
|
||||
v8::Local<'s, v8::Object>,
|
||||
v8::Local<'s, v8::Object>,
|
||||
Option<WindowExecutionContextIdentity>,
|
||||
) {
|
||||
let relevant_identity = context_host_ptr_from_global_bridge(scope).and_then(|host_ptr| {
|
||||
unsafe { &*host_ptr }
|
||||
.window_execution_context_identity_for_v8_context(scope, relevant_context)
|
||||
});
|
||||
let relevant_context_anchor = {
|
||||
let scope = &mut v8::ContextScope::new(scope, relevant_context);
|
||||
v8::Object::new(scope)
|
||||
@@ -898,7 +929,11 @@ fn simple_callback_context_anchors_for_contexts<'s>(
|
||||
let scope = &mut v8::ContextScope::new(scope, incumbent_context);
|
||||
v8::Object::new(scope)
|
||||
};
|
||||
(relevant_context_anchor, incumbent_context_anchor)
|
||||
(
|
||||
relevant_context_anchor,
|
||||
incumbent_context_anchor,
|
||||
relevant_identity,
|
||||
)
|
||||
}
|
||||
|
||||
fn simple_object_event_listener_entry_object<'s>(
|
||||
@@ -907,12 +942,13 @@ fn simple_object_event_listener_entry_object<'s>(
|
||||
callback: v8::Local<'s, v8::Object>,
|
||||
relevant_context_anchor: v8::Local<'s, v8::Object>,
|
||||
incumbent_context_anchor: v8::Local<'s, v8::Object>,
|
||||
relevant_identity: Option<WindowExecutionContextIdentity>,
|
||||
is_callable: bool,
|
||||
capture: bool,
|
||||
once: bool,
|
||||
passive: bool,
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
SimpleObjectEventListenerEntryDeclaration::new(
|
||||
let entry = SimpleObjectEventListenerEntryDeclaration::new(
|
||||
original,
|
||||
callback,
|
||||
relevant_context_anchor,
|
||||
@@ -923,7 +959,9 @@ fn simple_object_event_listener_entry_object<'s>(
|
||||
passive,
|
||||
)
|
||||
.bind(scope)
|
||||
.expect("SimpleObject event listener entry declaration should bind")
|
||||
.expect("SimpleObject event listener entry declaration should bind");
|
||||
set_simple_object_event_listener_relevant_identity(scope, entry, relevant_identity);
|
||||
entry
|
||||
}
|
||||
|
||||
fn simple_object_event_handler_entry_object<'s>(
|
||||
@@ -932,9 +970,10 @@ fn simple_object_event_handler_entry_object<'s>(
|
||||
callback: v8::Local<'s, v8::Object>,
|
||||
relevant_context_anchor: v8::Local<'s, v8::Object>,
|
||||
incumbent_context_anchor: v8::Local<'s, v8::Object>,
|
||||
relevant_identity: Option<WindowExecutionContextIdentity>,
|
||||
) -> v8::Local<'s, v8::Object> {
|
||||
let marker = format!("event-handler:{handler_slot_name}");
|
||||
SimpleObjectEventHandlerEntryDeclaration::new(
|
||||
let entry = SimpleObjectEventHandlerEntryDeclaration::new(
|
||||
v8_string(scope, &marker),
|
||||
handler_slot_name,
|
||||
callback,
|
||||
@@ -943,7 +982,26 @@ fn simple_object_event_handler_entry_object<'s>(
|
||||
true,
|
||||
)
|
||||
.bind(scope)
|
||||
.expect("SimpleObject event handler entry declaration should bind")
|
||||
.expect("SimpleObject event handler entry declaration should bind");
|
||||
set_simple_object_event_listener_relevant_identity(scope, entry, relevant_identity);
|
||||
entry
|
||||
}
|
||||
|
||||
fn set_simple_object_event_listener_relevant_identity<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
entry: v8::Local<'s, v8::Object>,
|
||||
identity: Option<WindowExecutionContextIdentity>,
|
||||
) {
|
||||
let value = identity
|
||||
.and_then(|identity| v8_string(scope, &identity.serialize_for_internal_slot()))
|
||||
.map(v8::Local::<v8::Value>::from)
|
||||
.unwrap_or_else(|| v8::undefined(scope).into());
|
||||
set_private_value(
|
||||
scope,
|
||||
entry,
|
||||
SIMPLE_EVENT_TARGET_LISTENER_RELEVANT_IDENTITY_SLOT,
|
||||
value,
|
||||
);
|
||||
}
|
||||
|
||||
fn simple_object_event_listener_array_contains_original(
|
||||
@@ -1046,6 +1104,16 @@ fn simple_object_event_listener_snapshot_entry<'s>(
|
||||
entry,
|
||||
SIMPLE_EVENT_TARGET_LISTENER_INCUMBENT_CONTEXT_ANCHOR_SLOT,
|
||||
)?;
|
||||
let relevant_identity = get_private_value(
|
||||
scope,
|
||||
entry,
|
||||
SIMPLE_EVENT_TARGET_LISTENER_RELEVANT_IDENTITY_SLOT,
|
||||
)
|
||||
.and_then(|value| value.to_string(scope))
|
||||
.map(|value| value.to_rust_string_lossy(scope))
|
||||
.and_then(|serialized| {
|
||||
WindowExecutionContextIdentity::deserialize_from_internal_slot(&serialized)
|
||||
});
|
||||
let is_callable =
|
||||
simple_object_private_bool_slot(scope, entry, SIMPLE_EVENT_TARGET_LISTENER_CALLABLE_SLOT)
|
||||
.unwrap_or(false);
|
||||
@@ -1063,6 +1131,7 @@ fn simple_object_event_listener_snapshot_entry<'s>(
|
||||
callback,
|
||||
relevant_context,
|
||||
incumbent_context,
|
||||
relevant_identity,
|
||||
is_callable,
|
||||
capture,
|
||||
once,
|
||||
|
||||
@@ -718,6 +718,18 @@ impl JsContextHost {
|
||||
.popup_load_event()
|
||||
}
|
||||
|
||||
pub(crate) fn page_popup_close_sender(
|
||||
&self,
|
||||
) -> crate::page_task_queue::RendererPagePopupCloseSender {
|
||||
self.page_task_capabilities
|
||||
.get()
|
||||
.expect(
|
||||
"a live Page Window must install its complete Page task capabilities before popup close admission",
|
||||
)
|
||||
.dom_manipulation()
|
||||
.popup_close()
|
||||
}
|
||||
|
||||
pub(crate) fn page_file_entry_file_callback_sender(
|
||||
&self,
|
||||
) -> crate::page_task_queue::RendererPageFileEntryFileCallbackSender {
|
||||
|
||||
@@ -8,12 +8,14 @@ use crate::{
|
||||
SharedWebStorageStore, WINDOW_NAME_SLOT, apply_local_window_location_navigation,
|
||||
deep_clone_shared_web_storage_store, dispatch_simple_event_target_event,
|
||||
install_navigation_bootstrap_entry_for_holder, install_simple_event_target_methods,
|
||||
install_storage_aliases_for_window,
|
||||
install_simple_event_target_ordered_handlers, install_storage_aliases_for_window,
|
||||
install_window_location_history_navigation_runtime_state, new_shared_web_storage_store,
|
||||
scoped_indexed_db_factory, sync_document_location_runtime_state_from_window,
|
||||
scoped_indexed_db_factory, simple_object_event_set_ordered_handler,
|
||||
sync_document_location_runtime_state_from_window,
|
||||
sync_window_location_history_navigation_runtime_surface,
|
||||
sync_window_location_runtime_state, web_storage_area_key_for_storage_key,
|
||||
},
|
||||
definitions::define_function_accessor_property,
|
||||
document_runtime::create_content_security_policy_violation_event,
|
||||
document_runtime::{DocumentPolicyContainer, DocumentSandboxPolicy, DomHandle},
|
||||
host::HostTimerOwner,
|
||||
@@ -444,13 +446,18 @@ pub(super) struct LightweightPopupBrowsingContextRecord {
|
||||
location_url: Url,
|
||||
opener_sandbox_policy: Option<DocumentSandboxPolicy>,
|
||||
lifecycle: LightweightPopupLifecycle,
|
||||
is_closing: bool,
|
||||
navigation_id: LightweightPopupNavigationId,
|
||||
}
|
||||
|
||||
impl LightweightPopupBrowsingContextRecord {
|
||||
fn is_open(&self) -> bool {
|
||||
fn is_live(&self) -> bool {
|
||||
matches!(self.lifecycle, LightweightPopupLifecycle::Open(_))
|
||||
}
|
||||
|
||||
fn is_open(&self) -> bool {
|
||||
self.is_live() && !self.is_closing
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct OpenedLightweightPopup<'scope> {
|
||||
@@ -610,15 +617,30 @@ impl JsContextHost {
|
||||
.map(|record| record.navigation_id)
|
||||
}
|
||||
|
||||
fn close_lightweight_popup_browsing_context(
|
||||
fn begin_lightweight_popup_close(&mut self, popup_id: u64) -> bool {
|
||||
let Some(record) = self.lightweight_popup_record_mut(popup_id) else {
|
||||
return false;
|
||||
};
|
||||
if !record.is_open() {
|
||||
return false;
|
||||
}
|
||||
record.is_closing = true;
|
||||
true
|
||||
}
|
||||
|
||||
fn take_lightweight_popup_close_transition(
|
||||
&mut self,
|
||||
popup_id: u64,
|
||||
) -> Option<LightweightPopupCloseTransition> {
|
||||
let record = self.lightweight_popup_record_mut(popup_id)?;
|
||||
if !record.is_live() || !record.is_closing {
|
||||
return None;
|
||||
}
|
||||
let lifecycle = std::mem::replace(&mut record.lifecycle, LightweightPopupLifecycle::Closed);
|
||||
let LightweightPopupLifecycle::Open(open) = lifecycle else {
|
||||
return None;
|
||||
};
|
||||
record.is_closing = false;
|
||||
record.navigation_id = LightweightPopupNavigationId::new(
|
||||
record
|
||||
.navigation_id
|
||||
@@ -635,6 +657,34 @@ impl JsContextHost {
|
||||
})
|
||||
}
|
||||
|
||||
/// Definitely close a lightweight top-level browsing context after its
|
||||
/// queued DOM-manipulation close task reaches the head of the source.
|
||||
pub(crate) fn definitely_close_lightweight_popup_browsing_context<'s>(
|
||||
&mut self,
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
popup_id: u64,
|
||||
) -> bool {
|
||||
let Some(transition) = self.take_lightweight_popup_close_transition(popup_id) else {
|
||||
return false;
|
||||
};
|
||||
let window = self.lightweight_popup_window(scope, popup_id);
|
||||
self.unregister_service_worker_popup_client(popup_id);
|
||||
self.cancel_lightweight_popup_document_loads(popup_id);
|
||||
self.cancel_lightweight_popup_classic_script_loads(popup_id);
|
||||
if let Some(window) = window {
|
||||
clear_lightweight_popup_window_document_event_state(scope, window);
|
||||
}
|
||||
if let Some(document_handle) = transition.retired_document_handle {
|
||||
self.retire_lightweight_popup_document_handle(popup_id, document_handle);
|
||||
self.clear_custom_element_registry_associations_for_document(document_handle);
|
||||
}
|
||||
self.retire_lightweight_popup_document_owner(transition.retired_owner);
|
||||
self.retire_lightweight_popup_local_window(popup_id, transition.retired_local_window_id);
|
||||
self.lightweight_popup_window_names
|
||||
.retain(|_, named_popup_id| *named_popup_id != popup_id);
|
||||
true
|
||||
}
|
||||
|
||||
fn set_lightweight_popup_same_document_url(&mut self, popup_id: u64, url: Url) -> bool {
|
||||
let Some(record) = self.lightweight_popup_record_mut(popup_id) else {
|
||||
return false;
|
||||
@@ -824,6 +874,7 @@ impl JsContextHost {
|
||||
LIGHTWEIGHT_POPUP_EVENT_LISTENERS_SLOT,
|
||||
false,
|
||||
);
|
||||
install_lightweight_popup_event_handler_accessors(scope, window);
|
||||
let _ = LightweightPopupWindowMethodsDeclaration::default().initialize(scope, window);
|
||||
let initial_document_owner = self.allocate_lightweight_popup_document_owner(popup_id);
|
||||
let initial_local_window_id = self.allocate_lightweight_popup_local_window_id();
|
||||
@@ -896,6 +947,7 @@ impl JsContextHost {
|
||||
},
|
||||
session_storage_store,
|
||||
})),
|
||||
is_closing: false,
|
||||
navigation_id: LightweightPopupNavigationId::new(1),
|
||||
},
|
||||
);
|
||||
@@ -1118,7 +1170,7 @@ impl JsContextHost {
|
||||
) -> Option<v8::Local<'s, v8::Object>> {
|
||||
let (endpoint, opener) = {
|
||||
let record = self.lightweight_popup_browsing_contexts.get(&popup_id)?;
|
||||
if !record.is_open() {
|
||||
if !record.is_live() {
|
||||
return None;
|
||||
}
|
||||
let endpoint = record.opener?;
|
||||
@@ -1486,6 +1538,11 @@ impl JsContextHost {
|
||||
.is_some_and(LightweightPopupBrowsingContextRecord::is_open)
|
||||
}
|
||||
|
||||
pub(crate) fn lightweight_popup_is_closing(&self, popup_id: u64) -> bool {
|
||||
self.lightweight_popup_record(popup_id)
|
||||
.is_some_and(|record| record.is_live() && record.is_closing)
|
||||
}
|
||||
|
||||
pub(crate) fn open_lightweight_popup_ids(&self) -> Vec<u64> {
|
||||
let mut popup_ids = self
|
||||
.lightweight_popup_browsing_contexts
|
||||
@@ -1758,7 +1815,7 @@ impl JsContextHost {
|
||||
document_handle: crate::document_runtime::DomHandle,
|
||||
) -> bool {
|
||||
self.lightweight_popup_id_for_document_handle(document_handle)
|
||||
.is_some()
|
||||
.is_some_and(|popup_id| self.lightweight_popup_is_open(popup_id))
|
||||
}
|
||||
|
||||
pub(crate) fn current_lightweight_popup_document_owner(
|
||||
@@ -3637,7 +3694,7 @@ impl JsContextHost {
|
||||
document_remained_current = false;
|
||||
break;
|
||||
}
|
||||
set_object_slot(scope, window, &name, value);
|
||||
set_lightweight_popup_persisted_script_global(scope, window, &name, value);
|
||||
if !self.lightweight_popup_document_owner_is_current(script_document_owner) {
|
||||
document_remained_current = false;
|
||||
break;
|
||||
@@ -3664,7 +3721,7 @@ impl JsContextHost {
|
||||
if value.is_undefined() {
|
||||
continue;
|
||||
}
|
||||
set_object_slot(scope, window, &name, value);
|
||||
set_lightweight_popup_persisted_script_global(scope, window, &name, value);
|
||||
if !self.lightweight_popup_document_owner_is_current(script_document_owner) {
|
||||
document_remained_current = false;
|
||||
break;
|
||||
@@ -4394,27 +4451,21 @@ fn lightweight_popup_close_callback<'s>(
|
||||
return;
|
||||
};
|
||||
let host = unsafe { &mut *host_ptr };
|
||||
let Some(transition) = host.close_lightweight_popup_browsing_context(popup_id) else {
|
||||
if !host.begin_lightweight_popup_close(popup_id) {
|
||||
return;
|
||||
};
|
||||
}
|
||||
set_object_slot(
|
||||
scope,
|
||||
window,
|
||||
"closed",
|
||||
v8::Boolean::new(scope, true).into(),
|
||||
);
|
||||
host.unregister_service_worker_popup_client(popup_id);
|
||||
host.cancel_lightweight_popup_document_loads(popup_id);
|
||||
host.cancel_lightweight_popup_classic_script_loads(popup_id);
|
||||
clear_lightweight_popup_window_document_event_state(scope, window);
|
||||
if let Some(document_handle) = transition.retired_document_handle {
|
||||
host.retire_lightweight_popup_document_handle(popup_id, document_handle);
|
||||
host.clear_custom_element_registry_associations_for_document(document_handle);
|
||||
if host.page_popup_close_sender().send(popup_id).is_err() {
|
||||
tracing::debug!(
|
||||
popup_id,
|
||||
"retired Page DOM-manipulation route rejected popup close task"
|
||||
);
|
||||
}
|
||||
host.retire_lightweight_popup_document_owner(transition.retired_owner);
|
||||
host.retire_lightweight_popup_local_window(popup_id, transition.retired_local_window_id);
|
||||
host.lightweight_popup_window_names
|
||||
.retain(|_, named_popup_id| *named_popup_id != popup_id);
|
||||
}
|
||||
|
||||
fn lightweight_popup_initiator_endpoint<'s>(
|
||||
@@ -4752,6 +4803,89 @@ fn sync_lightweight_popup_window_location<'s>(
|
||||
}
|
||||
}
|
||||
|
||||
fn install_lightweight_popup_event_handler_accessors<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
window: v8::Local<'s, v8::Object>,
|
||||
) {
|
||||
install_simple_event_target_ordered_handlers(scope, window);
|
||||
for property_name in WINDOW_EVENT_HANDLER_PROPERTIES {
|
||||
let data = v8str(scope, property_name).into();
|
||||
define_function_accessor_property(
|
||||
scope,
|
||||
window,
|
||||
property_name,
|
||||
lightweight_popup_event_handler_getter,
|
||||
Some(data),
|
||||
lightweight_popup_event_handler_setter,
|
||||
Some(data),
|
||||
v8::PropertyAttribute::NONE,
|
||||
)
|
||||
.expect("lightweight popup Window event handler accessor should initialize");
|
||||
}
|
||||
}
|
||||
|
||||
fn lightweight_popup_event_handler_name<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
data: v8::Local<'s, v8::Value>,
|
||||
) -> Option<&'static str> {
|
||||
let requested = data.to_string(scope)?.to_rust_string_lossy(scope);
|
||||
WINDOW_EVENT_HANDLER_PROPERTIES
|
||||
.iter()
|
||||
.copied()
|
||||
.find(|candidate| *candidate == requested)
|
||||
}
|
||||
|
||||
fn lightweight_popup_event_handler_getter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if lightweight_popup_id_from_window(scope, args.this()).is_none() {
|
||||
rv.set_null();
|
||||
return;
|
||||
}
|
||||
let Some(property_name) = lightweight_popup_event_handler_name(scope, args.data()) else {
|
||||
rv.set_null();
|
||||
return;
|
||||
};
|
||||
rv.set(
|
||||
get_private_value(scope, args.this(), property_name)
|
||||
.filter(|value| value.is_function())
|
||||
.unwrap_or_else(|| v8::null(scope).into()),
|
||||
);
|
||||
}
|
||||
|
||||
fn lightweight_popup_event_handler_setter<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
args: v8::FunctionCallbackArguments<'s>,
|
||||
mut rv: v8::ReturnValue<'_, v8::Value>,
|
||||
) {
|
||||
if lightweight_popup_id_from_window(scope, args.this()).is_none() {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
}
|
||||
let Some(property_name) = lightweight_popup_event_handler_name(scope, args.data()) else {
|
||||
rv.set_undefined();
|
||||
return;
|
||||
};
|
||||
let value = args.get(0);
|
||||
let stored = if value.is_function() {
|
||||
value
|
||||
} else {
|
||||
v8::null(scope).into()
|
||||
};
|
||||
set_private_value(scope, args.this(), property_name, stored);
|
||||
simple_object_event_set_ordered_handler(
|
||||
scope,
|
||||
args.this(),
|
||||
LIGHTWEIGHT_POPUP_EVENT_LISTENERS_SLOT,
|
||||
property_name.strip_prefix("on").unwrap_or(property_name),
|
||||
property_name,
|
||||
stored.is_function(),
|
||||
);
|
||||
rv.set_undefined();
|
||||
}
|
||||
|
||||
fn clear_lightweight_popup_window_document_event_state<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
window: v8::Local<'s, v8::Object>,
|
||||
@@ -4769,6 +4903,21 @@ fn clear_lightweight_popup_window_document_event_state<'s>(
|
||||
}
|
||||
}
|
||||
|
||||
fn set_lightweight_popup_persisted_script_global<'s>(
|
||||
scope: &mut v8::PinScope<'s, '_>,
|
||||
window: v8::Local<'s, v8::Object>,
|
||||
name: &str,
|
||||
value: v8::Local<'s, v8::Value>,
|
||||
) {
|
||||
if WINDOW_EVENT_HANDLER_PROPERTIES.contains(&name) {
|
||||
// The wrapped popup script already assigned this value while its
|
||||
// exact popup owner scope was active. Do not invoke the setter again
|
||||
// from the surrounding top scope or replace its accessor.
|
||||
return;
|
||||
}
|
||||
set_object_slot(scope, window, name, value);
|
||||
}
|
||||
|
||||
fn lightweight_popup_effective_navigation_kind(
|
||||
current_url: Option<&Url>,
|
||||
kind: crate::context_bootstrap::LocationNavigationKind,
|
||||
|
||||
@@ -216,9 +216,157 @@ impl WindowExecutionContextIdentity {
|
||||
self.access_policy == WindowExecutionContextAccessPolicy::Universal
|
||||
}
|
||||
|
||||
/// Encodes an exact callback-realm identity for storage in an internal V8
|
||||
/// private slot. Lightweight popup realms alias their opener's V8 context,
|
||||
/// so retaining only that context would lose the LocalWindow generation
|
||||
/// and dispatch address captured when the callback was converted.
|
||||
pub(crate) fn serialize_for_internal_slot(self) -> String {
|
||||
let policy = u8::from(self.grants_universal_access());
|
||||
match (self.owner, self.dispatch_scope) {
|
||||
(WindowExecutionContextOwner::Frame(local_window_id), OwnerDispatchScope::Top) => {
|
||||
format!(
|
||||
"frame:{}:top:{}:{policy}",
|
||||
local_window_id.0,
|
||||
self.realm_token.as_u64()
|
||||
)
|
||||
}
|
||||
(
|
||||
WindowExecutionContextOwner::Frame(local_window_id),
|
||||
OwnerDispatchScope::Child(child_handle),
|
||||
) => format!(
|
||||
"frame:{}:child:{}:{}:{policy}",
|
||||
local_window_id.0,
|
||||
child_handle.index(),
|
||||
self.realm_token.as_u64()
|
||||
),
|
||||
(
|
||||
WindowExecutionContextOwner::LightweightPopup {
|
||||
popup_id,
|
||||
local_window_id,
|
||||
},
|
||||
OwnerDispatchScope::LightweightPopup(dispatch_popup_id),
|
||||
) => {
|
||||
debug_assert_eq!(popup_id, dispatch_popup_id);
|
||||
format!(
|
||||
"popup:{popup_id}:{}:{}:{policy}",
|
||||
local_window_id.as_u64(),
|
||||
self.realm_token.as_u64()
|
||||
)
|
||||
}
|
||||
_ => unreachable!("Window execution-context owner and dispatch scope diverged"),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn deserialize_from_internal_slot(serialized: &str) -> Option<Self> {
|
||||
fn access_policy(value: &str) -> Option<WindowExecutionContextAccessPolicy> {
|
||||
match value {
|
||||
"0" => Some(WindowExecutionContextAccessPolicy::EnforceWebOrigin),
|
||||
"1" => Some(WindowExecutionContextAccessPolicy::Universal),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
let parts = serialized.split(':').collect::<Vec<_>>();
|
||||
match parts.as_slice() {
|
||||
["frame", local_window_id, "top", realm_token, policy] => Some(Self::new(
|
||||
WindowExecutionContextOwner::Frame(crate::frame_owner_model::LocalWindowId(
|
||||
local_window_id.parse().ok()?,
|
||||
)),
|
||||
OwnerDispatchScope::Top,
|
||||
RuntimeObservableContextToken::from_raw(realm_token.parse().ok()?),
|
||||
access_policy(policy)?,
|
||||
)),
|
||||
[
|
||||
"frame",
|
||||
local_window_id,
|
||||
"child",
|
||||
child_handle,
|
||||
realm_token,
|
||||
policy,
|
||||
] => {
|
||||
let child_handle = child_handle.parse::<u64>().ok()?;
|
||||
let child_handle = usize::try_from(child_handle).ok()?;
|
||||
Some(Self::new(
|
||||
WindowExecutionContextOwner::Frame(crate::frame_owner_model::LocalWindowId(
|
||||
local_window_id.parse().ok()?,
|
||||
)),
|
||||
OwnerDispatchScope::Child(crate::document_runtime::DomHandle::new(
|
||||
child_handle,
|
||||
)),
|
||||
RuntimeObservableContextToken::from_raw(realm_token.parse().ok()?),
|
||||
access_policy(policy)?,
|
||||
))
|
||||
}
|
||||
["popup", popup_id, local_window_id, realm_token, policy] => {
|
||||
let popup_id = popup_id.parse().ok()?;
|
||||
Some(Self::new(
|
||||
WindowExecutionContextOwner::LightweightPopup {
|
||||
popup_id,
|
||||
local_window_id: LightweightPopupLocalWindowId::new(
|
||||
local_window_id.parse().ok()?,
|
||||
),
|
||||
},
|
||||
OwnerDispatchScope::LightweightPopup(popup_id),
|
||||
RuntimeObservableContextToken::from_raw(realm_token.parse().ok()?),
|
||||
access_policy(policy)?,
|
||||
))
|
||||
}
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::native_bridge::context_host) fn access_policy(
|
||||
self,
|
||||
) -> WindowExecutionContextAccessPolicy {
|
||||
self.access_policy
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn callback_identity_internal_slot_encoding_round_trips_every_owner_shape() {
|
||||
let identities = [
|
||||
WindowExecutionContextIdentity::new(
|
||||
WindowExecutionContextOwner::Frame(crate::frame_owner_model::LocalWindowId(7)),
|
||||
OwnerDispatchScope::Top,
|
||||
RuntimeObservableContextToken::from_raw(11),
|
||||
WindowExecutionContextAccessPolicy::EnforceWebOrigin,
|
||||
),
|
||||
WindowExecutionContextIdentity::new(
|
||||
WindowExecutionContextOwner::Frame(crate::frame_owner_model::LocalWindowId(13)),
|
||||
OwnerDispatchScope::Child(crate::document_runtime::DomHandle::new(17)),
|
||||
RuntimeObservableContextToken::from_raw(19),
|
||||
WindowExecutionContextAccessPolicy::Universal,
|
||||
),
|
||||
WindowExecutionContextIdentity::new(
|
||||
WindowExecutionContextOwner::LightweightPopup {
|
||||
popup_id: 23,
|
||||
local_window_id: LightweightPopupLocalWindowId::new(29),
|
||||
},
|
||||
OwnerDispatchScope::LightweightPopup(23),
|
||||
RuntimeObservableContextToken::from_raw(31),
|
||||
WindowExecutionContextAccessPolicy::EnforceWebOrigin,
|
||||
),
|
||||
];
|
||||
|
||||
for identity in identities {
|
||||
assert_eq!(
|
||||
WindowExecutionContextIdentity::deserialize_from_internal_slot(
|
||||
&identity.serialize_for_internal_slot()
|
||||
),
|
||||
Some(identity)
|
||||
);
|
||||
}
|
||||
assert_eq!(
|
||||
WindowExecutionContextIdentity::deserialize_from_internal_slot("popup:23:29:31:2"),
|
||||
None
|
||||
);
|
||||
assert_eq!(
|
||||
WindowExecutionContextIdentity::deserialize_from_internal_slot("not-an-identity"),
|
||||
None
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ mod owner_sources;
|
||||
mod parse_time;
|
||||
mod parser_async_module_admission;
|
||||
mod parser_owned_module_continuation;
|
||||
mod popup_close;
|
||||
mod popup_load_event;
|
||||
mod post_domcontentloaded_runtime;
|
||||
mod post_parse_owner_work;
|
||||
@@ -371,6 +372,10 @@ pub(crate) use self::parser_owned_module_continuation::{
|
||||
PageParserOwnedModuleContinuationBodyActivity, PageParserOwnedModuleContinuationTargetEffect,
|
||||
PageParserOwnedModuleContinuationTurnAction,
|
||||
};
|
||||
pub(crate) use self::popup_close::{
|
||||
PagePopupCloseTargetEffect, PagePopupCloseTurnAction, PagePopupCloseTurnOutcome,
|
||||
RendererPagePopupCloseOwner, RendererPagePopupCloseSender, RendererPagePopupCloseTask,
|
||||
};
|
||||
pub(crate) use self::popup_load_event::{
|
||||
PagePopupLoadEventTargetEffect, PagePopupLoadEventTurnAction, PagePopupLoadEventTurnOutcome,
|
||||
RendererPagePopupLoadEventOwner, RendererPagePopupLoadEventSender,
|
||||
|
||||
@@ -27,6 +27,9 @@ use super::{
|
||||
RendererPageImageLoadEventOwner, RendererPageImageLoadEventSender,
|
||||
RendererPageImageLoadEventTask,
|
||||
},
|
||||
popup_close::{
|
||||
RendererPagePopupCloseOwner, RendererPagePopupCloseSender, RendererPagePopupCloseTask,
|
||||
},
|
||||
popup_load_event::{
|
||||
RendererPagePopupLoadEventOwner, RendererPagePopupLoadEventSender,
|
||||
RendererPagePopupLoadEventTask,
|
||||
@@ -66,6 +69,7 @@ pub(crate) enum RendererPageDomManipulationOwner {
|
||||
FileEntryFileCallback(RendererPageFileEntryFileCallbackOwner),
|
||||
ImageLoadEvent(RendererPageImageLoadEventOwner),
|
||||
PopupLoadEvent(RendererPagePopupLoadEventOwner),
|
||||
PopupClose(RendererPagePopupCloseOwner),
|
||||
ConnectedStyleEvent(RendererPageStylesheetTaskOwner),
|
||||
TextTrackDefaultMode(RendererPageTextTrackDefaultModeOwner),
|
||||
TextTrackLoad(RendererPageTextTrackLoadOwner),
|
||||
@@ -82,6 +86,7 @@ pub(crate) enum RendererPageDomManipulationTask {
|
||||
FileEntryFileCallback(RendererPageFileEntryFileCallbackTask),
|
||||
ImageLoadEvent(RendererPageImageLoadEventTask),
|
||||
PopupLoadEvent(RendererPagePopupLoadEventTask),
|
||||
PopupClose(RendererPagePopupCloseTask),
|
||||
ConnectedStyleEvent(RendererPageConnectedStyleEventTask),
|
||||
TextTrackDefaultMode(RendererPageTextTrackDefaultModeTask),
|
||||
TextTrackLoad(RendererPageTextTrackLoadTask),
|
||||
@@ -110,6 +115,7 @@ impl RendererPageDomManipulationTask {
|
||||
Self::PopupLoadEvent(task) => {
|
||||
RendererPageDomManipulationOwner::PopupLoadEvent(task.owner())
|
||||
}
|
||||
Self::PopupClose(task) => RendererPageDomManipulationOwner::PopupClose(task.owner()),
|
||||
Self::ConnectedStyleEvent(task) => {
|
||||
RendererPageDomManipulationOwner::ConnectedStyleEvent(task.owner())
|
||||
}
|
||||
@@ -144,6 +150,7 @@ pub(crate) enum PageDomManipulationTurnAction {
|
||||
FileEntryFileCallback(super::PageFileEntryFileCallbackTurnAction),
|
||||
ImageLoadEvent(super::PageImageLoadEventTurnAction),
|
||||
PopupLoadEvent(super::PagePopupLoadEventTurnAction),
|
||||
PopupClose(super::PagePopupCloseTurnAction),
|
||||
ConnectedStyleEvent(PageConnectedStyleEventTurnAction),
|
||||
TextTrackDefaultMode(super::PageTextTrackDefaultModeTurnAction),
|
||||
TextTrackLoad(super::PageTextTrackLoadTurnAction),
|
||||
@@ -203,6 +210,10 @@ impl RendererPageDomManipulationSender {
|
||||
RendererPagePopupLoadEventSender::new(self.route.clone(), self.root_document)
|
||||
}
|
||||
|
||||
pub(crate) fn popup_close(&self) -> RendererPagePopupCloseSender {
|
||||
RendererPagePopupCloseSender::new(self.route.clone(), self.root_document)
|
||||
}
|
||||
|
||||
pub(crate) fn text_track_default_mode(&self) -> RendererPageTextTrackDefaultModeSender {
|
||||
RendererPageTextTrackDefaultModeSender::new(self.route.clone(), self.root_document)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
use crate::runtime::{PageOwnerTurnOutcome, RendererDocumentToken};
|
||||
|
||||
use super::dom_manipulation::{RendererPageDomManipulationRoute, RendererPageDomManipulationTask};
|
||||
|
||||
/// PageVm namespace plus the top-level lightweight browsing context whose
|
||||
/// script-visible closing flag has been set.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(crate) struct RendererPagePopupCloseOwner {
|
||||
root_document: RendererDocumentToken,
|
||||
popup_id: u64,
|
||||
}
|
||||
|
||||
impl RendererPagePopupCloseOwner {
|
||||
pub(crate) const fn new(root_document: RendererDocumentToken, popup_id: u64) -> Self {
|
||||
Self {
|
||||
root_document,
|
||||
popup_id,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) const fn root_document(self) -> RendererDocumentToken {
|
||||
self.root_document
|
||||
}
|
||||
|
||||
pub(crate) const fn popup_id(self) -> u64 {
|
||||
self.popup_id
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct RendererPagePopupCloseTask {
|
||||
owner: RendererPagePopupCloseOwner,
|
||||
}
|
||||
|
||||
impl RendererPagePopupCloseTask {
|
||||
fn new(owner: RendererPagePopupCloseOwner) -> Self {
|
||||
Self { owner }
|
||||
}
|
||||
|
||||
pub(crate) const fn owner(&self) -> RendererPagePopupCloseOwner {
|
||||
self.owner
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(crate) struct RendererPagePopupCloseRouteClosed;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct RendererPagePopupCloseSender {
|
||||
route: RendererPageDomManipulationRoute,
|
||||
root_document: RendererDocumentToken,
|
||||
}
|
||||
|
||||
impl RendererPagePopupCloseSender {
|
||||
pub(super) fn new(
|
||||
route: RendererPageDomManipulationRoute,
|
||||
root_document: RendererDocumentToken,
|
||||
) -> Self {
|
||||
Self {
|
||||
route,
|
||||
root_document,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn send(&self, popup_id: u64) -> Result<(), RendererPagePopupCloseRouteClosed> {
|
||||
let owner = RendererPagePopupCloseOwner::new(self.root_document, popup_id);
|
||||
self.route
|
||||
.send(RendererPageDomManipulationTask::PopupClose(
|
||||
RendererPagePopupCloseTask::new(owner),
|
||||
))
|
||||
.map_err(|_| RendererPagePopupCloseRouteClosed)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(crate) enum PagePopupCloseTargetEffect {
|
||||
DefinitelyClosed,
|
||||
DiscardedStaleOwner {
|
||||
current_owner: Option<RendererPagePopupCloseOwner>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(crate) struct PagePopupCloseTurnAction {
|
||||
pub(crate) owner: RendererPagePopupCloseOwner,
|
||||
pub(crate) target_effect: PagePopupCloseTargetEffect,
|
||||
}
|
||||
|
||||
pub(crate) type PagePopupCloseTurnOutcome = PageOwnerTurnOutcome<PagePopupCloseTurnAction>;
|
||||
@@ -111,6 +111,7 @@ pub(crate) use self::page_vm::AuthorizedCurrentPageMiscPlatformApiTask;
|
||||
pub(crate) use self::page_vm::AuthorizedCurrentPageModuleReaction;
|
||||
pub(crate) use self::page_vm::AuthorizedCurrentPageNavigationApiTask;
|
||||
pub(crate) use self::page_vm::AuthorizedCurrentPageOpfsTask;
|
||||
pub(crate) use self::page_vm::AuthorizedCurrentPagePopupClose;
|
||||
pub(crate) use self::page_vm::AuthorizedCurrentPagePopupLoadEvent;
|
||||
pub(crate) use self::page_vm::AuthorizedCurrentPageRenderingUpdate;
|
||||
pub(crate) use self::page_vm::AuthorizedCurrentPageServiceWorkerClientMessage;
|
||||
|
||||
@@ -131,6 +131,7 @@ mod page_owned_document_script_completion;
|
||||
mod page_owned_document_script_hooks;
|
||||
mod page_parser_async_module_admission;
|
||||
mod page_parser_owned_module_continuation;
|
||||
mod page_popup_close;
|
||||
mod page_popup_load_event;
|
||||
mod page_rendering_update;
|
||||
#[cfg(test)]
|
||||
@@ -221,6 +222,7 @@ pub(crate) use page_module_reaction::AuthorizedCurrentPageModuleReaction;
|
||||
pub(crate) use page_modulepreload_start::AuthorizedCurrentChildModulepreloadStartTask;
|
||||
pub(crate) use page_navigation_api_task::AuthorizedCurrentPageNavigationApiTask;
|
||||
pub(crate) use page_opfs_task::AuthorizedCurrentPageOpfsTask;
|
||||
pub(crate) use page_popup_close::AuthorizedCurrentPagePopupClose;
|
||||
pub(crate) use page_popup_load_event::AuthorizedCurrentPagePopupLoadEvent;
|
||||
pub(crate) use page_rendering_update::AuthorizedCurrentPageRenderingUpdate;
|
||||
pub(crate) use page_resource_completion::{
|
||||
|
||||
@@ -33,6 +33,9 @@ impl PageVm {
|
||||
RendererPageDomManipulationTask::PopupLoadEvent(task) => self
|
||||
.apply_selected_page_popup_load_event_turn(task)
|
||||
.map(|outcome| outcome.map_action(PageDomManipulationTurnAction::PopupLoadEvent)),
|
||||
RendererPageDomManipulationTask::PopupClose(task) => self
|
||||
.apply_selected_page_popup_close_turn(task)
|
||||
.map(|outcome| outcome.map_action(PageDomManipulationTurnAction::PopupClose)),
|
||||
RendererPageDomManipulationTask::ConnectedStyleEvent(task) => self
|
||||
.apply_selected_page_connected_style_event_turn(task)
|
||||
.map(|outcome| {
|
||||
|
||||
@@ -37,6 +37,7 @@ impl PageVm {
|
||||
PageDomManipulationTurnAction::PopupLoadEvent(action) => {
|
||||
action.into_page_task_completion()
|
||||
}
|
||||
PageDomManipulationTurnAction::PopupClose(action) => action.into_page_task_completion(),
|
||||
PageDomManipulationTurnAction::ConnectedStyleEvent(action) => {
|
||||
return self
|
||||
.finish_selected_page_connected_style_event_task(action, loader)
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
use crate::page_task_queue::{
|
||||
PagePopupCloseTargetEffect, PagePopupCloseTurnAction, PagePopupCloseTurnOutcome,
|
||||
RendererPagePopupCloseOwner, RendererPagePopupCloseTask,
|
||||
};
|
||||
|
||||
use super::{IntoPageTaskCompletion, PageTaskCompletion, PageVm};
|
||||
|
||||
impl IntoPageTaskCompletion for PagePopupCloseTurnAction {
|
||||
fn into_page_task_completion(self) -> PageTaskCompletion {
|
||||
match self.target_effect {
|
||||
PagePopupCloseTargetEffect::DefinitelyClosed => PageTaskCompletion::CheckpointOnly,
|
||||
PagePopupCloseTargetEffect::DiscardedStaleOwner { .. } => {
|
||||
PageTaskCompletion::NoCompletion
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Proof that the Page arbiter matched the PageVm namespace and a live
|
||||
/// lightweight popup whose closing flag is set.
|
||||
pub(crate) struct AuthorizedCurrentPagePopupClose(RendererPagePopupCloseTask);
|
||||
|
||||
impl AuthorizedCurrentPagePopupClose {
|
||||
fn new(task: RendererPagePopupCloseTask) -> Self {
|
||||
Self(task)
|
||||
}
|
||||
|
||||
pub(crate) fn into_task(self) -> RendererPagePopupCloseTask {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl PageVm {
|
||||
fn current_page_popup_close_owner(
|
||||
&self,
|
||||
expected: RendererPagePopupCloseOwner,
|
||||
) -> Option<RendererPagePopupCloseOwner> {
|
||||
if expected.root_document() != self.document_lifecycle.identity().document {
|
||||
return None;
|
||||
}
|
||||
self.vm().current_popup_close_owner(
|
||||
expected.popup_id(),
|
||||
self.document_lifecycle.identity().document,
|
||||
)
|
||||
}
|
||||
|
||||
pub(in crate::runtime) fn apply_selected_page_popup_close_turn(
|
||||
&mut self,
|
||||
task: RendererPagePopupCloseTask,
|
||||
) -> anyhow::Result<PagePopupCloseTurnOutcome> {
|
||||
let owner = task.owner();
|
||||
let current_owner = self.current_page_popup_close_owner(owner);
|
||||
let target_effect = if current_owner == Some(owner) {
|
||||
self.vm_mut()
|
||||
.apply_current_popup_close_body(AuthorizedCurrentPagePopupClose::new(task))?;
|
||||
PagePopupCloseTargetEffect::DefinitelyClosed
|
||||
} else {
|
||||
tracing::debug!(
|
||||
?owner,
|
||||
?current_owner,
|
||||
"discarded stale exact-popup close task"
|
||||
);
|
||||
PagePopupCloseTargetEffect::DiscardedStaleOwner { current_owner }
|
||||
};
|
||||
let action = PagePopupCloseTurnAction {
|
||||
owner,
|
||||
target_effect,
|
||||
};
|
||||
Ok(PagePopupCloseTurnOutcome::new(action))
|
||||
}
|
||||
}
|
||||
@@ -746,6 +746,7 @@ mod page_task_enqueue;
|
||||
mod parser_owned_classic;
|
||||
pub(crate) use parser_owned_classic::*;
|
||||
mod parser_module_terminal;
|
||||
mod popup_close;
|
||||
mod popup_load_event;
|
||||
mod post_parse;
|
||||
mod post_parse_lifecycle;
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
use anyhow::Result;
|
||||
|
||||
use super::ScriptVm;
|
||||
use crate::{
|
||||
page_task_queue::RendererPagePopupCloseOwner,
|
||||
runtime::{AuthorizedCurrentPagePopupClose, RendererDocumentToken},
|
||||
};
|
||||
|
||||
impl ScriptVm {
|
||||
pub(crate) fn current_popup_close_owner(
|
||||
&self,
|
||||
popup_id: u64,
|
||||
root_document: RendererDocumentToken,
|
||||
) -> Option<RendererPagePopupCloseOwner> {
|
||||
self._context_host
|
||||
.borrow()
|
||||
.lightweight_popup_is_closing(popup_id)
|
||||
.then(|| RendererPagePopupCloseOwner::new(root_document, popup_id))
|
||||
}
|
||||
|
||||
pub(crate) fn apply_current_popup_close_body(
|
||||
&mut self,
|
||||
authorization: AuthorizedCurrentPagePopupClose,
|
||||
) -> Result<()> {
|
||||
let popup_id = authorization.into_task().owner().popup_id();
|
||||
self.with_default_context_scope(|scope, host_ptr| {
|
||||
assert!(
|
||||
unsafe { &mut *host_ptr }
|
||||
.definitely_close_lightweight_popup_browsing_context(scope, popup_id),
|
||||
"authorized popup close task must retain its closing browsing context"
|
||||
);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -5885,7 +5885,8 @@ popup.clearTimeout(cancelledPopupTimer);
|
||||
#[tokio::test]
|
||||
async fn lightweight_popup_close_retires_its_local_window_timers() {
|
||||
let loader = ResourceRequestClient::new(&moli_fetch::FetchConfig::default()).expect("loader");
|
||||
let mut vm = new_storage_test_vm_with_loader("https://timer-popup-close.test/", &loader);
|
||||
let mut vm =
|
||||
new_page_task_executor_test_vm_with_loader("https://timer-popup-close.test/", &loader);
|
||||
|
||||
vm.eval(
|
||||
r#"
|
||||
@@ -5897,9 +5898,14 @@ popup.close();
|
||||
"#,
|
||||
)
|
||||
.expect("popup timer close setup should evaluate");
|
||||
vm.advance_timers_until_deadline_for_test(&loader)
|
||||
.await
|
||||
.expect("closed popup timer should leave the timer queue quiescent");
|
||||
advance_page_task_executor_until_eval_equals(
|
||||
&mut vm,
|
||||
&loader,
|
||||
"String(popup.opener === null)",
|
||||
"true",
|
||||
"queued popup definite close",
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
vm.eval("__closedPopupTimerEvents.length")
|
||||
.expect("closed popup timer result should evaluate"),
|
||||
|
||||
@@ -25606,9 +25606,10 @@ fn window_open_about_blank_returns_lightweight_popup_window() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn lightweight_popup_opener_accessor_preserves_and_disowns_the_underlying_relation() {
|
||||
let mut vm = new_storage_test_vm("https://example.com/");
|
||||
#[tokio::test]
|
||||
async fn lightweight_popup_opener_accessor_preserves_and_disowns_the_underlying_relation() {
|
||||
let loader = ResourceRequestClient::new(&moli_fetch::FetchConfig::default()).expect("loader");
|
||||
let mut vm = new_page_task_executor_test_vm_with_loader("https://example.com/", &loader);
|
||||
|
||||
let result = vm
|
||||
.eval(
|
||||
@@ -25631,6 +25632,8 @@ fn lightweight_popup_opener_accessor_preserves_and_disowns_the_underlying_relati
|
||||
const closedGet = Object.getOwnPropertyDescriptor(closed, "opener").get;
|
||||
closed.close();
|
||||
closed.opener = "closed replacement";
|
||||
globalThis.__closedPopupOpenerGet = closedGet;
|
||||
globalThis.__closedPopupForOpenerTest = closed;
|
||||
|
||||
return JSON.stringify({
|
||||
replaced: {
|
||||
@@ -25655,8 +25658,9 @@ fn lightweight_popup_opener_accessor_preserves_and_disowns_the_underlying_relati
|
||||
boundGetterIsNull: disownedGet() === null,
|
||||
accessorPreserved: descriptorAfterNull.get === disownedGet
|
||||
},
|
||||
closed: {
|
||||
boundGetterIsNull: closedGet() === null,
|
||||
closing: {
|
||||
closedFlag: closed.closed,
|
||||
boundGetterKeepsRelation: closedGet() === window,
|
||||
value: closed.opener
|
||||
}
|
||||
});
|
||||
@@ -25667,7 +25671,20 @@ fn lightweight_popup_opener_accessor_preserves_and_disowns_the_underlying_relati
|
||||
|
||||
assert_eq!(
|
||||
result,
|
||||
r#"{"replaced":{"accessorShape":"function:function::true:true","value":"replacement","boundGetterKeepsRelation":true,"borrowedGetterKeepsRelation":true,"dataShape":"true:true:true"},"disowned":{"valueIsNull":true,"boundGetterIsNull":true,"accessorPreserved":true},"closed":{"boundGetterIsNull":true,"value":"closed replacement"}}"#
|
||||
r#"{"replaced":{"accessorShape":"function:function::true:true","value":"replacement","boundGetterKeepsRelation":true,"borrowedGetterKeepsRelation":true,"dataShape":"true:true:true"},"disowned":{"valueIsNull":true,"boundGetterIsNull":true,"accessorPreserved":true},"closing":{"closedFlag":true,"boundGetterKeepsRelation":true,"value":"closed replacement"}}"#
|
||||
);
|
||||
advance_page_task_executor_until_eval_equals(
|
||||
&mut vm,
|
||||
&loader,
|
||||
"String(__closedPopupOpenerGet() === null)",
|
||||
"true",
|
||||
"queued popup definite close clears the opener relation",
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
vm.eval("String(__closedPopupForOpenerTest.opener)")
|
||||
.expect("closed popup replacement opener should evaluate"),
|
||||
"closed replacement"
|
||||
);
|
||||
}
|
||||
|
||||
@@ -26973,6 +26990,129 @@ async fn noopener_hyperlink_reuses_an_existing_named_popup_and_preserves_its_ope
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn top_realm_popup_load_handler_can_queue_a_timer_after_window_close() {
|
||||
let loader = ResourceRequestClient::new(&moli_fetch::FetchConfig::default()).expect("loader");
|
||||
let mut vm =
|
||||
new_page_task_executor_test_vm_with_loader("https://example.com/base/page.html", &loader);
|
||||
|
||||
assert_eq!(
|
||||
vm.eval(
|
||||
r#"
|
||||
(() => {
|
||||
globalThis.__popupCloseHandlerState = "pending";
|
||||
globalThis.__popupCloseHandlerEvents = [];
|
||||
const targetUrl = URL.createObjectURL(new Blob([
|
||||
"<!doctype html><title>close target</title>"
|
||||
], { type: "text/html" }));
|
||||
const popup = open(targetUrl);
|
||||
popup.onload = () => {
|
||||
popup.close();
|
||||
const timerId = setTimeout(() => {
|
||||
__popupCloseHandlerEvents.push([
|
||||
popup.closed,
|
||||
popup.opener === null
|
||||
].join(":"));
|
||||
}, 0);
|
||||
__popupCloseHandlerState = [
|
||||
popup.closed,
|
||||
popup.opener === window,
|
||||
timerId !== 0
|
||||
].join(":");
|
||||
};
|
||||
globalThis.__popupCloseHandlerPopup = popup;
|
||||
return __popupCloseHandlerState;
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("popup close handler setup should evaluate"),
|
||||
"pending"
|
||||
);
|
||||
|
||||
advance_page_task_executor_until_eval_equals(
|
||||
&mut vm,
|
||||
&loader,
|
||||
"__popupCloseHandlerState",
|
||||
"true:true:true",
|
||||
"popup close handler synchronous state",
|
||||
)
|
||||
.await;
|
||||
assert_eq!(
|
||||
vm.eval("__popupCloseHandlerEvents.length")
|
||||
.expect("popup close handler timer count should evaluate"),
|
||||
"0",
|
||||
"the close task must remain ahead of the later timer"
|
||||
);
|
||||
|
||||
advance_page_task_executor_until_eval_equals(
|
||||
&mut vm,
|
||||
&loader,
|
||||
"__popupCloseHandlerEvents.join('|')",
|
||||
"true:true",
|
||||
"top-realm timer queued after popup close",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn named_popup_broadcast_precedes_its_queued_close() {
|
||||
let loader = ResourceRequestClient::new(&moli_fetch::FetchConfig::default()).expect("loader");
|
||||
let mut vm =
|
||||
new_page_task_executor_test_vm_with_loader("https://example.com/base/page.html", &loader);
|
||||
|
||||
assert_eq!(
|
||||
vm.eval(
|
||||
r#"
|
||||
(() => {
|
||||
const popupName = "moli-noopener-close-order";
|
||||
globalThis.__namedPopupCloseOrder = "pending";
|
||||
const channel = new BroadcastChannel(popupName);
|
||||
const popup = open("about:blank", popupName);
|
||||
channel.onmessage = event => {
|
||||
__namedPopupCloseOrder = JSON.stringify({
|
||||
payload: event.data,
|
||||
openerPreserved: popup.opener === window,
|
||||
alreadyClosing: popup.closed
|
||||
});
|
||||
};
|
||||
const targetMarkup = `<!doctype html><script>
|
||||
const channel = new BroadcastChannel(window.name);
|
||||
channel.postMessage("sent-before-close");
|
||||
window.close();
|
||||
<\/script>`;
|
||||
const link = document.createElement("a");
|
||||
link.rel = "noopener";
|
||||
link.target = popupName;
|
||||
link.href = URL.createObjectURL(new Blob([targetMarkup], { type: "text/html" }));
|
||||
document.body.appendChild(link);
|
||||
globalThis.__namedPopupCloseOrderPopup = popup;
|
||||
link.click();
|
||||
return __namedPopupCloseOrder;
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.expect("named popup close ordering setup should evaluate"),
|
||||
"pending"
|
||||
);
|
||||
|
||||
advance_page_task_executor_until_eval_equals(
|
||||
&mut vm,
|
||||
&loader,
|
||||
"__namedPopupCloseOrder",
|
||||
r#"{"payload":"sent-before-close","openerPreserved":true,"alreadyClosing":true}"#,
|
||||
"BroadcastChannel delivery before named popup close",
|
||||
)
|
||||
.await;
|
||||
advance_page_task_executor_until_eval_equals(
|
||||
&mut vm,
|
||||
&loader,
|
||||
"String(__namedPopupCloseOrderPopup.opener === null)",
|
||||
"true",
|
||||
"named popup definite close",
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn lightweight_popup_post_message_interleaves_opener_promise_waiters() {
|
||||
let loader = ResourceRequestClient::new(&moli_fetch::FetchConfig::default()).expect("loader");
|
||||
|
||||
Reference in New Issue
Block a user