mirror of
https://github.com/servo/servo.git
synced 2025-07-23 07:13:52 +01:00
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 ```  Another example: ``` ./mach run --devtools=6080 https://charming.daz.cat/ ```  - [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:
parent
107fd25465
commit
b92542b756
6 changed files with 142 additions and 38 deletions
|
@ -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),
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -551,4 +551,5 @@ impl fmt::Display for ShadowRootMode {
|
|||
pub struct SourceInfo {
|
||||
pub url: ServoUrl,
|
||||
pub external: bool,
|
||||
pub worker_id: Option<WorkerId>,
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue