aboutsummaryrefslogtreecommitdiffstats
path: root/components
diff options
context:
space:
mode:
authordelan azabani <dazabani@igalia.com>2025-04-08 17:22:53 +0800
committerGitHub <noreply@github.com>2025-04-08 09:22:53 +0000
commit95eedb997ae459002c73ec61aa4c6c45d0cc358e (patch)
tree259ef3e4174a8223e8349723377e00e062600919 /components
parent40655cc06cbeb9affd8f4eeae9f45cd57db319f1 (diff)
downloadservo-95eedb997ae459002c73ec61aa4c6c45d0cc358e.tar.gz
servo-95eedb997ae459002c73ec61aa4c6c45d0cc358e.zip
Devtools: initial Debugger > Sources panel (#36164)
This patch adds support for listing scripts in the Sources panel. Classic scripts, both external and inline, are implemented, but worker scripts and imported module scripts are not yet implemented. For example: ```html <!-- sources.html --> <!doctype html><meta charset=utf-8> <script src="classic.js"></script> <script> console.log("inline classic"); new Worker("worker.js"); </script> <script type="module"> import module from "./module.js"; console.log("inline module"); </script> <script src="https://servo.org/js/load-table.js"></script> ``` ```js // classic.js console.log("external classic"); ``` ```js // worker.js console.log("external classic worker"); ``` ```js // module.js export default 1; console.log("external module"); ``` ![image](https://github.com/user-attachments/assets/2f1d8d7c-501f-4fe5-bd07-085c95e504f2) --- <!-- Thank you for contributing to Servo! Please replace each `[ ]` by `[X]` when the step is complete, and replace `___` with appropriate data: --> - [x] `./mach build -d` does not report any errors - [x] `./mach test-tidy` does not report any errors - [x] These changes partially implement #36027 <!-- Either: --> - [ ] There are tests for these changes OR - [x] These changes require tests, but they are blocked on #36325 Signed-off-by: Delan Azabani <dazabani@igalia.com> Co-authored-by: atbrakhi <atbrakhi@igalia.com>
Diffstat (limited to 'components')
-rw-r--r--components/devtools/actors/browsing_context.rs26
-rw-r--r--components/devtools/actors/thread.rs33
-rw-r--r--components/devtools/actors/watcher.rs29
-rw-r--r--components/devtools/lib.rs39
-rw-r--r--components/script/dom/htmlscriptelement.rs14
-rw-r--r--components/shared/devtools/lib.rs9
6 files changed, 143 insertions, 7 deletions
diff --git a/components/devtools/actors/browsing_context.rs b/components/devtools/actors/browsing_context.rs
index 9a037c11428..c4ead7272bd 100644
--- a/components/devtools/actors/browsing_context.rs
+++ b/components/devtools/actors/browsing_context.rs
@@ -34,6 +34,12 @@ use crate::protocol::JsonPacketStream;
use crate::{EmptyReplyMsg, StreamId};
#[derive(Serialize)]
+struct ListWorkersReply {
+ from: String,
+ workers: Vec<()>,
+}
+
+#[derive(Serialize)]
struct FrameUpdateReply {
from: String,
#[serde(rename = "type")]
@@ -166,6 +172,14 @@ impl Actor for BrowsingContextActor {
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
},
+ "listWorkers" => {
+ let _ = stream.write_json_packet(&ListWorkersReply {
+ from: self.name(),
+ // TODO: Find out what needs to be listed here
+ workers: vec![],
+ });
+ ActorMessageStatus::Processed
+ },
_ => ActorMessageStatus::Ignored,
})
}
@@ -344,11 +358,19 @@ impl BrowsingContextActor {
});
}
- pub(crate) fn resource_available<T: Serialize>(&self, message: T, resource_type: String) {
+ 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, vec![message])],
+ array: vec![(resource_type, resources)],
};
for stream in self.streams.borrow_mut().values_mut() {
diff --git a/components/devtools/actors/thread.rs b/components/devtools/actors/thread.rs
index 6e272cd3d28..85ff2b732eb 100644
--- a/components/devtools/actors/thread.rs
+++ b/components/devtools/actors/thread.rs
@@ -2,10 +2,13 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+use std::cell::{Ref, RefCell};
+use std::collections::BTreeSet;
use std::net::TcpStream;
use serde::Serialize;
use serde_json::{Map, Value};
+use servo_url::ServoUrl;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::protocol::JsonPacketStream;
@@ -55,16 +58,38 @@ struct SourcesReply {
sources: Vec<Source>,
}
-#[derive(Serialize)]
-enum Source {}
+#[derive(Eq, Ord, PartialEq, PartialOrd, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct Source {
+ pub actor: String,
+ /// URL of the script, or URL of the page for inline scripts.
+ pub url: String,
+ pub is_black_boxed: bool,
+}
pub struct ThreadActor {
name: String,
+ source_urls: RefCell<BTreeSet<Source>>,
}
impl ThreadActor {
pub fn new(name: String) -> ThreadActor {
- ThreadActor { name }
+ ThreadActor {
+ name,
+ source_urls: RefCell::new(BTreeSet::default()),
+ }
+ }
+
+ pub fn add_source(&self, url: ServoUrl) {
+ self.source_urls.borrow_mut().insert(Source {
+ actor: self.name.clone(),
+ url: url.to_string(),
+ is_black_boxed: false,
+ });
+ }
+
+ pub fn sources(&self) -> Ref<BTreeSet<Source>> {
+ self.source_urls.borrow()
}
}
@@ -125,6 +150,8 @@ impl Actor for ThreadActor {
ActorMessageStatus::Processed
},
+ // Client has attached to the thread and wants to load script sources.
+ // <https://firefox-source-docs.mozilla.org/devtools/backend/protocol.html#loading-script-sources>
"sources" => {
let msg = SourcesReply {
from: self.name(),
diff --git a/components/devtools/actors/watcher.rs b/components/devtools/actors/watcher.rs
index fab071faa2a..77f82c1023a 100644
--- a/components/devtools/actors/watcher.rs
+++ b/components/devtools/actors/watcher.rs
@@ -19,6 +19,7 @@ use serde::Serialize;
use serde_json::{Map, Value};
use self::network_parent::{NetworkParentActor, NetworkParentActorMsg};
+use super::thread::ThreadActor;
use crate::actor::{Actor, ActorMessageStatus, ActorRegistry};
use crate::actors::browsing_context::{BrowsingContextActor, BrowsingContextActorMsg};
use crate::actors::watcher::target_configuration::{
@@ -78,7 +79,7 @@ impl SessionContext {
("network-event-stacktrace", false),
("reflow", false),
("stylesheet", false),
- ("source", false),
+ ("source", true),
("thread-state", false),
("server-sent-event", false),
("websocket", false),
@@ -135,6 +136,18 @@ struct GetThreadConfigurationActorReply {
#[derive(Serialize)]
#[serde(rename_all = "camelCase")]
+struct GetBreakpointListActorReply {
+ from: String,
+ breakpoint_list: GetBreakpointListActorReplyInner,
+}
+
+#[derive(Serialize)]
+struct GetBreakpointListActorReplyInner {
+ actor: String,
+}
+
+#[derive(Serialize)]
+#[serde(rename_all = "camelCase")]
struct DocumentEvent {
#[serde(rename = "hasNativeConsoleAPI")]
has_native_console_api: Option<bool>,
@@ -249,6 +262,11 @@ impl Actor for WatcherActor {
target.resource_available(event, "document-event".into());
}
},
+ "source" => {
+ let thread_actor = registry.find::<ThreadActor>(&target.thread);
+ let sources = thread_actor.sources();
+ target.resources_available(sources.iter().collect(), "source".into());
+ },
"console-message" | "error-message" => {},
_ => warn!("resource {} not handled yet", resource),
}
@@ -295,6 +313,15 @@ impl Actor for WatcherActor {
let _ = stream.write_json_packet(&msg);
ActorMessageStatus::Processed
},
+ "getBreakpointListActor" => {
+ let _ = stream.write_json_packet(&GetBreakpointListActorReply {
+ from: self.name(),
+ breakpoint_list: GetBreakpointListActorReplyInner {
+ actor: registry.new_name("breakpoint-list"),
+ },
+ });
+ ActorMessageStatus::Processed
+ },
_ => ActorMessageStatus::Ignored,
})
}
diff --git a/components/devtools/lib.rs b/components/devtools/lib.rs
index 4fc92457b04..43d2f6de880 100644
--- a/components/devtools/lib.rs
+++ b/components/devtools/lib.rs
@@ -19,12 +19,13 @@ use std::net::{Shutdown, TcpListener, TcpStream};
use std::sync::{Arc, Mutex};
use std::thread;
+use actors::thread::Source;
use base::id::{BrowsingContextId, PipelineId, WebViewId};
use crossbeam_channel::{Receiver, Sender, unbounded};
use devtools_traits::{
ChromeToDevtoolsControlMsg, ConsoleMessage, ConsoleMessageBuilder, DevtoolScriptControlMsg,
DevtoolsControlMsg, DevtoolsPageInfo, LogLevel, NavigationState, NetworkEvent, PageError,
- ScriptToDevtoolsControlMsg, WorkerId,
+ ScriptToDevtoolsControlMsg, SourceInfo, WorkerId,
};
use embedder_traits::{AllowOrDeny, EmbedderMsg, EmbedderProxy};
use ipc_channel::ipc::{self, IpcSender};
@@ -247,6 +248,10 @@ impl DevtoolsInstance {
console_message,
worker_id,
)) => self.handle_console_message(pipeline_id, worker_id, console_message),
+ DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::ScriptSourceLoaded(
+ pipeline_id,
+ source_info,
+ )) => self.handle_script_source_info(pipeline_id, source_info),
DevtoolsControlMsg::FromScript(ScriptToDevtoolsControlMsg::ReportPageError(
pipeline_id,
page_error,
@@ -498,6 +503,38 @@ 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,
+ };
+
+ let actor_name = match self.browsing_contexts.get(browsing_context_id) {
+ Some(name) => name,
+ None => return,
+ };
+
+ let thread_actor_name = actors
+ .find::<BrowsingContextActor>(actor_name)
+ .thread
+ .clone();
+
+ let thread_actor = actors.find_mut::<ThreadActor>(&thread_actor_name);
+ thread_actor.add_source(source_info.url.clone());
+
+ let source = Source {
+ 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());
+ }
}
fn allow_devtools_client(stream: &mut TcpStream, embedder: &EmbedderProxy, token: &str) -> bool {
diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs
index ed7c6e6da30..71e3d4ed72b 100644
--- a/components/script/dom/htmlscriptelement.rs
+++ b/components/script/dom/htmlscriptelement.rs
@@ -13,6 +13,7 @@ use std::sync::{Arc, Mutex};
use base::id::{PipelineId, WebViewId};
use content_security_policy as csp;
+use devtools_traits::{ScriptToDevtoolsControlMsg, SourceInfo};
use dom_struct::dom_struct;
use encoding_rs::Encoding;
use html5ever::{LocalName, Prefix, local_name, namespace_url, ns};
@@ -988,6 +989,19 @@ 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,
+ };
+ let _ = chan.send(ScriptToDevtoolsControlMsg::ScriptSourceLoaded(
+ pipeline_id,
+ source_info,
+ ));
+ }
+
if script.type_ == ScriptType::Classic {
unminify_js(&mut script);
self.substitute_with_local_script(&mut script);
diff --git a/components/shared/devtools/lib.rs b/components/shared/devtools/lib.rs
index cd934f5d5ff..59857dc0d00 100644
--- a/components/shared/devtools/lib.rs
+++ b/components/shared/devtools/lib.rs
@@ -103,6 +103,9 @@ pub enum ScriptToDevtoolsControlMsg {
/// Report a page title change
TitleChanged(PipelineId, String),
+
+ /// Get source information from script
+ ScriptSourceLoaded(PipelineId, SourceInfo),
}
/// Serialized JS return values
@@ -543,3 +546,9 @@ impl fmt::Display for ShadowRootMode {
}
}
}
+
+#[derive(Debug, Deserialize, Serialize)]
+pub struct SourceInfo {
+ pub url: ServoUrl,
+ pub external: bool,
+}