diff --git a/packages/cli/src/build/builder.rs b/packages/cli/src/build/builder.rs index 0c080d456b..8284cfca56 100644 --- a/packages/cli/src/build/builder.rs +++ b/packages/cli/src/build/builder.rs @@ -1,10 +1,11 @@ use crate::{ - BuildArtifacts, BuildRequest, BuildStage, BuilderUpdate, Platform, ProgressRx, ProgressTx, - Result, StructuredOutput, + serve::WebServer, BuildArtifacts, BuildRequest, BuildStage, BuilderUpdate, Platform, + ProgressRx, ProgressTx, Result, StructuredOutput, }; use anyhow::Context; use dioxus_cli_opt::process_file_to; use futures_util::{future::OptionFuture, pin_mut, FutureExt}; +use itertools::Itertools; use std::{ env, time::{Duration, Instant, SystemTime}, @@ -91,6 +92,9 @@ pub(crate) struct AppBuilder { pub compile_end: Option, pub bundle_start: Option, pub bundle_end: Option, + + /// The debugger for the app - must be enabled with the `d` key + pub(crate) pid: Option, } impl AppBuilder { @@ -156,6 +160,7 @@ impl AppBuilder { entropy_app_exe: None, artifacts: None, patch_cache: None, + pid: None, }) } @@ -183,12 +188,16 @@ impl AppBuilder { StderrReceived { msg } }, Some(status) = OptionFuture::from(self.child.as_mut().map(|f| f.wait())) => { - // Panicking here is on purpose. If the task crashes due to a JoinError (a panic), - // we want to propagate that panic up to the serve controller. - let status = status.unwrap(); - self.child = None; - - ProcessExited { status } + match status { + Ok(status) => { + self.child = None; + ProcessExited { status } + }, + Err(err) => { + let () = futures_util::future::pending().await; + ProcessWaitFailed { err } + } + } } }; @@ -263,6 +272,7 @@ impl AppBuilder { StdoutReceived { .. } => {} StderrReceived { .. } => {} ProcessExited { .. } => {} + ProcessWaitFailed { .. } => {} } update @@ -402,6 +412,7 @@ impl AppBuilder { BuilderUpdate::StdoutReceived { .. } => {} BuilderUpdate::StderrReceived { .. } => {} BuilderUpdate::ProcessExited { .. } => {} + BuilderUpdate::ProcessWaitFailed { .. } => {} } } } @@ -464,7 +475,7 @@ impl AppBuilder { } // We try to use stdin/stdout to communicate with the app - let running_process = match self.build.platform { + match self.build.platform { // Unfortunately web won't let us get a proc handle to it (to read its stdout/stderr) so instead // use use the websocket to communicate with it. I wish we could merge the concepts here, // like say, opening the socket as a subprocess, but alas, it's simpler to do that somewhere else. @@ -473,15 +484,12 @@ impl AppBuilder { if open_browser { self.open_web(open_address.unwrap_or(devserver_ip)); } - - None } - Platform::Ios => Some(self.open_ios_sim(envs).await?), + Platform::Ios => self.open_ios_sim(envs).await?, Platform::Android => { self.open_android_sim(false, devserver_ip, envs).await?; - None } // These are all just basically running the main exe, but with slightly different resource dir paths @@ -489,18 +497,9 @@ impl AppBuilder { | Platform::MacOS | Platform::Windows | Platform::Linux - | Platform::Liveview => Some(self.open_with_main_exe(envs)?), + | Platform::Liveview => self.open_with_main_exe(envs)?, }; - // If we have a running process, we need to attach to it and wait for its outputs - if let Some(mut child) = running_process { - let stdout = BufReader::new(child.stdout.take().unwrap()); - let stderr = BufReader::new(child.stderr.take().unwrap()); - self.stdout = Some(stdout.lines()); - self.stderr = Some(stderr.lines()); - self.child = Some(child); - } - self.builds_opened += 1; Ok(()) @@ -728,19 +727,25 @@ impl AppBuilder { /// paths right now, but they will when we start to enable things like swift integration. /// /// Server/liveview/desktop are all basically the same, though - fn open_with_main_exe(&mut self, envs: Vec<(&str, String)>) -> Result { + fn open_with_main_exe(&mut self, envs: Vec<(&str, String)>) -> Result<()> { let main_exe = self.app_exe(); tracing::debug!("Opening app with main exe: {main_exe:?}"); - let child = Command::new(main_exe) + let mut child = Command::new(main_exe) .envs(envs) .stderr(Stdio::piped()) .stdout(Stdio::piped()) .kill_on_drop(true) .spawn()?; - Ok(child) + let stdout = BufReader::new(child.stdout.take().unwrap()); + let stderr = BufReader::new(child.stderr.take().unwrap()); + self.stdout = Some(stdout.lines()); + self.stderr = Some(stderr.lines()); + self.child = Some(child); + + Ok(()) } /// Open the web app by opening the browser to the given address. @@ -765,7 +770,7 @@ impl AppBuilder { /// /// TODO(jon): we should probably check if there's a simulator running before trying to install, /// and open the simulator if we have to. - async fn open_ios_sim(&mut self, envs: Vec<(&str, String)>) -> Result { + async fn open_ios_sim(&mut self, envs: Vec<(&str, String)>) -> Result<()> { tracing::debug!("Installing app to simulator {:?}", self.build.root_dir()); let res = Command::new("xcrun") @@ -784,7 +789,7 @@ impl AppBuilder { .iter() .map(|(k, v)| (format!("SIMCTL_CHILD_{k}"), v.clone())); - let child = Command::new("xcrun") + let mut child = Command::new("xcrun") .arg("simctl") .arg("launch") .arg("--console") @@ -796,7 +801,13 @@ impl AppBuilder { .kill_on_drop(true) .spawn()?; - Ok(child) + let stdout = BufReader::new(child.stdout.take().unwrap()); + let stderr = BufReader::new(child.stderr.take().unwrap()); + self.stdout = Some(stdout.lines()); + self.stderr = Some(stderr.lines()); + self.child = Some(child); + + Ok(()) } /// We have this whole thing figured out, but we don't actually use it yet. @@ -1339,4 +1350,173 @@ We checked the folder: {} pub(crate) fn can_receive_hotreloads(&self) -> bool { matches!(&self.stage, BuildStage::Success | BuildStage::Failed) } + + pub(crate) async fn open_debugger(&mut self, server: &WebServer) -> Result<()> { + let url = match self.build.platform { + Platform::MacOS + | Platform::Windows + | Platform::Linux + | Platform::Server + | Platform::Liveview => { + let Some(Some(pid)) = self.child.as_mut().map(|f| f.id()) else { + tracing::warn!("No process to attach debugger to"); + return Ok(()); + }; + + format!( + "vscode://vadimcn.vscode-lldb/launch/config?{{'request':'attach','pid':{}}}", + pid + ) + } + + Platform::Web => { + // code --open-url "vscode://DioxusLabs.dioxus/debugger?uri=http://127.0.0.1:8080" + // todo - debugger could open to the *current* page afaik we don't have a way to have that info + let address = server.devserver_address(); + let base_path = self.build.config.web.app.base_path.clone(); + let https = self.build.config.web.https.enabled.unwrap_or_default(); + let protocol = if https { "https" } else { "http" }; + let base_path = match base_path.as_deref() { + Some(base_path) => format!("/{}", base_path.trim_matches('/')), + None => "".to_owned(), + }; + format!("vscode://DioxusLabs.dioxus/debugger?uri={protocol}://{address}{base_path}") + } + + Platform::Ios => { + let Some(pid) = self.pid else { + tracing::warn!("No process to attach debugger to"); + return Ok(()); + }; + + format!( + "vscode://vadimcn.vscode-lldb/launch/config?{{'request':'attach','pid':{pid}}}" + ) + } + + // https://stackoverflow.com/questions/53733781/how-do-i-use-lldb-to-debug-c-code-on-android-on-command-line/64997332#64997332 + // https://android.googlesource.com/platform/development/+/refs/heads/main/scripts/gdbclient.py + // run lldbserver on the device and then connect + // + // # TODO: https://code.visualstudio.com/api/references/vscode-api#debug and + // # https://code.visualstudio.com/api/extension-guides/debugger-extension and + // # https://github.com/vadimcn/vscode-lldb/blob/6b775c439992b6615e92f4938ee4e211f1b060cf/extension/pickProcess.ts#L6 + // + // res = { + // "name": "(lldbclient.py) Attach {} (port: {})".format(binary_name.split("/")[-1], port), + // "type": "lldb", + // "request": "custom", + // "relativePathBase": root, + // "sourceMap": { "/b/f/w" : root, '': root, '.': root }, + // "initCommands": ['settings append target.exec-search-paths {}'.format(' '.join(solib_search_path))], + // "targetCreateCommands": ["target create {}".format(binary_name), + // "target modules search-paths add / {}/".format(sysroot)], + // "processCreateCommands": ["gdb-remote {}".format(str(port))] + // } + // + // https://github.com/vadimcn/codelldb/issues/213 + // + // lots of pain to figure this out: + // + // (lldb) image add target/dx/tw6/debug/android/app/app/src/main/jniLibs/arm64-v8a/libdioxusmain.so + // (lldb) settings append target.exec-search-paths target/dx/tw6/debug/android/app/app/src/main/jniLibs/arm64-v8a/libdioxusmain.so + // (lldb) process handle SIGSEGV --pass true --stop false --notify true (otherwise the java threads cause crash) + // + Platform::Android => { + // adb push ./sdk/ndk/29.0.13113456/toolchains/llvm/prebuilt/darwin-x86_64/lib/clang/20/lib/linux/aarch64/lldb-server /tmp + // adb shell "/tmp/lldb-server --server --listen ..." + // "vscode://vadimcn.vscode-lldb/launch/config?{{'request':'connect','port': {}}}", + // format!( + // "vscode://vadimcn.vscode-lldb/launch/config?{{'request':'attach','pid':{pid}}}" + // ) + let tools = &self.build.workspace.android_tools()?; + + // get the pid of the app + let pid = Command::new(&tools.adb) + .arg("shell") + .arg("pidof") + .arg(self.build.bundle_identifier()) + .output() + .await + .ok() + .and_then(|output| String::from_utf8(output.stdout).ok()) + .and_then(|s| s.trim().parse::().ok()) + .unwrap(); + + // copy the lldb-server to the device + let lldb_server = tools + .android_tools_dir() + .parent() + .unwrap() + .join("lib") + .join("clang") + .join("20") + .join("lib") + .join("linux") + .join("aarch64") + .join("lldb-server"); + + tracing::info!("Copying lldb-server to device: {lldb_server:?}"); + + _ = Command::new(&tools.adb) + .arg("push") + .arg(lldb_server) + .arg("/tmp/lldb-server") + .output() + .await; + + // Forward requests on 10086 to the device + _ = Command::new(&tools.adb) + .arg("forward") + .arg("tcp:10086") + .arg("tcp:10086") + .output() + .await; + + // start the server - running it multiple times will make the subsequent ones fail (which is fine) + _ = Command::new(&tools.adb) + .arg("shell") + .arg(r#"cd /tmp && ./lldb-server platform --server --listen '*:10086'"#) + .kill_on_drop(false) + .stdin(Stdio::null()) + .stdout(Stdio::piped()) + .stderr(Stdio::piped()) + .spawn(); + + let program_path = self.build.main_exe(); + format!( + r#"vscode://vadimcn.vscode-lldb/launch/config?{{ + 'name':'Attach to Android', + 'type':'lldb', + 'request':'attach', + 'pid': '{pid}', + 'processCreateCommands': [ + 'platform select remote-android', + 'platform connect connect://localhost:10086', + 'settings set target.inherit-env false', + 'settings set target.inline-breakpoint-strategy always', + 'settings set target.process.thread.step-avoid-regexp \"JavaBridge|JDWP|Binder|ReferenceQueueDaemon\"', + 'process handle SIGSEGV --pass true --stop false --notify true"', + 'settings append target.exec-search-paths {program_path}', + 'attach --pid {pid}', + 'continue' + ] + }}"#, + program_path = program_path.display(), + ) + .lines() + .map(|line| line.trim()) + .join("") + } + }; + + tracing::info!("Opening debugger for [{}]: {url}", self.build.platform); + + _ = tokio::process::Command::new("code") + .arg("--open-url") + .arg(url) + .spawn(); + + Ok(()) + } } diff --git a/packages/cli/src/build/context.rs b/packages/cli/src/build/context.rs index fc119bc855..e38ec12c0c 100644 --- a/packages/cli/src/build/context.rs +++ b/packages/cli/src/build/context.rs @@ -65,6 +65,12 @@ pub enum BuilderUpdate { ProcessExited { status: ExitStatus, }, + + /// Waiting for the process failed. This might be because it's hung or being debugged. + /// This is not the same as the process exiting, so it should just be logged but not treated as an error. + ProcessWaitFailed { + err: std::io::Error, + }, } impl BuildContext { diff --git a/packages/cli/src/build/request.rs b/packages/cli/src/build/request.rs index 9ab3220496..a71a38d8fa 100644 --- a/packages/cli/src/build/request.rs +++ b/packages/cli/src/build/request.rs @@ -2128,6 +2128,12 @@ impl BuildRequest { )); } + // for debuggability, we need to make sure android studio can properly understand our build + // https://stackoverflow.com/questions/68481401/debugging-a-prebuilt-shared-library-in-android-studio + if self.platform == Platform::Android { + cargo_args.push("-Clink-arg=-Wl,--build-id=sha1".to_string()); + } + // Handle frameworks/dylibs by setting the rpath // This is dependent on the bundle structure - in this case, appimage and appbundle for mac/linux // todo: we need to figure out what to do for windows diff --git a/packages/cli/src/cli/run.rs b/packages/cli/src/cli/run.rs index 1f969c4f55..f0df280db7 100644 --- a/packages/cli/src/cli/run.rs +++ b/packages/cli/src/cli/run.rs @@ -123,6 +123,7 @@ impl RunArgs { break; } + BuilderUpdate::ProcessWaitFailed { .. } => {} } } _ => {} diff --git a/packages/cli/src/serve/mod.rs b/packages/cli/src/serve/mod.rs index 5fa390967c..f6820f3e35 100644 --- a/packages/cli/src/serve/mod.rs +++ b/packages/cli/src/serve/mod.rs @@ -90,7 +90,11 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) -> // Run the server in the background // Waiting for updates here lets us tap into when clients are added/removed - ServeUpdate::NewConnection { id, aslr_reference } => { + ServeUpdate::NewConnection { + id, + aslr_reference, + pid, + } => { devserver .send_hotreload(builder.applied_hot_reload_changes(BuildId::CLIENT)) .await; @@ -101,7 +105,7 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) -> .await; } - builder.client_connected(id, aslr_reference).await; + builder.client_connected(id, aslr_reference, pid).await; } // Received a message from the devtools server - currently we only use this for @@ -173,6 +177,11 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) -> tracing::error!("Application [{platform}] exited with error: {status}"); } } + BuilderUpdate::ProcessWaitFailed { err } => { + tracing::warn!( + "Failed to wait for process - maybe it's hung or being debugged?: {err}" + ); + } } } @@ -202,6 +211,10 @@ pub(crate) async fn serve_all(args: ServeArgs, tracer: &mut TraceController) -> ) } + ServeUpdate::OpenDebugger { id } => { + builder.open_debugger(&devserver, id).await; + } + ServeUpdate::Exit { error } => { _ = builder.shutdown().await; _ = devserver.shutdown().await; diff --git a/packages/cli/src/serve/output.rs b/packages/cli/src/serve/output.rs index 8d32696cec..59accb2c3a 100644 --- a/packages/cli/src/serve/output.rs +++ b/packages/cli/src/serve/output.rs @@ -1,6 +1,6 @@ use crate::{ serve::{ansi_buffer::AnsiStringLine, ServeUpdate, WebServer}, - BuildStage, BuilderUpdate, Platform, TraceContent, TraceMsg, TraceSrc, + BuildId, BuildStage, BuilderUpdate, Platform, TraceContent, TraceMsg, TraceSrc, }; use cargo_metadata::diagnostic::Diagnostic; use crossterm::{ @@ -245,6 +245,16 @@ impl Output { self.trace = !self.trace; tracing::info!("Tracing is now {}", if self.trace { "on" } else { "off" }); } + KeyCode::Char('D') => { + return Ok(Some(ServeUpdate::OpenDebugger { + id: BuildId::SERVER, + })); + } + KeyCode::Char('d') => { + return Ok(Some(ServeUpdate::OpenDebugger { + id: BuildId::CLIENT, + })); + } KeyCode::Char('c') => { stdout() diff --git a/packages/cli/src/serve/runner.rs b/packages/cli/src/serve/runner.rs index 2f36f79cdf..02883ecf67 100644 --- a/packages/cli/src/serve/runner.rs +++ b/packages/cli/src/serve/runner.rs @@ -692,6 +692,7 @@ impl AppServer { &mut self, build_id: BuildId, aslr_reference: Option, + pid: Option, ) { match build_id { BuildId::CLIENT => { @@ -701,6 +702,9 @@ impl AppServer { if let Some(aslr_reference) = aslr_reference { self.client.aslr_reference = Some(aslr_reference); } + if let Some(pid) = pid { + self.client.pid = Some(pid); + } } } BuildId::SERVER => { @@ -970,6 +974,24 @@ impl AppServer { server.compiled_crates as f64 / server.expected_crates as f64 } + + pub(crate) async fn open_debugger(&mut self, dev: &WebServer, build: BuildId) { + if self.use_hotpatch_engine { + tracing::warn!("Debugging symbols might not work properly with hotpatching enabled. Consider disabling hotpatching for debugging."); + } + + match build { + BuildId::CLIENT => { + _ = self.client.open_debugger(dev).await; + } + BuildId::SERVER => { + if let Some(server) = self.server.as_mut() { + _ = server.open_debugger(dev).await; + } + } + _ => {} + } + } } /// Bind a listener to any point and return it diff --git a/packages/cli/src/serve/server.rs b/packages/cli/src/serve/server.rs index 114ee9d605..63482916aa 100644 --- a/packages/cli/src/serve/server.rs +++ b/packages/cli/src/serve/server.rs @@ -69,6 +69,7 @@ pub(crate) struct ConnectedWsClient { socket: WebSocket, build_id: Option, aslr_reference: Option, + pid: Option, } impl WebServer { @@ -146,12 +147,13 @@ impl WebServer { new_hot_reload_socket = &mut new_hot_reload_socket => { if let Some(new_socket) = new_hot_reload_socket { let aslr_reference = new_socket.aslr_reference; + let pid = new_socket.pid; let id = new_socket.build_id.unwrap_or(BuildId::CLIENT); drop(new_message); self.hot_reload_sockets.push(new_socket); - return ServeUpdate::NewConnection { aslr_reference, id }; + return ServeUpdate::NewConnection { aslr_reference, id, pid }; } else { panic!("Could not receive a socket - the devtools could not boot - the port is likely already in use"); } @@ -261,6 +263,7 @@ impl WebServer { BuilderUpdate::StdoutReceived { .. } => {} BuilderUpdate::StderrReceived { .. } => {} BuilderUpdate::ProcessExited { .. } => {} + BuilderUpdate::ProcessWaitFailed { .. } => {} } } @@ -495,6 +498,7 @@ fn build_devserver_router( struct ConnectionQuery { aslr_reference: Option, build_id: Option, + pid: Option, } // Setup websocket endpoint - and pass in the extension layer immediately after @@ -506,7 +510,7 @@ fn build_devserver_router( get( |ws: WebSocketUpgrade, ext: Extension>, query: Query| async move { tracing::debug!("New devtool websocket connection: {:?}", query); - ws.on_upgrade(move |socket| async move { _ = ext.0.unbounded_send(ConnectedWsClient { socket, aslr_reference: query.aslr_reference, build_id: query.build_id }) }) + ws.on_upgrade(move |socket| async move { _ = ext.0.unbounded_send(ConnectedWsClient { socket, aslr_reference: query.aslr_reference, build_id: query.build_id, pid: query.pid }) }) }, ), ) @@ -515,7 +519,7 @@ fn build_devserver_router( "/build_status", get( |ws: WebSocketUpgrade, ext: Extension>| async move { - ws.on_upgrade(move |socket| async move { _ = ext.0.unbounded_send(ConnectedWsClient { socket, aslr_reference: None, build_id: None }) }) + ws.on_upgrade(move |socket| async move { _ = ext.0.unbounded_send(ConnectedWsClient { socket, aslr_reference: None, build_id: None, pid: None }) }) }, ), ) diff --git a/packages/cli/src/serve/update.rs b/packages/cli/src/serve/update.rs index 4193c40cb8..dfb4b74050 100644 --- a/packages/cli/src/serve/update.rs +++ b/packages/cli/src/serve/update.rs @@ -10,6 +10,7 @@ pub(crate) enum ServeUpdate { NewConnection { id: BuildId, aslr_reference: Option, + pid: Option, }, WsMessage { platform: Platform, @@ -32,6 +33,10 @@ pub(crate) enum ServeUpdate { ToggleShouldRebuild, + OpenDebugger { + id: BuildId, + }, + Redraw, TracingLog { diff --git a/packages/devtools/src/lib.rs b/packages/devtools/src/lib.rs index e79c7be8ae..2545253bef 100644 --- a/packages/devtools/src/lib.rs +++ b/packages/devtools/src/lib.rs @@ -49,7 +49,7 @@ pub fn try_apply_changes(dom: &VirtualDom, msg: &HotReloadMsg) -> Result<(), Pat /// Connect to the devserver and handle its messages with a callback. /// /// This doesn't use any form of security or protocol, so it's not safe to expose to the internet. -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(target_family = "wasm"))] pub fn connect(callback: impl FnMut(DevserverMsg) + Send + 'static) { let Some(endpoint) = dioxus_cli_config::devserver_ws_endpoint() else { return; @@ -64,7 +64,7 @@ pub fn connect(callback: impl FnMut(DevserverMsg) + Send + 'static) { /// This is intended to be used by non-dioxus projects that want to use hotpatching. /// /// To handle the full devserver protocol, use `connect` instead. -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(target_family = "wasm"))] pub fn connect_subsecond() { connect(|msg| { if let DevserverMsg::HotReload(hot_reload_msg) = msg { @@ -75,13 +75,14 @@ pub fn connect_subsecond() { }); } -#[cfg(not(target_arch = "wasm32"))] +#[cfg(not(target_family = "wasm"))] pub fn connect_at(endpoint: String, mut callback: impl FnMut(DevserverMsg) + Send + 'static) { std::thread::spawn(move || { let uri = format!( - "{endpoint}?aslr_reference={}&build_id={}", + "{endpoint}?aslr_reference={}&build_id={}&pid={}", subsecond::aslr_reference(), - dioxus_cli_config::build_id() + dioxus_cli_config::build_id(), + std::process::id() ); let (mut websocket, _req) = match tungstenite::connect(uri) { diff --git a/packages/extension/.gitignore b/packages/extension/.gitignore index 548955b60f..7a61312978 100644 --- a/packages/extension/.gitignore +++ b/packages/extension/.gitignore @@ -11,3 +11,4 @@ tsconfig.lsif.json *.lsif *.db *.vsix +pkg/ diff --git a/packages/extension/package.json b/packages/extension/package.json index 0918a69073..7d066a5c81 100644 --- a/packages/extension/package.json +++ b/packages/extension/package.json @@ -2,7 +2,7 @@ "name": "dioxus", "displayName": "Dioxus", "description": "Useful tools for working with Dioxus", - "version": "0.6.0", + "version": "0.7.0", "publisher": "DioxusLabs", "private": true, "license": "MIT", diff --git a/packages/extension/src/main.ts b/packages/extension/src/main.ts index 03bf55b830..99005f3e86 100644 --- a/packages/extension/src/main.ts +++ b/packages/extension/src/main.ts @@ -22,6 +22,9 @@ export async function activate(context: vscode.ExtensionContext) { vscode.commands.registerCommand('extension.formatRsxDocument', formatRsxDocument), vscode.workspace.onWillSaveTextDocument(fmtDocumentOnSave) ); + + context.subscriptions.push(vscode.window.registerUriHandler(new UriLaunchServer())); + } function translate(component: boolean) { @@ -171,3 +174,15 @@ function fmtDocument(document: vscode.TextDocument) { vscode.window.showWarningMessage(`Errors occurred while formatting. Make sure you have the most recent Dioxus-CLI installed! \n${error}`); } } + +class UriLaunchServer implements vscode.UriHandler { + handleUri(uri: vscode.Uri): vscode.ProviderResult { + if (uri.path === '/debugger') { + let query = decodeURIComponent(uri.query); + let params = new URLSearchParams(query); + let route = params.get('uri'); + vscode.window.showInformationMessage(`Opening Chrome debugger: ${route}`); + vscode.commands.executeCommand('extension.js-debug.debugLink', route); + } + } +}