Split worker target projection by worker kind

This commit is contained in:
ldm0
2026-09-28 12:13:45 +08:00
parent 949d9efa78
commit 5fb3d8e9a0
7 changed files with 6764 additions and 6765 deletions
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,854 @@
use super::*;
pub(super) fn dedicated_worker_target_is_current(
conn: &CdpConnection,
browser_context_id: &str,
renderer_instance_id: u64,
target_id: &str,
) -> bool {
let Some(context) = conn.browser_context_by_id(browser_context_id) else {
return false;
};
context
.dedicated_worker_targets
.get(&renderer_instance_id)
.is_some_and(|target| {
target.target_id == target_id
&& context.target_page_residence_is_current(&target.owner_page)
})
}
pub(super) fn register_dedicated_worker_target(
conn: &mut CdpConnection,
browser_context_id: &str,
owner_page: TargetPageResidenceIdentity,
owner_renderer_page: RendererPageResidenceIdentity,
owner_page_network_sessions: Vec<Option<String>>,
info: RendererDedicatedWorkerTargetInfo,
) -> TargetPreparedOutputs {
let mut outputs = TargetPreparedOutputs::default();
if info.owner_local_host_id != owner_renderer_page.owner_local_host_id()
|| info.page_id != owner_renderer_page.page_id()
{
return outputs;
}
if conn
.browser_context_by_id(browser_context_id)
.and_then(|context| {
context.dedicated_worker_target_id_for_renderer_instance(info.instance_id)
})
.is_some()
{
return outputs;
}
let target_id = conn.gen_target_id();
let request_url = match Url::parse(&info.request_url) {
Ok(url) => url,
Err(_) => return outputs,
};
let document_url = match Url::parse(&info.document_url) {
Ok(url) => url,
Err(_) => return outputs,
};
let owner_target_id = owner_page.target_id().unwrap_or_default().to_owned();
let should_emit_created = conn.has_any_target_discovery();
let created_snapshot = {
let Some(context) = conn.browser_context_by_id_mut(browser_context_id) else {
return TargetPreparedOutputs::default();
};
context.insert_dedicated_worker_target(crate::conn::DedicatedWorkerTargetState::new(
owner_page.clone(),
info.owner_local_host_id,
info.instance_id,
target_id.clone(),
info.name,
owner_page_network_sessions.clone(),
));
should_emit_created
.then(|| context.devtools_target_info(&target_id))
.flatten()
};
if let Some(target_info) = created_snapshot {
outputs.push(WorkerTargetLifecycleOutput::DedicatedWorkerCreated {
browser_context_id: browser_context_id.to_owned(),
renderer_instance_id: info.instance_id,
target_delta: PreparedTargetHostDelta::created(target_id.clone(), Some(target_info)),
});
}
let timestamp = monotonic_timestamp_seconds();
for session_id in &owner_page_network_sessions {
let mut events = Vec::new();
network::emit_request_will_be_sent(
&mut events,
session_id.as_deref(),
&target_id,
&owner_target_id,
&owner_target_id,
timestamp,
&document_url,
&request_url,
"GET",
None,
&[],
DevToolsNetworkResourceType::Script,
SubresourceRequestInitiatorType::Other,
None,
false,
None,
&[],
);
if !events.is_empty() {
outputs.push(WorkerTargetLifecycleOutput::DedicatedWorkerEvents {
browser_context_id: browser_context_id.to_owned(),
renderer_instance_id: info.instance_id,
target_id: target_id.clone(),
events,
});
}
}
outputs
}
pub(super) fn record_dedicated_worker_main_script(
conn: &mut CdpConnection,
browser_context_id: &str,
renderer_instance_id: u64,
script_url: String,
outcome: crate::conn::DedicatedWorkerMainScriptOutcome,
) -> TargetPreparedOutputs {
let mut outputs = TargetPreparedOutputs::default();
let Some(owner_page) = conn
.browser_context_by_id(browser_context_id)
.and_then(|context| context.dedicated_worker_targets.get(&renderer_instance_id))
.map(|target| target.owner_page.clone())
else {
return outputs;
};
let auto_attach_owners = dedicated_worker_auto_attach_owner_sessions(conn, &owner_page);
let attached_sessions = auto_attach_owners
.into_iter()
.map(|owner| {
let waiting = conn.auto_attach_owner_waits_for_debugger_on_start(owner.as_deref());
(owner, conn.gen_session_id(), waiting)
})
.collect::<Vec<_>>();
let pause_failed_target_until_debugger_resume = attached_sessions
.iter()
.any(|(_, _, waiting_for_debugger)| *waiting_for_debugger);
let should_emit_info_changed = conn.has_any_target_discovery();
let (target_id, page_extra_events) = {
let Some(context) = conn.browser_context_by_id_mut(browser_context_id) else {
return outputs;
};
let Some(target) = context
.dedicated_worker_targets
.get_mut(&renderer_instance_id)
else {
return outputs;
};
let target_id = target.target_id.clone();
let owner_page_network_sessions = target.owner_page_network_sessions.clone();
let page_extra_events = dedicated_worker_main_script_page_extra_events(
&target_id,
&owner_page_network_sessions,
&outcome,
);
target.record_main_script(
script_url,
outcome,
pause_failed_target_until_debugger_resume,
);
(target_id, page_extra_events)
};
for (_session_id, events) in page_extra_events {
outputs.push(WorkerTargetLifecycleOutput::DedicatedWorkerEvents {
browser_context_id: browser_context_id.to_owned(),
renderer_instance_id,
target_id: target_id.clone(),
events,
});
}
let mut prepared_attaches = Vec::new();
for (owner_session_id, session_id, waiting_for_debugger) in attached_sessions {
let Some(target_info) = conn
.prepare_auto_attached_dedicated_worker_session_binding_info_in_browser_context(
browser_context_id,
&target_id,
session_id.clone(),
)
else {
continue;
};
let prepared_session = TargetAttachSessionCommit::auto_attached(
session_id.clone(),
owner_session_id,
CdpSessionRoute::DedicatedWorkerTarget {
browser_context_id: browser_context_id.to_owned(),
target_id: target_id.clone(),
},
waiting_for_debugger,
);
if waiting_for_debugger
&& let Some(context) = conn.browser_context_by_id_mut(browser_context_id)
&& let Some(target) = context
.dedicated_worker_targets
.get_mut(&renderer_instance_id)
{
target.allow_main_script_network_replay_to(&session_id);
}
prepared_attaches.push(WorkerTargetLifecycleOutput::DedicatedWorkerAttached {
browser_context_id: browser_context_id.to_owned(),
renderer_instance_id,
target_id: target_id.clone(),
session_id,
prepared_attach: PreparedTargetAttach::new(
target_id.clone(),
target_info,
[prepared_session],
),
});
}
let changed_snapshot = should_emit_info_changed
.then(|| {
conn.browser_context_by_id(browser_context_id)
.and_then(|context| context.devtools_target_info(&target_id))
})
.flatten();
if let Some(target_info) = changed_snapshot {
outputs.push(WorkerTargetLifecycleOutput::DedicatedWorkerInfoChanged {
browser_context_id: browser_context_id.to_owned(),
renderer_instance_id,
target_id: target_id.clone(),
target_delta: PreparedTargetHostDelta::info_changed(
target_id.clone(),
Some(target_info),
),
});
}
outputs
.worker_target_lifecycle_outputs
.extend(prepared_attaches);
let enabled_sessions = conn
.browser_context_by_id(browser_context_id)
.and_then(|context| context.dedicated_worker_targets.get(&renderer_instance_id))
.map(|target| {
target
.session_ids()
.into_iter()
.filter(|session_id| {
target.network_enabled(session_id)
&& !target.main_script_was_delivered_to(session_id)
})
.collect::<Vec<_>>()
})
.unwrap_or_default();
for session_id in enabled_sessions {
let events = conn
.browser_context_by_id(browser_context_id)
.and_then(|context| context.dedicated_worker_targets.get(&renderer_instance_id))
.and_then(|target| target.main_script())
.map(|script| {
dedicated_worker_main_script_worker_events(&target_id, Some(&session_id), script)
})
.unwrap_or_default();
if let Some(context) = conn.browser_context_by_id_mut(browser_context_id)
&& let Some(target) = context
.dedicated_worker_targets
.get_mut(&renderer_instance_id)
{
target.mark_main_script_delivered_to(&session_id);
}
if !events.is_empty() {
outputs.push(WorkerTargetLifecycleOutput::DedicatedWorkerEvents {
browser_context_id: browser_context_id.to_owned(),
renderer_instance_id,
target_id: target_id.clone(),
events,
});
}
}
outputs
}
pub(in crate::domains::target) fn dedicated_worker_auto_attach_owner_session_allowed(
conn: &CdpConnection,
owner_session_id: Option<&str>,
owner_page: &TargetPageResidenceIdentity,
) -> bool {
let Some(owner_session_id) = owner_session_id else {
return false;
};
conn.target_page_residence_identity_for_session(Some(owner_session_id))
.as_ref()
== Some(owner_page)
}
pub(super) fn shared_worker_auto_attach_owner_sessions(
conn: &CdpConnection,
) -> Vec<Option<String>> {
conn.auto_attach_owner_sessions_for_target_type("shared_worker")
.into_iter()
.filter(|owner_session_id| {
super::super::browser_level_auto_attach_owner_session_allowed(
conn,
owner_session_id.as_deref(),
)
})
.collect()
}
pub(super) fn service_worker_auto_attach_owner_sessions(
conn: &CdpConnection,
) -> Vec<Option<String>> {
conn.auto_attach_owner_sessions_for_target_type("service_worker")
.into_iter()
.filter(|owner_session_id| {
super::super::browser_level_auto_attach_owner_session_allowed(
conn,
owner_session_id.as_deref(),
)
})
.collect()
}
pub(super) fn dedicated_worker_auto_attach_owner_sessions(
conn: &CdpConnection,
owner_page: &TargetPageResidenceIdentity,
) -> Vec<Option<String>> {
conn.auto_attach_owner_sessions_for_target_type("worker")
.into_iter()
.filter(|owner_session_id| {
dedicated_worker_auto_attach_owner_session_allowed(
conn,
owner_session_id.as_deref(),
owner_page,
)
})
.collect()
}
pub(super) fn dedicated_worker_main_script_page_extra_events(
request_id: &str,
sessions: &[Option<String>],
outcome: &crate::conn::DedicatedWorkerMainScriptOutcome,
) -> Vec<(Option<String>, Vec<BackgroundProtocolEvent>)> {
let response = match outcome {
crate::conn::DedicatedWorkerMainScriptOutcome::Loaded(response) => Some(response.as_ref()),
crate::conn::DedicatedWorkerMainScriptOutcome::Failed { response, .. } => {
response.as_deref()
}
};
let Some(response) = response else {
return Vec::new();
};
let has_network_extra_info =
response.network_request_headers().is_some() || response.request_cookie_report.is_some();
if !has_network_extra_info {
return Vec::new();
}
let default_cookie_report = moli_cookie_jar::StoredCookieQueryReport::default();
let cookie_report = response
.request_cookie_report
.as_ref()
.unwrap_or(&default_cookie_report);
sessions
.iter()
.map(|session_id| {
let mut events = Vec::new();
network::emit_request_will_be_sent_extra_info(
&mut events,
session_id.as_deref(),
request_id,
response.network_request_headers().unwrap_or_default(),
cookie_report,
monotonic_timestamp_seconds(),
);
network::emit_response_received_extra_info(
&mut events,
session_id.as_deref(),
request_id,
&response.headers,
response.status,
&response.cookie_set_reports,
);
(session_id.clone(), events)
})
.collect()
}
pub(super) fn dedicated_worker_main_script_worker_events(
target_id: &str,
session_id: Option<&str>,
script: &crate::conn::DedicatedWorkerMainScriptSnapshot,
) -> Vec<BackgroundProtocolEvent> {
let mut events = Vec::new();
let timestamp = monotonic_timestamp_seconds();
let response = match &script.outcome {
crate::conn::DedicatedWorkerMainScriptOutcome::Loaded(response) => Some(response.as_ref()),
crate::conn::DedicatedWorkerMainScriptOutcome::Failed { response, .. } => {
response.as_deref()
}
};
if let Some(response) = response {
let has_extra_info = response.network_request_headers().is_some()
|| response.request_cookie_report.is_some();
network::emit_response_received_without_extra_info_event(
&mut events,
session_id,
target_id,
target_id,
target_id,
timestamp,
&response.final_url,
response.status,
None,
&response.headers,
response.body_bytes().len(),
response.from_cache,
response.negotiated_http_version,
has_extra_info,
DevToolsNetworkResourceType::Script,
);
}
match &script.outcome {
crate::conn::DedicatedWorkerMainScriptOutcome::Loaded(response) => {
network::emit_loading_finished(
&mut events,
session_id,
target_id,
target_id,
target_id,
timestamp,
response.body_bytes().len(),
DevToolsNetworkResourceType::Script,
);
}
crate::conn::DedicatedWorkerMainScriptOutcome::Failed { error_message, .. } => {
network::emit_loading_failed(
&mut events,
session_id,
target_id,
target_id,
target_id,
timestamp,
dedicated_worker_loading_error_text(error_message),
DevToolsNetworkResourceType::Script,
);
}
}
events
}
pub(super) fn dedicated_worker_loading_error_text(error_message: &str) -> &str {
error_message
.split_ascii_whitespace()
.find(|part| part.starts_with("net::ERR_"))
.map(|part| {
part.trim_end_matches(|character: char| {
!character.is_ascii_alphanumeric() && character != '_'
})
})
.unwrap_or("net::ERR_FAILED")
}
pub(in crate::domains) fn dedicated_worker_main_script_network_replay_for_session(
conn: &mut CdpConnection,
session_id: &str,
) -> Vec<BackgroundProtocolEvent> {
let Some(crate::conn::CdpSessionRoute::DedicatedWorkerTarget {
browser_context_id,
target_id,
}) = conn.session_route(Some(session_id))
else {
return Vec::new();
};
let Some(renderer_instance_id) = conn
.browser_context_by_id(&browser_context_id)
.and_then(|context| context.dedicated_worker_target(&target_id))
.map(|target| target.renderer_instance_id)
else {
return Vec::new();
};
let events = conn
.browser_context_by_id(&browser_context_id)
.and_then(|context| context.dedicated_worker_targets.get(&renderer_instance_id))
.filter(|target| {
target.network_enabled(session_id)
&& target.main_script_network_replay_allowed_for(session_id)
&& !target.main_script_was_delivered_to(session_id)
})
.and_then(|target| target.main_script())
.map(|script| {
dedicated_worker_main_script_worker_events(&target_id, Some(session_id), script)
})
.unwrap_or_default();
if events.is_empty() {
return events;
}
if let Some(context) = conn.browser_context_by_id_mut(&browser_context_id)
&& let Some(target) = context
.dedicated_worker_targets
.get_mut(&renderer_instance_id)
{
target.mark_main_script_delivered_to(session_id);
}
events
}
pub(in crate::domains) fn release_failed_dedicated_worker_target_after_debugger_resume(
conn: &mut CdpConnection,
session_id: Option<&str>,
) -> Option<Vec<BackgroundProtocolEvent>> {
let session_id = session_id?;
let crate::conn::CdpSessionRoute::DedicatedWorkerTarget {
browser_context_id,
target_id,
} = conn.session_route(Some(session_id))?
else {
return None;
};
let renderer_instance_id = {
let target = conn
.browser_context_by_id_mut(&browser_context_id)?
.dedicated_worker_target_mut(&target_id)?;
if !target.release_deferred_renderer_destroyed_for_debugger_resume() {
return None;
}
target.renderer_instance_id
};
let outputs = prepare_dedicated_worker_target_retirement(
conn,
&browser_context_id,
renderer_instance_id,
DedicatedWorkerRetirementCause::OwnerRetired,
);
Some(commit_failed_dedicated_worker_retirement_sync(
conn, outputs,
))
}
pub(in crate::domains) async fn retire_dedicated_worker_targets_for_replaced_page_async(
conn: &mut CdpConnection,
replaced_page_owner: &TargetPageResidenceIdentity,
) -> Vec<BackgroundProtocolEvent> {
let renderer_instance_ids = conn
.browser_context_by_id(replaced_page_owner.browser_context_id())
.map(|context| {
context
.dedicated_worker_targets
.iter()
.filter_map(|(renderer_instance_id, target)| {
(&target.owner_page == replaced_page_owner).then_some(*renderer_instance_id)
})
.collect::<Vec<_>>()
})
.unwrap_or_default();
let mut events = Vec::new();
for renderer_instance_id in renderer_instance_ids {
let outputs = prepare_dedicated_worker_target_retirement(
conn,
replaced_page_owner.browser_context_id(),
renderer_instance_id,
DedicatedWorkerRetirementCause::OwnerRetired,
);
for output in outputs.worker_target_lifecycle_outputs {
match commit_dedicated_worker_retirement_output_async(conn, output).await {
Ok(output_events) => events.extend(output_events),
Err(output) => {
debug_assert!(
false,
"Page replacement DedicatedWorker retirement contained non-terminal output: {output:?}"
);
}
}
}
}
events
}
pub(super) async fn commit_dedicated_worker_retirement_output_async(
conn: &mut CdpConnection,
output: WorkerTargetLifecycleOutput,
) -> Result<Vec<BackgroundProtocolEvent>, WorkerTargetLifecycleOutput> {
let mut events = Vec::new();
match output {
WorkerTargetLifecycleOutput::DedicatedWorkerDetached {
target_delta,
cleanup_plan,
} => {
if let Some(target_delta) = target_delta {
events.extend(
conn.prepared_target_info_changed_event_plan_for_discovery_owners(target_delta),
);
}
let mut response_events = Vec::new();
let outcome = super::super::session_disposal::dispose_dedicated_worker_session_after_prepared_state_delta_async(
conn,
&mut events,
&mut response_events,
cleanup_plan,
)
.await
.expect("retired dedicated-worker session cleanup should succeed");
let (event_plan, predecessor) = outcome.into_parts();
debug_assert!(
predecessor.is_none(),
"DedicatedWorker disposal cannot publish a Page renderer fence"
);
events.extend(response_events);
events.extend(event_plan);
}
WorkerTargetLifecycleOutput::DedicatedWorkerDestroyed {
browser_context_id,
renderer_instance_id,
target_id,
target_delta,
} => {
let removed = conn
.browser_context_by_id_mut(&browser_context_id)
.and_then(|context| {
let target = context
.dedicated_worker_targets
.get(&renderer_instance_id)?;
(target.target_id == target_id).then(|| {
context.remove_dedicated_worker_target_by_renderer_instance(
renderer_instance_id,
)
})
})
.flatten();
if removed.is_none() {
return Ok(events);
}
if let Some(target_delta) = target_delta {
events.extend(conn.prepared_target_host_delta_event_plan(target_delta));
}
}
output => return Err(output),
}
Ok(events)
}
pub(super) fn commit_failed_dedicated_worker_retirement_sync(
conn: &mut CdpConnection,
outputs: TargetPreparedOutputs,
) -> Vec<BackgroundProtocolEvent> {
let retirement_identity = outputs
.worker_target_lifecycle_outputs
.iter()
.find_map(|output| match output {
WorkerTargetLifecycleOutput::DedicatedWorkerDestroyed {
browser_context_id,
renderer_instance_id,
target_id,
..
} => Some((
browser_context_id.clone(),
*renderer_instance_id,
target_id.clone(),
)),
_ => None,
});
let Some((browser_context_id, renderer_instance_id, target_id)) = retirement_identity else {
return Vec::new();
};
let removed = conn
.browser_context_by_id_mut(&browser_context_id)
.and_then(|context| {
let target = context
.dedicated_worker_targets
.get(&renderer_instance_id)?;
(target.target_id == target_id).then(|| {
context.remove_dedicated_worker_target_by_renderer_instance(renderer_instance_id)
})
})
.flatten();
if removed.is_none() {
return Vec::new();
}
let mut events = Vec::new();
for output in outputs.worker_target_lifecycle_outputs {
match output {
WorkerTargetLifecycleOutput::DedicatedWorkerDetached {
target_delta,
cleanup_plan,
} => {
if let Some(target_delta) = target_delta {
events.extend(
conn.prepared_target_info_changed_event_plan_for_discovery_owners(
target_delta,
),
);
}
events.extend(
super::super::session_disposal::dispose_removed_dedicated_worker_session_after_failed_retirement(
conn,
cleanup_plan,
),
);
}
WorkerTargetLifecycleOutput::DedicatedWorkerDestroyed { target_delta, .. } => {
if let Some(target_delta) = target_delta {
events.extend(conn.prepared_target_host_delta_event_plan(target_delta));
}
}
output => {
debug_assert!(
false,
"failed DedicatedWorker retirement contained non-terminal output: {output:?}"
);
}
}
}
events
}
pub(super) fn record_dedicated_worker_target_runtime_inspector_messages(
conn: &CdpConnection,
browser_context_id: &str,
renderer_instance_id: u64,
inspector_session_id: Option<String>,
messages: Vec<RendererRuntimeInspectorMessage>,
) -> TargetPreparedOutputs {
let mut outputs = TargetPreparedOutputs::default();
let Some(target) = conn
.browser_context_by_id(browser_context_id)
.and_then(|context| context.dedicated_worker_targets.get(&renderer_instance_id))
else {
return outputs;
};
let target_id = target.target_id.clone();
let session_ids = if let Some(session_id) = inspector_session_id {
target
.is_session(&session_id)
.then_some(vec![session_id])
.unwrap_or_default()
} else {
target.session_ids()
};
for session_id in session_ids {
outputs.push(
WorkerTargetLifecycleOutput::DedicatedWorkerRuntimeInspectorMessages {
browser_context_id: browser_context_id.to_owned(),
renderer_instance_id,
target_id: target_id.clone(),
session_id,
messages: messages.clone(),
},
);
}
outputs
}
pub(super) fn record_dedicated_worker_target_console_message(
conn: &mut CdpConnection,
browser_context_id: &str,
renderer_instance_id: u64,
message: RendererSharedWorkerConsoleMessage,
) -> TargetPreparedOutputs {
let mut outputs = TargetPreparedOutputs::default();
let Some(context) = conn.browser_context_by_id_mut(browser_context_id) else {
return outputs;
};
let Some(target) = context
.dedicated_worker_targets
.get_mut(&renderer_instance_id)
else {
return outputs;
};
target.record_console_message(message);
let target_id = target.target_id.clone();
let console_end = target.console_message_count();
for session_id in target.session_ids() {
let console_messages = target.pending_console_domain_messages(&session_id).to_vec();
let runtime_messages = target
.pending_runtime_console_messages(&session_id)
.to_vec();
if console_messages.is_empty() && runtime_messages.is_empty() {
continue;
}
outputs.push(
WorkerTargetLifecycleOutput::DedicatedWorkerConsoleMessages {
browser_context_id: browser_context_id.to_owned(),
renderer_instance_id,
target_id: target_id.clone(),
session_id,
console_messages,
runtime_messages,
console_end,
},
);
}
outputs
}
pub(super) fn prepare_dedicated_worker_target_retirement(
conn: &mut CdpConnection,
browser_context_id: &str,
renderer_instance_id: u64,
cause: DedicatedWorkerRetirementCause,
) -> TargetPreparedOutputs {
let mut outputs = TargetPreparedOutputs::default();
if matches!(cause, DedicatedWorkerRetirementCause::RendererDestroyed)
&& conn
.browser_context_by_id_mut(browser_context_id)
.and_then(|context| {
context
.dedicated_worker_targets
.get_mut(&renderer_instance_id)
})
.is_some_and(|target| target.defer_renderer_destroyed_for_debugger_resume())
{
return outputs;
}
let target_id = match conn
.browser_context_by_id(browser_context_id)
.and_then(|context| {
context.dedicated_worker_target_id_for_renderer_instance(renderer_instance_id)
}) {
Some(target_id) => target_id.to_owned(),
None => return outputs,
};
let destroyed_delta = conn
.has_any_target_discovery()
.then(|| conn.prepare_destroyed_target_host_delta(&target_id))
.flatten();
let mut detached_delta = conn
.browser_context_by_id(browser_context_id)
.and_then(|context| {
let target = context.dedicated_worker_target(&target_id)?;
if !target.has_session() {
return None;
}
let mut target_info = context.devtools_target_info(&target_id)?;
target_info.attached = false;
Some(PreparedTargetHostDelta::info_changed(
target_id.clone(),
Some(target_info),
))
});
let Some(context) = conn.browser_context_by_id(browser_context_id) else {
return outputs;
};
let Some(target) = context.dedicated_worker_targets.get(&renderer_instance_id) else {
return outputs;
};
for session_id in target.session_ids() {
outputs.push(WorkerTargetLifecycleOutput::DedicatedWorkerDetached {
target_delta: detached_delta.take(),
cleanup_plan: TargetSessionDetachCleanupPlan::new(
target_id.clone(),
session_id,
None,
None,
),
});
}
outputs.push(WorkerTargetLifecycleOutput::DedicatedWorkerDestroyed {
browser_context_id: browser_context_id.to_owned(),
renderer_instance_id,
target_id,
target_delta: destroyed_delta,
});
outputs
}
@@ -0,0 +1,566 @@
use super::*;
pub(in crate::domains) async fn project_worker_target_output_async(
output: ProtocolOutputSlot,
conn: &mut CdpConnection,
context: &mut ProtocolOutputProjectionContext<'_>,
prepared_outputs: Option<&mut ProtocolOutputPayloads>,
) {
match output {
ProtocolOutputSlot::SharedWorkerTargetLifecycle
| ProtocolOutputSlot::ServiceWorkerTargetLifecycle
| ProtocolOutputSlot::DedicatedWorkerTargetLifecycle => {}
_ => panic!("non-Target output routed through the Target projector: {output:?}"),
}
emit_target_lifecycle_events(conn, context, prepared_outputs).await;
}
pub(super) async fn emit_target_lifecycle_events(
conn: &mut CdpConnection,
context: &mut ProtocolOutputProjectionContext<'_>,
prepared_outputs: Option<&mut ProtocolOutputPayloads>,
) {
let Some(events) = prepared_outputs
.and_then(ProtocolOutputPayloads::target_mut)
.and_then(TargetPreparedOutputSlot::take_worker_target_lifecycle_outputs)
else {
return;
};
let mut side_effects = events::TargetProtocolSideEffects::default();
for event in events {
let event = match commit_dedicated_worker_retirement_output_async(conn, event).await {
Ok(events) => {
side_effects.extend_background_events(events);
continue;
}
Err(event) => event,
};
match event {
WorkerTargetLifecycleOutput::DedicatedWorkerEvents {
browser_context_id,
renderer_instance_id,
target_id,
events,
} => {
if dedicated_worker_target_is_current(
conn,
&browser_context_id,
renderer_instance_id,
&target_id,
) {
side_effects.extend_background_events(events);
}
}
WorkerTargetLifecycleOutput::DedicatedWorkerConsoleMessages {
browser_context_id,
renderer_instance_id,
target_id,
session_id,
console_messages,
runtime_messages,
console_end,
} => {
if !dedicated_worker_target_is_current(
conn,
&browser_context_id,
renderer_instance_id,
&target_id,
) || !conn
.browser_context_by_id(&browser_context_id)
.and_then(|context| context.dedicated_worker_targets.get(&renderer_instance_id))
.is_some_and(|target| target.is_session(&session_id))
{
continue;
}
side_effects.extend_background_events(console_message_added_events(
&session_id,
&console_messages,
));
side_effects.extend_background_events(runtime_console_api_called_events(
&session_id,
&runtime_messages,
));
if let Some(target) = conn
.browser_context_by_id_mut(&browser_context_id)
.and_then(|context| {
context
.dedicated_worker_targets
.get_mut(&renderer_instance_id)
})
.filter(|target| target.target_id == target_id)
{
if !console_messages.is_empty() {
target.mark_console_domain_emitted(&session_id, console_end);
}
if !runtime_messages.is_empty() {
target.mark_runtime_console_emitted(&session_id, console_end);
}
}
}
WorkerTargetLifecycleOutput::DedicatedWorkerCreated {
browser_context_id,
renderer_instance_id,
target_delta,
} => {
if dedicated_worker_target_is_current(
conn,
&browser_context_id,
renderer_instance_id,
target_delta.target_id(),
) {
side_effects.extend_background_events(
conn.prepared_target_host_delta_event_plan(target_delta),
);
}
}
WorkerTargetLifecycleOutput::DedicatedWorkerInfoChanged {
browser_context_id,
renderer_instance_id,
target_id,
target_delta,
} => {
if dedicated_worker_target_is_current(
conn,
&browser_context_id,
renderer_instance_id,
&target_id,
) {
side_effects.extend_background_events(
conn.prepared_target_host_delta_event_plan(target_delta),
);
}
}
WorkerTargetLifecycleOutput::DedicatedWorkerAttached {
browser_context_id,
renderer_instance_id,
target_id,
session_id,
prepared_attach,
} => {
let current = conn
.browser_context_by_id(&browser_context_id)
.and_then(|context| context.dedicated_worker_targets.get(&renderer_instance_id))
.is_some_and(|target| {
target.target_id == target_id && target.is_session(&session_id)
});
if current {
side_effects.extend_background_events(
conn.commit_prepared_dedicated_worker_attach_event_plan(prepared_attach),
);
}
}
WorkerTargetLifecycleOutput::DedicatedWorkerDetached { .. }
| WorkerTargetLifecycleOutput::DedicatedWorkerDestroyed { .. } => {
unreachable!("DedicatedWorker retirement outputs are committed before projection")
}
WorkerTargetLifecycleOutput::SharedWorkerAttachmentEvents { attachment, events } => {
if attachment.is_current() {
side_effects.extend_background_events(events);
}
}
WorkerTargetLifecycleOutput::SharedWorkerCreated { target_delta } => {
side_effects.extend_background_events(
conn.prepared_target_host_delta_event_plan(target_delta),
);
}
WorkerTargetLifecycleOutput::SharedWorkerAttached {
attachment,
prepared_attach,
} => {
if !attachment.is_current() {
continue;
}
side_effects.extend_background_events(
conn.commit_prepared_attach_event_plan(prepared_attach),
);
}
WorkerTargetLifecycleOutput::ServiceWorkerVersionEvents { version, events } => {
if version.is_current() {
side_effects.extend_background_events(events);
}
}
WorkerTargetLifecycleOutput::ServiceWorkerAttachmentEvents { attachment, events } => {
if attachment.is_current() {
side_effects.extend_background_events(events);
}
}
WorkerTargetLifecycleOutput::ServiceWorkerRunEvents { run, events } => {
if run.is_current() {
side_effects.extend_background_events(events);
}
}
WorkerTargetLifecycleOutput::ServiceWorkerRuntimeEvents { runtime, events } => {
if runtime.is_current() {
side_effects.extend_background_events(events);
}
}
WorkerTargetLifecycleOutput::ServiceWorkerCreated {
version,
target_delta,
} => {
if version.is_current() {
side_effects.extend_background_events(
conn.prepared_target_host_delta_event_plan(target_delta),
);
}
}
WorkerTargetLifecycleOutput::ServiceWorkerAttached {
attachment,
prepared_attach,
} => {
if attachment.is_current() {
side_effects.extend_background_events(
conn.commit_prepared_attach_event_plan(prepared_attach),
);
}
}
WorkerTargetLifecycleOutput::SharedWorkerDetached {
retirement,
cleanup_plan,
} => {
if !retirement.is_current() {
continue;
}
assert_eq!(
cleanup_plan.target_id(),
retirement.identity().target_id(),
"shared-worker detach plan must retain its exact target"
);
assert_eq!(
cleanup_plan.session_id(),
retirement.identity().session_id(),
"shared-worker detach plan must retain its exact attachment"
);
let event_plan =
super::super::session_disposal::dispose_removed_worker_session_async(
conn,
cleanup_plan,
)
.await
.expect("retired shared-worker session cleanup should succeed");
side_effects.extend_background_events(event_plan);
retirement.retire();
}
WorkerTargetLifecycleOutput::ServiceWorkerDetached {
retirement,
cleanup_plan,
} => {
if !retirement.is_current() {
continue;
}
assert_eq!(
cleanup_plan.target_id(),
retirement.identity().target_id(),
"service-worker detach plan must retain its exact version target"
);
assert_eq!(
cleanup_plan.session_id(),
retirement.identity().session_id(),
"service-worker detach plan must retain its exact attachment"
);
let event_plan =
super::super::session_disposal::dispose_removed_worker_session_async(
conn,
cleanup_plan,
)
.await
.expect("retired service-worker session cleanup should succeed");
side_effects.extend_background_events(event_plan);
retirement.retire();
}
WorkerTargetLifecycleOutput::SharedWorkerDestroyed { target_delta } => {
side_effects.extend_background_events(
conn.prepared_target_host_delta_event_plan(target_delta),
);
}
WorkerTargetLifecycleOutput::ServiceWorkerRunRetired { retirement } => {
assert!(
retirement.is_current(),
"service-worker run retirement must be consumed exactly once in source order"
);
retirement.retire();
}
WorkerTargetLifecycleOutput::ServiceWorkerDestroyed {
retirement,
target_delta,
} => {
assert!(
retirement.is_current(),
"service-worker version retirement must be consumed exactly once"
);
if let Some(target_delta) = target_delta {
assert_eq!(
target_delta.target_id(),
retirement.identity().target_id(),
"service-worker destruction must retain its exact version target"
);
side_effects.extend_background_events(
conn.prepared_target_host_delta_event_plan(target_delta),
);
}
retirement.retire();
}
WorkerTargetLifecycleOutput::ServiceWorkerConsoleMessages {
runtime,
messages,
console_end: _,
} => {
if runtime.is_current() {
side_effects.extend_background_events(console_message_added_events(
runtime.session_id(),
&messages,
));
}
}
WorkerTargetLifecycleOutput::SharedWorkerConsoleMessages {
attachment,
messages,
console_end,
} => {
if !attachment.is_current() {
continue;
}
side_effects.extend_background_events(console_message_added_events(
attachment.session_id(),
&messages,
));
mark_exact_shared_worker_console_domain_emitted(conn, &attachment, console_end);
}
WorkerTargetLifecycleOutput::ServiceWorkerRuntimeConsoleMessages {
runtime,
messages,
console_end: _,
} => {
if runtime.is_current() {
side_effects.extend_background_events(runtime_console_api_called_events(
runtime.session_id(),
&messages,
));
}
}
WorkerTargetLifecycleOutput::SharedWorkerRuntimeConsoleMessages {
attachment,
messages,
console_end,
} => {
if !attachment.is_current() {
continue;
}
side_effects.extend_background_events(runtime_console_api_called_events(
attachment.session_id(),
&messages,
));
mark_exact_shared_worker_runtime_console_emitted(conn, &attachment, console_end);
}
WorkerTargetLifecycleOutput::ServiceWorkerRuntimeExceptionMessages {
runtime,
messages,
exception_start,
exception_end: _,
} => {
if runtime.is_current() {
side_effects.extend_background_events(runtime_exception_thrown_events(
runtime.session_id(),
&messages,
exception_start,
));
}
}
WorkerTargetLifecycleOutput::ServiceWorkerFetchDiagnostics {
runtime,
diagnostics,
diagnostic_start,
diagnostic_end: _,
} => {
if runtime.is_current() {
side_effects.extend_background_events(service_worker_fetch_diagnostic_events(
runtime.session_id(),
runtime.target_id(),
&diagnostics,
diagnostic_start,
));
}
}
WorkerTargetLifecycleOutput::ServiceWorkerRuntimeInspectorMessages {
runtime,
background_events,
response_events,
pending_runtime_console,
pending_runtime_exceptions,
} => {
if !runtime.is_current() {
continue;
}
let session_id = runtime.session_id();
side_effects.extend_background_events(background_events);
if service_worker_runtime_is_registry_current(conn, &runtime) {
replay_shared_worker_runtime_bindings_for_session_async(conn, Some(session_id))
.await;
}
side_effects.extend_background_events(response_events);
if let Some((messages, _console_end)) = pending_runtime_console {
side_effects.extend_background_events(runtime_console_api_called_events(
session_id, &messages,
));
}
if let Some((messages, exception_start, _exception_end)) =
pending_runtime_exceptions
{
side_effects.extend_background_events(runtime_exception_thrown_events(
session_id,
&messages,
exception_start,
));
}
}
WorkerTargetLifecycleOutput::SharedWorkerRuntimeInspectorMessages {
attachment,
messages,
} => {
if !attachment.is_current() {
continue;
}
let session_id = attachment.session_id();
let mut response_events = Vec::new();
let mut background_events = Vec::new();
let current_response_seen = route_worker_runtime_inspector_messages_into(
conn,
messages,
session_id,
&mut response_events,
&mut background_events,
);
debug_assert!(!current_response_seen);
side_effects.extend_background_events(background_events);
let pending_runtime_console =
exact_shared_worker_pending_runtime_console(conn, &attachment);
replay_shared_worker_runtime_bindings_for_session_async(conn, Some(session_id))
.await;
side_effects.extend_background_events(response_events);
if let Some((messages, console_end)) = pending_runtime_console {
side_effects.extend_background_events(runtime_console_api_called_events(
session_id, &messages,
));
mark_exact_shared_worker_runtime_console_emitted(
conn,
&attachment,
console_end,
);
}
}
WorkerTargetLifecycleOutput::DedicatedWorkerRuntimeInspectorMessages {
browser_context_id,
renderer_instance_id,
target_id,
session_id,
messages,
} => {
if !dedicated_worker_target_is_current(
conn,
&browser_context_id,
renderer_instance_id,
&target_id,
) {
continue;
}
let mut response_events = Vec::new();
let mut background_events = Vec::new();
let current_response_seen = route_worker_runtime_inspector_messages_into(
conn,
messages,
&session_id,
&mut response_events,
&mut background_events,
);
debug_assert!(!current_response_seen);
side_effects.extend_background_events(background_events);
replay_shared_worker_runtime_bindings_for_session_async(conn, Some(&session_id))
.await;
side_effects.extend_background_events(response_events);
}
}
}
for event in side_effects.into_background_events() {
context.command.push_protocol_event(event);
}
}
pub(super) fn route_worker_runtime_inspector_messages_into(
conn: &mut CdpConnection,
messages: Vec<RendererRuntimeInspectorMessage>,
session_id: &str,
response_events: &mut Vec<BackgroundProtocolEvent>,
background_events: &mut Vec<BackgroundProtocolEvent>,
) -> bool {
let owner = crate::conn::CommandOwnerScope::for_session(session_id);
conn.route_renderer_runtime_inspector_messages_for_owner_with_background_events_into(
messages,
None,
&owner,
response_events,
background_events,
)
}
pub(super) fn console_message_added_events(
session_id: &str,
messages: &[RuntimeConsoleMessageSnapshot],
) -> Vec<BackgroundProtocolEvent> {
messages
.iter()
.map(|message| {
let (level, text) = console_message_level_and_text(&message.message);
console_message_added_background_event(Some(session_id), "console-api", level, text, "")
})
.collect()
}
pub(super) fn runtime_console_api_called_events(
session_id: &str,
messages: &[RuntimeConsoleMessageSnapshot],
) -> Vec<BackgroundProtocolEvent> {
let base_timestamp = monotonic_timestamp_seconds();
messages
.iter()
.enumerate()
.map(|(index, message)| {
let (console_type, text) = runtime_console_message_type_and_text(&message.message);
runtime_console_api_called_background_event(
Some(session_id),
None,
console_type,
text,
&message.args,
message.stack.as_deref(),
message.execution_context_id,
base_timestamp + ((index + 1) as f64 * 0.000_001),
)
})
.collect()
}
pub(super) fn runtime_exception_thrown_events(
session_id: &str,
messages: &[ServiceWorkerRuntimeExceptionSnapshot],
exception_start: usize,
) -> Vec<BackgroundProtocolEvent> {
let base_timestamp = monotonic_timestamp_seconds();
messages
.iter()
.enumerate()
.map(|(offset, message)| {
let exception_index = exception_start + offset;
runtime_exception_thrown_background_event(
Some(session_id),
None,
&message.message.message,
&message.message.filename,
message.execution_context_id,
exception_index,
base_timestamp + ((offset + 1) as f64 * 0.000_001),
Some(u64::from(message.message.lineno.saturating_sub(1))),
Some(u64::from(message.message.colno.saturating_sub(1))),
)
})
.collect()
}
@@ -0,0 +1,362 @@
use super::*;
pub(super) fn push_service_worker_version_events(
outputs: &mut TargetPreparedOutputs,
version: TargetServiceWorkerVersionIdentity,
events: Vec<BackgroundProtocolEvent>,
) {
if events.is_empty() {
return;
}
outputs.push(WorkerTargetLifecycleOutput::ServiceWorkerVersionEvents { version, events });
}
pub(super) fn push_service_worker_run_events(
outputs: &mut TargetPreparedOutputs,
run: TargetServiceWorkerRunIdentity,
events: Vec<BackgroundProtocolEvent>,
) {
if events.is_empty() {
return;
}
outputs.push(WorkerTargetLifecycleOutput::ServiceWorkerRunEvents { run, events });
}
pub(super) fn push_service_worker_attachment_events(
outputs: &mut TargetPreparedOutputs,
attachment: TargetServiceWorkerProtocolAttachmentIdentity,
events: Vec<BackgroundProtocolEvent>,
) {
if events.is_empty() {
return;
}
outputs.push(WorkerTargetLifecycleOutput::ServiceWorkerAttachmentEvents { attachment, events });
}
pub(super) fn push_service_worker_runtime_events(
outputs: &mut TargetPreparedOutputs,
runtime: TargetServiceWorkerRuntimeAttachmentIdentity,
events: Vec<BackgroundProtocolEvent>,
) {
if events.is_empty() {
return;
}
outputs.push(WorkerTargetLifecycleOutput::ServiceWorkerRuntimeEvents { runtime, events });
}
pub(super) fn shared_worker_target_lifecycle_outputs_for_events(
conn: &mut CdpConnection,
browser_context_id: String,
events: Vec<RendererSharedWorkerTargetEvent>,
) -> TargetPreparedOutputs {
let mut outputs = TargetPreparedOutputs::default();
for event in events {
match event {
RendererSharedWorkerTargetEvent::Created(info) => {
let owner_target_id =
conn.browser_context_by_id(&browser_context_id)
.and_then(|context| {
context.target_id_for_renderer_owner_local_host_id(
info.owner_local_host_id,
)
});
outputs.extend(register_shared_worker_target(
conn,
&browser_context_id,
owner_target_id,
info,
));
}
RendererSharedWorkerTargetEvent::Destroyed { instance_id } => {
outputs.extend(remove_shared_worker_target(
conn,
&browser_context_id,
instance_id,
));
}
RendererSharedWorkerTargetEvent::Console {
instance_id,
message,
} => {
outputs.extend(record_shared_worker_target_console_message(
conn,
&browser_context_id,
instance_id,
message,
));
}
RendererSharedWorkerTargetEvent::RuntimeInspectorMessages {
instance_id,
inspector_session_id,
messages,
} => {
outputs.extend(record_shared_worker_target_runtime_inspector_messages(
conn,
&browser_context_id,
instance_id,
inspector_session_id,
messages,
));
}
}
}
outputs
}
pub(in crate::domains) fn shared_worker_target_lifecycle_prepared_outputs_for_event(
conn: &mut CdpConnection,
browser_context_id: String,
event: RendererSharedWorkerTargetEvent,
) -> TargetPreparedOutputs {
shared_worker_target_lifecycle_outputs_for_events(conn, browser_context_id, vec![event])
}
pub(in crate::domains) fn service_worker_target_lifecycle_prepared_outputs_for_event(
conn: &mut CdpConnection,
browser_context_id: String,
event: RendererServiceWorkerTargetEvent,
) -> TargetPreparedOutputs {
service_worker_target_lifecycle_outputs_for_events(conn, browser_context_id, vec![event])
}
pub(in crate::domains) fn dedicated_worker_target_lifecycle_prepared_outputs_for_event(
conn: &mut CdpConnection,
owner: &CommandOwnerScope,
event: RendererDedicatedWorkerTargetEvent,
) -> TargetPreparedOutputs {
let Some(owner_page) = conn.target_page_residence_identity_for_owner(owner) else {
return TargetPreparedOutputs::default();
};
let Some(owner_renderer_page) = conn.renderer_page_residence_identity_for_owner(owner) else {
return TargetPreparedOutputs::default();
};
let browser_context_id = owner_page.browser_context_id().to_owned();
let owner_page_network_sessions = conn.network_event_session_ids_for_owner(owner);
dedicated_worker_target_lifecycle_outputs_for_events(
conn,
browser_context_id,
owner_page,
owner_renderer_page,
owner_page_network_sessions,
vec![event],
)
}
pub(super) fn dedicated_worker_target_lifecycle_outputs_for_events(
conn: &mut CdpConnection,
browser_context_id: String,
owner_page: TargetPageResidenceIdentity,
owner_renderer_page: RendererPageResidenceIdentity,
owner_page_network_sessions: Vec<Option<String>>,
events: Vec<RendererDedicatedWorkerTargetEvent>,
) -> TargetPreparedOutputs {
let mut outputs = TargetPreparedOutputs::default();
for event in events {
match event {
RendererDedicatedWorkerTargetEvent::Created(info) => {
outputs.extend(register_dedicated_worker_target(
conn,
&browser_context_id,
owner_page.clone(),
owner_renderer_page,
owner_page_network_sessions.clone(),
info,
));
}
RendererDedicatedWorkerTargetEvent::ScriptLoaded {
instance_id,
script_url,
response,
} => {
outputs.extend(record_dedicated_worker_main_script(
conn,
&browser_context_id,
instance_id,
script_url,
crate::conn::DedicatedWorkerMainScriptOutcome::Loaded(response),
));
}
RendererDedicatedWorkerTargetEvent::ScriptLoadFailed {
instance_id,
script_url,
error_message,
response,
} => {
outputs.extend(record_dedicated_worker_main_script(
conn,
&browser_context_id,
instance_id,
script_url,
crate::conn::DedicatedWorkerMainScriptOutcome::Failed {
error_message,
response,
},
));
}
RendererDedicatedWorkerTargetEvent::Console {
instance_id,
message,
} => {
outputs.extend(record_dedicated_worker_target_console_message(
conn,
&browser_context_id,
instance_id,
message,
));
}
RendererDedicatedWorkerTargetEvent::RuntimeInspectorMessages {
instance_id,
inspector_session_id,
messages,
} => {
outputs.extend(record_dedicated_worker_target_runtime_inspector_messages(
conn,
&browser_context_id,
instance_id,
inspector_session_id,
messages,
));
}
RendererDedicatedWorkerTargetEvent::Destroyed { instance_id } => {
outputs.extend(prepare_dedicated_worker_target_retirement(
conn,
&browser_context_id,
instance_id,
DedicatedWorkerRetirementCause::RendererDestroyed,
));
}
}
}
outputs
}
pub(super) fn service_worker_target_lifecycle_outputs_for_events(
conn: &mut CdpConnection,
browser_context_id: String,
events: Vec<RendererServiceWorkerTargetEvent>,
) -> TargetPreparedOutputs {
let mut outputs = TargetPreparedOutputs::default();
for event in events {
match event {
RendererServiceWorkerTargetEvent::Created { info, active_run } => {
outputs.extend(register_service_worker_target_with_active_run(
conn,
&browser_context_id,
info,
active_run,
));
}
RendererServiceWorkerTargetEvent::Started { version_id, run } => {
outputs.extend(record_service_worker_target_started(
conn,
&browser_context_id,
version_id,
run,
));
}
RendererServiceWorkerTargetEvent::Stopped {
version_id,
run,
reason,
} => {
outputs.extend(record_service_worker_target_stopped(
conn,
&browser_context_id,
version_id,
run,
reason,
));
}
RendererServiceWorkerTargetEvent::Destroyed {
version_id,
active_run,
} => {
outputs.extend(remove_service_worker_target(
conn,
&browser_context_id,
version_id,
active_run,
));
}
RendererServiceWorkerTargetEvent::VersionUpdated { version_id, status } => {
outputs.extend(record_service_worker_target_version_updated(
conn,
&browser_context_id,
version_id,
status,
));
}
RendererServiceWorkerTargetEvent::Console {
version_id,
run,
message,
} => {
outputs.extend(record_service_worker_target_console_message(
conn,
&browser_context_id,
version_id,
run,
message,
));
}
RendererServiceWorkerTargetEvent::Exception {
version_id,
run,
message,
} => {
outputs.extend(record_service_worker_target_exception_message(
conn,
&browser_context_id,
version_id,
run,
message,
));
}
RendererServiceWorkerTargetEvent::FetchDiagnostic {
version_id,
run,
diagnostic,
} => {
outputs.extend(record_service_worker_target_fetch_diagnostic(
conn,
&browser_context_id,
version_id,
run,
diagnostic,
));
}
RendererServiceWorkerTargetEvent::RuntimeInspectorMessages {
version_id,
run,
inspector_session_id,
messages,
} => {
outputs.extend(record_service_worker_target_runtime_inspector_messages(
conn,
&browser_context_id,
version_id,
run,
inspector_session_id,
messages,
));
}
}
}
outputs
}
pub(super) fn append_service_worker_domain_snapshot(
conn: &CdpConnection,
browser_context_id: &str,
version: TargetServiceWorkerVersionIdentity,
outputs: &mut TargetPreparedOutputs,
) {
let session_ids =
service_worker::enabled_sessions_for_browser_context(conn, browser_context_id);
if session_ids.is_empty() {
return;
}
let events =
service_worker::snapshot_events_for_browser_context(conn, browser_context_id, &session_ids);
push_service_worker_version_events(outputs, version, events);
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,433 @@
use super::*;
pub(super) fn register_shared_worker_target(
conn: &mut CdpConnection,
browser_context_id: &str,
owner_target_id: Option<String>,
info: RendererSharedWorkerTargetInfo,
) -> TargetPreparedOutputs {
let mut outputs = TargetPreparedOutputs::default();
if conn
.browser_context_by_id(browser_context_id)
.and_then(|context| context.shared_worker_target_id_for_renderer_instance(info.instance_id))
.is_some()
{
return outputs;
}
let target_id = conn.gen_target_id();
let should_emit_created = conn.has_any_target_discovery();
let auto_attach_owners = shared_worker_auto_attach_owner_sessions(conn);
let attached_sessions = auto_attach_owners
.iter()
.map(|owner| {
(
owner.clone(),
conn.gen_session_id(),
conn.auto_attach_owner_waits_for_debugger_on_start(owner.as_deref()),
)
})
.collect::<Vec<_>>();
let created_snapshot = {
let Some(context) = conn.browser_context_by_id_mut(browser_context_id) else {
return outputs;
};
context.insert_shared_worker_target(SharedWorkerTargetState::new(
info.owner_local_host_id,
info.instance_id,
target_id.clone(),
owner_target_id,
info.url,
info.name,
));
if should_emit_created {
let snapshot = context.devtools_target_info(&target_id);
debug_assert!(snapshot.is_some());
snapshot
} else {
None
}
};
let mut attached_outputs = Vec::new();
for (owner_session_id, session_id, waiting_for_debugger) in attached_sessions {
if let Some(target_info) = conn
.prepare_auto_attached_shared_worker_session_binding_info_in_browser_context(
browser_context_id,
&target_id,
session_id.clone(),
)
{
let attachment = conn
.browser_context_by_id(browser_context_id)
.and_then(|context| context.shared_worker_target(&target_id))
.and_then(|target| {
target.protocol_attachment_identity(browser_context_id, &session_id)
})
.expect("new shared-worker session must expose its exact attachment identity");
let prepared_session = TargetAttachSessionCommit::auto_attached(
session_id,
owner_session_id,
CdpSessionRoute::SharedWorkerTarget {
browser_context_id: browser_context_id.to_owned(),
target_id: target_id.clone(),
},
waiting_for_debugger,
);
assert!(
matches!(
prepared_session.route(),
CdpSessionRoute::SharedWorkerTarget {
browser_context_id: route_browser_context_id,
target_id: route_target_id,
} if route_browser_context_id == browser_context_id
&& route_target_id == &target_id
),
"shared-worker auto-attach must freeze its exact target route at capture"
);
attached_outputs.push((
attachment,
PreparedTargetAttach::new(target_id.clone(), target_info, [prepared_session]),
));
}
}
if let Some(target_info) = created_snapshot {
outputs.push(WorkerTargetLifecycleOutput::SharedWorkerCreated {
target_delta: PreparedTargetHostDelta::created(target_id.clone(), Some(target_info)),
});
}
for (attachment, prepared_attach) in attached_outputs {
outputs.push(WorkerTargetLifecycleOutput::SharedWorkerAttached {
attachment,
prepared_attach,
});
}
outputs
}
pub(super) fn remove_shared_worker_target(
conn: &mut CdpConnection,
browser_context_id: &str,
renderer_instance_id: SharedWorkerInstanceId,
) -> TargetPreparedOutputs {
remove_shared_worker_target_with_reason(
conn,
browser_context_id,
renderer_instance_id,
"Target closed",
)
}
pub(super) fn remove_shared_worker_target_with_reason(
conn: &mut CdpConnection,
browser_context_id: &str,
renderer_instance_id: SharedWorkerInstanceId,
reason: &'static str,
) -> TargetPreparedOutputs {
let mut outputs = TargetPreparedOutputs::default();
let should_emit_destroyed = conn.has_any_target_discovery();
let target_id = {
let Some(context) = conn.browser_context_by_id(browser_context_id) else {
return outputs;
};
let Some(target_id) = context
.shared_worker_target_id_for_renderer_instance(renderer_instance_id)
.map(str::to_owned)
else {
return outputs;
};
target_id
};
let destroyed_delta = should_emit_destroyed
.then(|| conn.prepare_destroyed_target_host_delta(&target_id))
.flatten();
let Some(context) = conn.browser_context_by_id_mut(browser_context_id) else {
return outputs;
};
let Some(mut target) =
context.remove_shared_worker_target_by_renderer_instance(renderer_instance_id)
else {
return outputs;
};
let session_ids = target.session_ids();
let mut pending_await_direct_outputs = Vec::new();
let mut pending_await_claimed_outputs = Vec::new();
for session_id in &session_ids {
let attachment = target
.protocol_attachment_identity(browser_context_id, session_id)
.expect("removed shared-worker session must retain its exact attachment identity");
let mut pending_await_direct_events = Vec::new();
let mut pending_await_claimed_events = Vec::new();
conn.fail_pending_inspector_awaits_for_session_owner_background_events_into(
&mut pending_await_direct_events,
&mut pending_await_claimed_events,
Some(session_id),
reason,
);
CdpConnection::fail_pending_inspector_awaits_from_shared_worker_target_session_background_events_into(
&mut pending_await_direct_events,
&mut target,
session_id,
reason,
);
if !pending_await_direct_events.is_empty() {
pending_await_direct_outputs.push((attachment.clone(), pending_await_direct_events));
}
if !pending_await_claimed_events.is_empty() {
pending_await_claimed_outputs.push((attachment, pending_await_claimed_events));
}
}
for (attachment, events) in pending_await_direct_outputs
.into_iter()
.chain(pending_await_claimed_outputs)
{
outputs
.push(WorkerTargetLifecycleOutput::SharedWorkerAttachmentEvents { attachment, events });
}
for session_id in session_ids {
let retirement = target
.take_protocol_attachment_retirement(browser_context_id, &session_id)
.expect("removed shared-worker session must transfer its attachment scope");
outputs.push(WorkerTargetLifecycleOutput::SharedWorkerDetached {
cleanup_plan: TargetSessionDetachCleanupPlan::new(
target_id.clone(),
session_id,
None,
None,
),
retirement,
});
}
if let Some(target_delta) = destroyed_delta {
outputs.push(WorkerTargetLifecycleOutput::SharedWorkerDestroyed { target_delta });
}
outputs
}
pub(in crate::domains::target) async fn close_shared_worker_target_for_target_close_async(
conn: &mut CdpConnection,
target_id: &str,
command_context: &mut crate::conn::CommandDispatchContext,
) -> bool {
let Some((browser_context_id, renderer_runtime, instance_id)) =
conn.browser_context.as_ref().and_then(|context| {
let target = context.shared_worker_target(target_id)?;
Some((
context.id.clone(),
context.renderer_runtime(),
target.renderer_instance_id,
))
})
else {
return false;
};
renderer_runtime.close_shared_worker_for_target_close(instance_id);
let outputs = remove_shared_worker_target(conn, &browser_context_id, instance_id);
let mut prepared_outputs =
ProtocolOutputPayloads::from_slot(TargetPreparedOutputSlot::from_outputs(outputs));
let owner = CommandOwnerScope::capture(conn, None);
emit_target_lifecycle_events(
conn,
&mut ProtocolOutputProjectionContext::new(&owner, command_context),
Some(&mut prepared_outputs),
)
.await;
true
}
pub(in crate::domains::target) async fn close_dedicated_worker_target_for_target_close_async(
conn: &mut CdpConnection,
target_id: &str,
command_context: &mut crate::conn::CommandDispatchContext,
) -> bool {
let Some((browser_context_id, renderer_runtime, instance_id)) =
conn.browser_context.as_ref().and_then(|context| {
let target = context.dedicated_worker_target(target_id)?;
Some((
context.id.clone(),
context.renderer_runtime(),
target.renderer_instance_id,
))
})
else {
return false;
};
renderer_runtime.close_dedicated_worker_for_devtools(instance_id);
let outputs = prepare_dedicated_worker_target_retirement(
conn,
&browser_context_id,
instance_id,
DedicatedWorkerRetirementCause::OwnerRetired,
);
let mut prepared_outputs =
ProtocolOutputPayloads::from_slot(TargetPreparedOutputSlot::from_outputs(outputs));
let owner = CommandOwnerScope::capture(conn, None);
emit_target_lifecycle_events(
conn,
&mut ProtocolOutputProjectionContext::new(&owner, command_context),
Some(&mut prepared_outputs),
)
.await;
true
}
pub(super) fn record_shared_worker_target_console_message(
conn: &mut CdpConnection,
browser_context_id: &str,
renderer_instance_id: SharedWorkerInstanceId,
message: RendererSharedWorkerConsoleMessage,
) -> TargetPreparedOutputs {
let mut outputs = TargetPreparedOutputs::default();
let Some(context) = conn.browser_context_by_id_mut(browser_context_id) else {
return outputs;
};
let Some(target_id) = context
.shared_worker_target_id_for_renderer_instance(renderer_instance_id)
.map(str::to_owned)
else {
// Worker inspector messages can race with target destruction. Once the
// renderer instance has no CDP target, late messages are stale and must
// not recreate target state or replay into a later fresh worker.
return outputs;
};
let Some(target) = context.shared_worker_target_mut(&target_id) else {
return outputs;
};
target.record_console_message(message);
for session_id in target.session_ids() {
let attachment = target
.protocol_attachment_identity(browser_context_id, &session_id)
.expect("shared-worker output session must retain its exact attachment identity");
let console_messages = target.pending_console_domain_messages(&session_id).to_vec();
if !console_messages.is_empty() {
outputs.push(WorkerTargetLifecycleOutput::SharedWorkerConsoleMessages {
attachment: attachment.clone(),
messages: console_messages,
console_end: target.console_message_count(),
});
}
let runtime_messages = target
.pending_runtime_console_messages(&session_id)
.to_vec();
if !runtime_messages.is_empty() {
outputs.push(
WorkerTargetLifecycleOutput::SharedWorkerRuntimeConsoleMessages {
attachment,
messages: runtime_messages,
console_end: target.console_message_count(),
},
);
}
}
outputs
}
pub(super) fn record_shared_worker_target_runtime_inspector_messages(
conn: &mut CdpConnection,
browser_context_id: &str,
renderer_instance_id: SharedWorkerInstanceId,
inspector_session_id: Option<String>,
messages: Vec<RendererRuntimeInspectorMessage>,
) -> TargetPreparedOutputs {
let mut outputs = TargetPreparedOutputs::default();
if messages.is_empty() {
return outputs;
}
let Some(context) = conn.browser_context_by_id_mut(browser_context_id) else {
return outputs;
};
let Some(target_id) = context
.shared_worker_target_id_for_renderer_instance(renderer_instance_id)
.map(str::to_owned)
else {
return outputs;
};
let Some(target) = context.shared_worker_target_mut(&target_id) else {
return outputs;
};
let session_ids = if let Some(session_id) = inspector_session_id {
if !target.is_session(&session_id) {
return outputs;
}
vec![session_id]
} else {
target.session_ids()
};
for session_id in session_ids {
let attachment = target
.protocol_attachment_identity(browser_context_id, &session_id)
.expect("shared-worker inspector route must retain its exact attachment identity");
outputs.push(
WorkerTargetLifecycleOutput::SharedWorkerRuntimeInspectorMessages {
attachment,
messages: messages.clone(),
},
);
}
outputs
}
pub(super) fn exact_shared_worker_target<'a>(
conn: &'a CdpConnection,
attachment: &TargetSharedWorkerProtocolAttachmentIdentity,
) -> Option<&'a SharedWorkerTargetState> {
if !attachment.is_current() {
return None;
}
let target = conn
.browser_context_by_id(attachment.browser_context_id())?
.shared_worker_target(attachment.target_id())?;
(target.renderer_owner_local_host_id == attachment.renderer_owner_local_host_id()
&& target.renderer_instance_id == attachment.renderer_instance_id()
&& target.owner_target_id() == attachment.owner_target_id()
&& target.is_session(attachment.session_id()))
.then_some(target)
}
pub(super) fn exact_shared_worker_target_mut<'a>(
conn: &'a mut CdpConnection,
attachment: &TargetSharedWorkerProtocolAttachmentIdentity,
) -> Option<&'a mut SharedWorkerTargetState> {
if !attachment.is_current() {
return None;
}
let target = conn
.browser_context_by_id_mut(attachment.browser_context_id())?
.shared_worker_target_mut(attachment.target_id())?;
(target.renderer_owner_local_host_id == attachment.renderer_owner_local_host_id()
&& target.renderer_instance_id == attachment.renderer_instance_id()
&& target.owner_target_id() == attachment.owner_target_id()
&& target.is_session(attachment.session_id()))
.then_some(target)
}
pub(super) fn exact_shared_worker_pending_runtime_console(
conn: &CdpConnection,
attachment: &TargetSharedWorkerProtocolAttachmentIdentity,
) -> Option<(Vec<RuntimeConsoleMessageSnapshot>, usize)> {
let target = exact_shared_worker_target(conn, attachment)?;
let messages = target
.pending_runtime_console_messages(attachment.session_id())
.to_vec();
(!messages.is_empty()).then(|| (messages, target.console_message_count()))
}
pub(super) fn mark_exact_shared_worker_console_domain_emitted(
conn: &mut CdpConnection,
attachment: &TargetSharedWorkerProtocolAttachmentIdentity,
console_end: usize,
) {
if let Some(target) = exact_shared_worker_target_mut(conn, attachment) {
target.mark_console_domain_emitted(attachment.session_id(), console_end);
}
}
pub(super) fn mark_exact_shared_worker_runtime_console_emitted(
conn: &mut CdpConnection,
attachment: &TargetSharedWorkerProtocolAttachmentIdentity,
console_end: usize,
) {
if let Some(target) = exact_shared_worker_target_mut(conn, attachment) {
target.mark_runtime_console_emitted(attachment.session_id(), console_end);
}
}
File diff suppressed because it is too large Load Diff