Devtools: Support worker scripts in Debugger > Source panel (#36562)

This patch adds support for listing `worker scripts` in `debugger >
source` panel

For example:
```
<!-- test.html -->
<!doctype html><meta charset=utf-8>
<script>
    setTimeout(() => {
        console.log("inline classic");
        new Worker("worker.js");
        
        const blob = new Blob([`console.log("blob worker");`], { type: "text/javascript" });
        const blobURL = URL.createObjectURL(blob);
        new Worker(blobURL);
    }, 2000);
</script>
```

```
// 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 <atbrakhi@igalia.com>
This commit is contained in:
atbrakhi 2025-04-29 10:27:42 +02:00 committed by GitHub
parent 107fd25465
commit b92542b756
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 142 additions and 38 deletions

View file

@ -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<ActorMessageStatus, ()> {
let target = registry.find::<BrowsingContextActor>(&self.browsing_context_actor);
let root = registry.find::<RootActor>("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::<WorkerActor>(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::<ThreadActor>(&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::<WorkerActor>(worker_name);
let thread = registry.find::<ThreadActor>(&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),

View file

@ -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<T: Serialize>(&self, resource: T, resource_type: String) {
self.resources_available(vec![resource], resource_type);
}
pub(crate) fn resources_available<T: Serialize>(
&self,
resources: Vec<T>,
resource_type: String,
) {
let msg = ResourceAvailableReply::<T> {
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,
}

View file

@ -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::<WorkerActor>(worker_actor_name).thread.clone();
let thread_actor_name = actors
.find::<BrowsingContextActor>(actor_name)
.thread
.clone();
let thread_actor = actors.find_mut::<ThreadActor>(&thread_actor_name);
thread_actor
.source_manager
.add_source(source_info.url.clone());
let thread_actor = actors.find_mut::<ThreadActor>(&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::<WorkerActor>(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::<BrowsingContextActor>(actor_name);
browsing_context.resource_available(source, "source".into());
let thread_actor_name = actors
.find::<BrowsingContextActor>(actor_name)
.thread
.clone();
let thread_actor = actors.find_mut::<ThreadActor>(&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::<BrowsingContextActor>(actor_name);
browsing_context.resource_available(source, "source".into());
}
}
}

View file

@ -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,

View file

@ -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<crate::DomTypeHolder> 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,
));
}
}

View file

@ -551,4 +551,5 @@ impl fmt::Display for ShadowRootMode {
pub struct SourceInfo {
pub url: ServoUrl,
pub external: bool,
pub worker_id: Option<WorkerId>,
}