From b92542b756ba3c733e35527d3a8d98bed42e5dc1 Mon Sep 17 00:00:00 2001 From: atbrakhi Date: Tue, 29 Apr 2025 10:27:42 +0200 Subject: [PATCH] Devtools: Support worker scripts in `Debugger > Source` panel (#36562) This patch adds support for listing `worker scripts` in `debugger > source` panel For example: ``` ``` ``` // worker.js console.log("external classic worker"); ``` ``` ./mach run --devtools=6080 http://127.0.0.1:3000/test.html ``` ![file1](https://github.com/user-attachments/assets/84dd94b9-95d8-4087-b4bb-ab936fca0023) Another example: ``` ./mach run --devtools=6080 https://charming.daz.cat/ ``` ![blob](https://github.com/user-attachments/assets/a1341ee4-3a1c-4cca-ac04-658675cdcf39) - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [x] These changes partially implement #36027 - [x] These changes require tests, but they are blocked on https://github.com/servo/servo/issues/36325 --------- Signed-off-by: atbrakhi --- components/devtools/actors/watcher.rs | 69 ++++++++++++++++++---- components/devtools/actors/worker.rs | 29 ++++++++- components/devtools/lib.rs | 67 +++++++++++++-------- components/script/dom/htmlscriptelement.rs | 2 +- components/script/dom/worker.rs | 12 +++- components/shared/devtools/lib.rs | 1 + 6 files changed, 142 insertions(+), 38 deletions(-) diff --git a/components/devtools/actors/watcher.rs b/components/devtools/actors/watcher.rs index 6a84499b6dd..b0b2c755fd8 100644 --- a/components/devtools/actors/watcher.rs +++ b/components/devtools/actors/watcher.rs @@ -20,8 +20,10 @@ use serde_json::{Map, Value}; use self::network_parent::{NetworkParentActor, NetworkParentActorMsg}; use super::thread::ThreadActor; +use super::worker::WorkerMsg; use crate::actor::{Actor, ActorMessageStatus, ActorRegistry}; use crate::actors::browsing_context::{BrowsingContextActor, BrowsingContextActorMsg}; +use crate::actors::root::RootActor; use crate::actors::watcher::target_configuration::{ TargetConfigurationActor, TargetConfigurationActorMsg, }; @@ -29,8 +31,8 @@ use crate::actors::watcher::thread_configuration::{ ThreadConfigurationActor, ThreadConfigurationActorMsg, }; use crate::protocol::JsonPacketStream; -use crate::resource::ResourceAvailable; -use crate::{EmptyReplyMsg, StreamId}; +use crate::resource::{ResourceAvailable, ResourceAvailableReply}; +use crate::{EmptyReplyMsg, StreamId, WorkerActor}; pub mod network_parent; pub mod target_configuration; @@ -55,7 +57,7 @@ impl SessionContext { supported_targets: HashMap::from([ ("frame", true), ("process", false), - ("worker", false), + ("worker", true), ("service_worker", false), ("shared_worker", false), ]), @@ -102,12 +104,19 @@ pub enum SessionContextType { _All, } +#[derive(Serialize)] +#[serde(untagged)] +enum TargetActorMsg { + BrowsingContext(BrowsingContextActorMsg), + Worker(WorkerMsg), +} + #[derive(Serialize)] struct WatchTargetsReply { from: String, #[serde(rename = "type")] type_: String, - target: BrowsingContextActorMsg, + target: TargetActorMsg, } #[derive(Serialize)] @@ -212,16 +221,38 @@ impl Actor for WatcherActor { _id: StreamId, ) -> Result { let target = registry.find::(&self.browsing_context_actor); + let root = registry.find::("root"); Ok(match msg_type { "watchTargets" => { - let msg = WatchTargetsReply { - from: self.name(), - type_: "target-available-form".into(), - target: target.encodable(), - }; - let _ = stream.write_json_packet(&msg); + // As per logs we either get targetType as "frame" or "worker" + let target_type = msg + .get("targetType") + .and_then(Value::as_str) + .unwrap_or("frame"); // default to "frame" - target.frame_update(stream); + if target_type == "frame" { + let msg = WatchTargetsReply { + from: self.name(), + type_: "target-available-form".into(), + target: TargetActorMsg::BrowsingContext(target.encodable()), + }; + let _ = stream.write_json_packet(&msg); + + target.frame_update(stream); + } else if target_type == "worker" { + for worker_name in &root.workers { + let worker = registry.find::(worker_name); + let worker_msg = WatchTargetsReply { + from: self.name(), + type_: "target-available-form".into(), + target: TargetActorMsg::Worker(worker.encodable()), + }; + let _ = stream.write_json_packet(&worker_msg); + } + } else { + warn!("Unexpected target_type: {}", target_type); + return Ok(ActorMessageStatus::Ignored); + } // Messages that contain a `type` field are used to send event callbacks, but they // don't count as a reply. Since every message needs to be responded, we send an @@ -267,6 +298,22 @@ impl Actor for WatcherActor { let thread_actor = registry.find::(&target.thread); let sources = thread_actor.source_manager.sources(); target.resources_available(sources.iter().collect(), "source".into()); + + for worker_name in &root.workers { + let worker = registry.find::(worker_name); + let thread = registry.find::(&worker.thread); + let worker_sources = thread.source_manager.sources(); + + let msg = ResourceAvailableReply { + from: worker.name(), + type_: "resources-available-array".into(), + array: vec![( + "source".to_string(), + worker_sources.iter().cloned().collect(), + )], + }; + let _ = stream.write_json_packet(&msg); + } }, "console-message" | "error-message" => {}, _ => warn!("resource {} not handled yet", resource), diff --git a/components/devtools/actors/worker.rs b/components/devtools/actors/worker.rs index 42c9d9a9c28..68ff56fb3b2 100644 --- a/components/devtools/actors/worker.rs +++ b/components/devtools/actors/worker.rs @@ -17,7 +17,7 @@ use servo_url::ServoUrl; use crate::StreamId; use crate::actor::{Actor, ActorMessageStatus, ActorRegistry}; use crate::protocol::JsonPacketStream; -use crate::resource::ResourceAvailable; +use crate::resource::{ResourceAvailable, ResourceAvailableReply}; #[derive(Clone, Copy)] #[allow(dead_code)] @@ -48,8 +48,10 @@ impl WorkerActor { url: self.url.to_string(), traits: WorkerTraits { is_parent_intercept_enabled: false, + supports_top_level_target_flag: false, }, type_: self.type_ as u32, + target_type: "worker".to_string(), } } } @@ -131,6 +133,28 @@ impl Actor for WorkerActor { } } +impl WorkerActor { + pub(crate) fn resource_available(&self, resource: T, resource_type: String) { + self.resources_available(vec![resource], resource_type); + } + + pub(crate) fn resources_available( + &self, + resources: Vec, + resource_type: String, + ) { + let msg = ResourceAvailableReply:: { + from: self.name(), + type_: "resources-available-array".into(), + array: vec![(resource_type, resources)], + }; + + for stream in self.streams.borrow_mut().values_mut() { + let _ = stream.write_json_packet(&msg); + } + } +} + #[derive(Serialize)] struct DetachedReply { from: String, @@ -160,6 +184,7 @@ struct ConnectReply { #[serde(rename_all = "camelCase")] struct WorkerTraits { is_parent_intercept_enabled: bool, + supports_top_level_target_flag: bool, } #[derive(Serialize)] @@ -173,4 +198,6 @@ pub(crate) struct WorkerMsg { traits: WorkerTraits, #[serde(rename = "type")] type_: u32, + #[serde(rename = "targetType")] + target_type: String, } diff --git a/components/devtools/lib.rs b/components/devtools/lib.rs index 4d1e0222177..5fb9485e9d3 100644 --- a/components/devtools/lib.rs +++ b/components/devtools/lib.rs @@ -510,35 +510,54 @@ impl DevtoolsInstance { fn handle_script_source_info(&mut self, pipeline_id: PipelineId, source_info: SourceInfo) { let mut actors = self.actors.lock().unwrap(); - let browsing_context_id = match self.pipelines.get(&pipeline_id) { - Some(id) => id, - None => return, - }; + if let Some(worker_id) = source_info.worker_id { + let Some(worker_actor_name) = self.actor_workers.get(&worker_id) else { + return; + }; - let actor_name = match self.browsing_contexts.get(browsing_context_id) { - Some(name) => name, - None => return, - }; + let thread_actor_name = actors.find::(worker_actor_name).thread.clone(); - let thread_actor_name = actors - .find::(actor_name) - .thread - .clone(); + let thread_actor = actors.find_mut::(&thread_actor_name); + thread_actor + .source_manager + .add_source(source_info.url.clone()); - let thread_actor = actors.find_mut::(&thread_actor_name); - thread_actor - .source_manager - .add_source(source_info.url.clone()); + let source = SourceData { + actor: thread_actor_name.clone(), + url: source_info.url.to_string(), + is_black_boxed: false, + }; - let source = SourceData { - actor: thread_actor_name.clone(), - url: source_info.url.to_string(), - is_black_boxed: false, - }; + let worker_actor = actors.find::(worker_actor_name); + worker_actor.resource_available(source, "source".into()); + } else { + let Some(browsing_context_id) = self.pipelines.get(&pipeline_id) else { + return; + }; + let Some(actor_name) = self.browsing_contexts.get(browsing_context_id) else { + return; + }; - // Notify browsing context about the new source - let browsing_context = actors.find::(actor_name); - browsing_context.resource_available(source, "source".into()); + let thread_actor_name = actors + .find::(actor_name) + .thread + .clone(); + + let thread_actor = actors.find_mut::(&thread_actor_name); + thread_actor + .source_manager + .add_source(source_info.url.clone()); + + let source = SourceData { + actor: thread_actor_name.clone(), + url: source_info.url.to_string(), + is_black_boxed: false, + }; + + // Notify browsing context about the new source + let browsing_context = actors.find::(actor_name); + browsing_context.resource_available(source, "source".into()); + } } } diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs index 9452dcb17a6..58853f600d2 100644 --- a/components/script/dom/htmlscriptelement.rs +++ b/components/script/dom/htmlscriptelement.rs @@ -1008,12 +1008,12 @@ impl HTMLScriptElement { Ok(script) => script, }; - // TODO: we need to handle this for worker if let Some(chan) = self.global().devtools_chan() { let pipeline_id = self.global().pipeline_id(); let source_info = SourceInfo { url: script.url.clone(), external: script.external, + worker_id: None, }; let _ = chan.send(ScriptToDevtoolsControlMsg::ScriptSourceLoaded( pipeline_id, diff --git a/components/script/dom/worker.rs b/components/script/dom/worker.rs index 429234c7a8e..6ad56026db6 100644 --- a/components/script/dom/worker.rs +++ b/components/script/dom/worker.rs @@ -8,7 +8,7 @@ use std::sync::atomic::{AtomicBool, Ordering}; use constellation_traits::{StructuredSerializedData, WorkerScriptLoadOrigin}; use crossbeam_channel::{Sender, unbounded}; -use devtools_traits::{DevtoolsPageInfo, ScriptToDevtoolsControlMsg, WorkerId}; +use devtools_traits::{DevtoolsPageInfo, ScriptToDevtoolsControlMsg, SourceInfo, WorkerId}; use dom_struct::dom_struct; use ipc_channel::ipc; use js::jsapi::{Heap, JSObject}; @@ -213,6 +213,16 @@ impl WorkerMethods for Worker { devtools_sender.clone(), page_info, )); + + let source_info = SourceInfo { + url: worker_url.clone(), + external: true, // Worker scripts are always external. + worker_id: Some(worker_id), + }; + let _ = chan.send(ScriptToDevtoolsControlMsg::ScriptSourceLoaded( + pipeline_id, + source_info, + )); } } diff --git a/components/shared/devtools/lib.rs b/components/shared/devtools/lib.rs index 59857dc0d00..0cf99d22658 100644 --- a/components/shared/devtools/lib.rs +++ b/components/shared/devtools/lib.rs @@ -551,4 +551,5 @@ impl fmt::Display for ShadowRootMode { pub struct SourceInfo { pub url: ServoUrl, pub external: bool, + pub worker_id: Option, }