mirror of
https://github.com/lexmount/moli.git
synced 2026-09-27 00:01:32 +00:00
fix(cdp): preserve session routing for child default bindings
This commit is contained in:
@@ -1040,6 +1040,15 @@ async fn runtime_add_binding_default_world_replays_into_runtime_materialized_chi
|
||||
)
|
||||
.await;
|
||||
|
||||
// V8 replays bindings into future contexts while Runtime is enabled.
|
||||
ctx.process_async(json!({
|
||||
"id": 420,
|
||||
"method": "Runtime.enable",
|
||||
"sessionId": "SID-1",
|
||||
}))
|
||||
.await;
|
||||
ctx.expect_result(420, json!({}), Some("SID-1"));
|
||||
|
||||
ctx.process_async(json!({
|
||||
"id": 421,
|
||||
"method": "Runtime.addBinding",
|
||||
@@ -1384,3 +1393,93 @@ async fn runtime_evaluate_child_history_back_emits_child_frame_navigation_and_li
|
||||
)
|
||||
.await;
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn attached_runtime_binding_reports_child_calls_without_primary_registration() {
|
||||
let mut ctx = TestContext::new();
|
||||
load_bc_with_session(&mut ctx, "BID-1", "TID-1", "SID-1", "about:blank");
|
||||
ctx.enable_page_events_for_test(Some("SID-1"));
|
||||
ctx.process_async(json!({
|
||||
"id": 48_000,
|
||||
"method": "Target.attachToTarget",
|
||||
"params": {"targetId": "TID-1", "flatten": true}
|
||||
}))
|
||||
.await;
|
||||
let attached_session_id = take_response_by_id(&mut ctx, 48_000)["result"]["sessionId"]
|
||||
.as_str()
|
||||
.expect("second target session")
|
||||
.to_owned();
|
||||
assert_ne!(attached_session_id, "SID-1");
|
||||
ctx.process_async(json!({
|
||||
"id": 48_001,
|
||||
"method": "Runtime.enable",
|
||||
"sessionId": attached_session_id,
|
||||
}))
|
||||
.await;
|
||||
ctx.expect_result(48_001, json!({}), Some(&attached_session_id));
|
||||
ctx.process_async(json!({
|
||||
"id": 48_002,
|
||||
"method": "Runtime.addBinding",
|
||||
"sessionId": attached_session_id,
|
||||
"params": {"name": "attachedChildBinding"}
|
||||
}))
|
||||
.await;
|
||||
ctx.expect_result(48_002, json!({}), Some(&attached_session_id));
|
||||
ctx.process_async(json!({
|
||||
"id": 48_003,
|
||||
"method": "Page.navigate",
|
||||
"sessionId": "SID-1",
|
||||
"params": {"url": "data:text/html,<iframe srcdoc=\"<body>child</body>\"></iframe>"}
|
||||
}))
|
||||
.await;
|
||||
let _ = take_response_by_id(&mut ctx, 48_003);
|
||||
let frame_id = child_frame_id_for_single_iframe(&mut ctx, 48_004).await;
|
||||
wait_until_frame_stopped_loading(&mut ctx, &frame_id).await;
|
||||
let child_context_id = wait_for_child_default_execution_context_id(
|
||||
&mut ctx,
|
||||
&frame_id,
|
||||
"attached child binding context",
|
||||
)
|
||||
.await;
|
||||
for (offset, scoped) in [(0_u64, false), (3, true)] {
|
||||
if scoped {
|
||||
ctx.process_async(json!({
|
||||
"id": 48_005 + offset,
|
||||
"method": "Runtime.addBinding",
|
||||
"sessionId": attached_session_id,
|
||||
"params": {"name": "attachedChildBinding", "executionContextId": child_context_id}
|
||||
}))
|
||||
.await;
|
||||
ctx.expect_result(48_005 + offset, json!({}), Some(&attached_session_id));
|
||||
}
|
||||
ctx.sent.clear();
|
||||
ctx.process_async(json!({
|
||||
"id": 48_006 + offset,
|
||||
"method": "Runtime.evaluate",
|
||||
"sessionId": "SID-1",
|
||||
"params": {"expression": "frames[0].attachedChildBinding('child-call'); 'called'"}
|
||||
}))
|
||||
.await;
|
||||
assert_eq!(
|
||||
take_response_by_id(&mut ctx, 48_006 + offset)["result"]["result"]["value"],
|
||||
json!("called")
|
||||
);
|
||||
let events = ctx
|
||||
.sent
|
||||
.iter()
|
||||
.filter(|message| message["method"] == "Runtime.bindingCalled")
|
||||
.collect::<Vec<_>>();
|
||||
assert_eq!(
|
||||
events.len(),
|
||||
1,
|
||||
"one notification for the registering session (scoped={scoped}): {events:?}"
|
||||
);
|
||||
assert_eq!(events[0]["sessionId"], json!(attached_session_id));
|
||||
assert_eq!(events[0]["params"]["name"], "attachedChildBinding");
|
||||
assert_eq!(events[0]["params"]["payload"], "child-call");
|
||||
assert_eq!(
|
||||
events[0]["params"]["executionContextId"],
|
||||
json!(child_context_id)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8654,46 +8654,24 @@ fn complete_pending_runtime_binding_context_lookup_command(
|
||||
mut task: RuntimeBindingCommandTask,
|
||||
completed_lookup: Result<CompletedRuntimeChildDefaultContextLookupDispatch, String>,
|
||||
) -> RuntimeCommandTaskStep {
|
||||
let is_child_default_context = match completed_lookup {
|
||||
Ok(completed_lookup) => {
|
||||
match conn.complete_child_default_execution_context_lookup(completed_lookup) {
|
||||
Ok(is_child_default_context) => is_child_default_context,
|
||||
Err(message) => {
|
||||
return RuntimeCommandTaskStep::Complete(runtime_inspector_error_plan(
|
||||
completed.command_id,
|
||||
message,
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
Err(message) => {
|
||||
return RuntimeCommandTaskStep::Complete(runtime_inspector_error_plan(
|
||||
completed.command_id,
|
||||
message,
|
||||
));
|
||||
}
|
||||
};
|
||||
|
||||
if !is_child_default_context {
|
||||
if matches!(task.action, RuntimeBindingCommand::Add)
|
||||
|| matches!(task.action, RuntimeBindingCommand::Remove)
|
||||
&& runtime_remove_binding_should_skip_live_page_update(conn, &completed.owner_scope)
|
||||
{
|
||||
task.skip_live_page_update_after_inspector_success = true;
|
||||
}
|
||||
return start_pending_runtime_binding_inspector_phase(conn, &completed, task);
|
||||
if let Err(message) = completed_lookup
|
||||
.and_then(|lookup| conn.complete_child_default_execution_context_lookup(lookup))
|
||||
{
|
||||
return RuntimeCommandTaskStep::Complete(runtime_inspector_error_plan(
|
||||
completed.command_id,
|
||||
message,
|
||||
));
|
||||
}
|
||||
|
||||
task.command_response = Some(RuntimeBindingCommandResponse::empty_success());
|
||||
match start_pending_runtime_binding_page_phase(
|
||||
conn,
|
||||
completed.command_id,
|
||||
task.clone(),
|
||||
completed.owner_scope.clone(),
|
||||
) {
|
||||
Some(pending) => RuntimeCommandTaskStep::Pending(Box::new(pending)),
|
||||
None => complete_runtime_binding_after_live_update(conn, completed, task),
|
||||
// Child default realms are registered V8 Inspector contexts too. Installing
|
||||
// a native callback here would bypass the registering Inspector session.
|
||||
if matches!(task.action, RuntimeBindingCommand::Add)
|
||||
|| matches!(task.action, RuntimeBindingCommand::Remove)
|
||||
&& runtime_remove_binding_should_skip_live_page_update(conn, &completed.owner_scope)
|
||||
{
|
||||
task.skip_live_page_update_after_inspector_success = true;
|
||||
}
|
||||
start_pending_runtime_binding_inspector_phase(conn, &completed, task)
|
||||
}
|
||||
|
||||
async fn complete_pending_runtime_binding_inspector_command(
|
||||
|
||||
@@ -461,10 +461,15 @@ impl JsContextHost {
|
||||
self.stored_document_start_scripts.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn stored_default_runtime_binding_names(&self) -> Vec<String> {
|
||||
pub(crate) fn stored_default_native_runtime_binding_names(&self) -> Vec<String> {
|
||||
self.stored_runtime_bindings
|
||||
.iter()
|
||||
.filter(|binding| binding.execution_context_name.is_none())
|
||||
// Inspector installs session-owned bindings when each V8 context
|
||||
// is reported. Replacing them with a native callback would lose
|
||||
// the registering sessions and their context filters.
|
||||
.filter(|binding| {
|
||||
binding.devtools_session.is_none() && binding.execution_context_name.is_none()
|
||||
})
|
||||
.map(|binding| binding.name.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
@@ -42,6 +42,10 @@ impl JsContextHost {
|
||||
handle: DomHandle,
|
||||
window: v8::Local<'_, v8::Object>,
|
||||
) {
|
||||
let binding_names = self.stored_default_native_runtime_binding_names();
|
||||
if binding_names.is_empty() {
|
||||
return;
|
||||
}
|
||||
let execution_context_id = self.child_default_execution_context_id(handle).unwrap_or(0);
|
||||
let Some(document_owner) = self.current_child_document_task_owner(handle) else {
|
||||
return;
|
||||
@@ -54,12 +58,6 @@ impl JsContextHost {
|
||||
if !self.register_runtime_binding_execution_context(execution_context, document_owner) {
|
||||
return;
|
||||
}
|
||||
let binding_names = self
|
||||
.stored_runtime_bindings
|
||||
.iter()
|
||||
.filter(|binding| binding.execution_context_name.is_none())
|
||||
.map(|binding| binding.name.clone())
|
||||
.collect::<Vec<_>>();
|
||||
for binding_name in binding_names {
|
||||
let Some(key) = v8_string(scope, &binding_name) else {
|
||||
continue;
|
||||
|
||||
+12
-3
@@ -213,9 +213,18 @@ async fn detached_session_binding_is_not_installed_in_a_later_child_realm() {
|
||||
binding(session_b, "liveChildBinding"),
|
||||
];
|
||||
page_vm.set_stored_runtime_bindings(&bindings);
|
||||
page_vm
|
||||
.vm_mut()
|
||||
.ensure_runtime_inspector_session(Some("session-a"));
|
||||
for (session_id, name) in [
|
||||
("session-a", "detachedChildBinding"),
|
||||
("session-b", "liveChildBinding"),
|
||||
] {
|
||||
page_vm
|
||||
.vm_mut()
|
||||
.dispatch_inspector_protocol_message_for_session(
|
||||
Some(session_id),
|
||||
r#"{"id":1,"method":"Runtime.enable"}"#,
|
||||
)?;
|
||||
page_vm.add_runtime_binding(Some(session_id), name, None, None)?;
|
||||
}
|
||||
|
||||
assert!(page_vm.detach_runtime_inspector_session(Some("session-a")));
|
||||
queue_child_realm_materialization(&mut page_vm, "post-detach-binding-child")?;
|
||||
|
||||
@@ -327,7 +327,7 @@ impl ScriptVm {
|
||||
let binding_names = self
|
||||
._context_host
|
||||
.borrow()
|
||||
.stored_default_runtime_binding_names();
|
||||
.stored_default_native_runtime_binding_names();
|
||||
for name in binding_names {
|
||||
if let Err(error) =
|
||||
self.install_runtime_binding_in_child_default_context(execution_context_id, &name)
|
||||
|
||||
Reference in New Issue
Block a user